u-foo 1.0.6 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -4
- package/SKILLS/ufoo/SKILL.md +17 -2
- package/SKILLS/uinit/SKILL.md +8 -3
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +4 -0
- package/modules/AGENTS.template.md +14 -4
- package/modules/bus/README.md +8 -5
- package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
- package/modules/context/SKILLS/uctx/SKILL.md +3 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +12 -3
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +20 -49
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +524 -31
- package/src/agent/internalRunner.js +76 -9
- package/src/agent/launcher.js +97 -45
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +144 -4
- package/src/agent/ptyRunner.js +480 -10
- package/src/agent/ptyWrapper.js +28 -3
- package/src/agent/readyDetector.js +16 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +11 -2
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +27 -11
- package/src/bus/daemon.js +133 -5
- package/src/bus/index.js +137 -80
- package/src/bus/inject.js +47 -17
- package/src/bus/message.js +145 -17
- package/src/bus/nickname.js +3 -1
- package/src/bus/queue.js +6 -1
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +20 -4
- package/src/bus/utils.js +9 -3
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +935 -2909
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +741 -238
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +47 -1
- package/src/context/decisions.js +12 -2
- package/src/context/index.js +18 -1
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +661 -488
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +417 -179
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +32 -17
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +2 -5
- package/src/daemon/status.js +24 -1
- package/src/init/index.js +68 -14
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/status/index.js +50 -17
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/ufoo/agentsStore.js +69 -3
- package/src/utils/banner.js +5 -2
- package/scripts/.archived/bash-to-js-migration/README.md +0 -46
- package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
- package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
- package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
- package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
- package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
- package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
- package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
- package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
- package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
- package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
- package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
- package/scripts/banner.sh +0 -2
- package/src/bus/API_DESIGN.md +0 -204
package/src/agent/ptyWrapper.js
CHANGED
|
@@ -30,6 +30,8 @@ class PtyWrapper {
|
|
|
30
30
|
|
|
31
31
|
// 日志记录
|
|
32
32
|
this.logger = null;
|
|
33
|
+
this._loggerBroken = false;
|
|
34
|
+
this._loggerErrorHandler = null;
|
|
33
35
|
|
|
34
36
|
// 监控回调
|
|
35
37
|
this.monitor = null;
|
|
@@ -119,7 +121,7 @@ class PtyWrapper {
|
|
|
119
121
|
dir: "out",
|
|
120
122
|
data: this._serializeData(data),
|
|
121
123
|
};
|
|
122
|
-
this.
|
|
124
|
+
this.writeLogEntry(logEntry);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
// 3. 可选:监控回调
|
|
@@ -153,7 +155,7 @@ class PtyWrapper {
|
|
|
153
155
|
data: this._serializeData(data),
|
|
154
156
|
source: "terminal",
|
|
155
157
|
};
|
|
156
|
-
this.
|
|
158
|
+
this.writeLogEntry(logEntry);
|
|
157
159
|
}
|
|
158
160
|
};
|
|
159
161
|
stdin.on("data", this._stdinHandler);
|
|
@@ -248,7 +250,28 @@ class PtyWrapper {
|
|
|
248
250
|
if (this.logger) {
|
|
249
251
|
throw new Error("Logging already enabled");
|
|
250
252
|
}
|
|
251
|
-
this.
|
|
253
|
+
this._loggerBroken = false;
|
|
254
|
+
const logger = fs.createWriteStream(logFile, { flags: "a" });
|
|
255
|
+
this._loggerErrorHandler = (err) => {
|
|
256
|
+
this._loggerBroken = true;
|
|
257
|
+
if (process.env.UFOO_DEBUG) {
|
|
258
|
+
console.error(`[PtyWrapper] logger error: ${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
logger.on("error", this._loggerErrorHandler);
|
|
262
|
+
this.logger = logger;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
writeLogEntry(logEntry) {
|
|
266
|
+
if (!this.logger || this._loggerBroken) return;
|
|
267
|
+
try {
|
|
268
|
+
this.logger.write(JSON.stringify(logEntry) + "\n");
|
|
269
|
+
} catch (err) {
|
|
270
|
+
this._loggerBroken = true;
|
|
271
|
+
if (process.env.UFOO_DEBUG) {
|
|
272
|
+
console.error(`[PtyWrapper] logger write failed: ${err.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
252
275
|
}
|
|
253
276
|
|
|
254
277
|
/**
|
|
@@ -287,6 +310,8 @@ class PtyWrapper {
|
|
|
287
310
|
// 忽略错误
|
|
288
311
|
}
|
|
289
312
|
this.logger = null;
|
|
313
|
+
this._loggerErrorHandler = null;
|
|
314
|
+
this._loggerBroken = false;
|
|
290
315
|
}
|
|
291
316
|
|
|
292
317
|
// 2. 清理PTY(codex-44:已退出则跳过,codex-45:移除监听器)
|
|
@@ -87,6 +87,20 @@ class ReadyDetector {
|
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* 检测ufoo-code/ucode的ready标记
|
|
92
|
+
*/
|
|
93
|
+
_detectUfooCodeReady(text) {
|
|
94
|
+
if (/(?:^|\n)(?:ufoo|ucode|pi-mono)>\s*$/m.test(text)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// 与 codex 路径保持一致的兜底:行首/行尾的单独 ">"
|
|
98
|
+
if (/(?:^|\n)>\s*$/.test(text)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/**
|
|
91
105
|
* 处理PTY输出数据
|
|
92
106
|
* @param {Buffer|string} data - PTY输出数据
|
|
@@ -121,6 +135,8 @@ class ReadyDetector {
|
|
|
121
135
|
isReady = this._detectClaudeCodeReady(this.buffer);
|
|
122
136
|
} else if (this.agentType === "codex") {
|
|
123
137
|
isReady = this._detectCodexReady(this.buffer);
|
|
138
|
+
} else if (this.agentType === "ufoo" || this.agentType === "ucode" || this.agentType === "ufoo-code") {
|
|
139
|
+
isReady = this._detectUfooCodeReady(this.buffer);
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
if (isReady) {
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
const { loadConfig } = require("../config");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
function bundledModuleRoots() {
|
|
6
|
+
const repoRoot = path.join(__dirname, "..", "..");
|
|
7
|
+
return [
|
|
8
|
+
path.join(repoRoot, "src", "code"),
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveFirstExisting(paths = []) {
|
|
13
|
+
for (const candidate of paths) {
|
|
14
|
+
if (!candidate) continue;
|
|
15
|
+
try {
|
|
16
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
17
|
+
} catch {
|
|
18
|
+
// ignore
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function defaultBundledCoreRoot() {
|
|
25
|
+
const root = path.join(__dirname, "..", "code");
|
|
26
|
+
const agentEntry = path.join(root, "agent.js");
|
|
27
|
+
if (resolveFirstExisting([agentEntry])) return root;
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultBundledPromptFile() {
|
|
32
|
+
const moduleRoots = bundledModuleRoots();
|
|
33
|
+
const candidates = moduleRoots.map((root) => path.join(root, "UCODE_PROMPT.md"));
|
|
34
|
+
return resolveFirstExisting(candidates) || candidates[0];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isWindowsPlatform() {
|
|
38
|
+
return process.platform === "win32";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function canExecutePath(filePath = "") {
|
|
42
|
+
const target = String(filePath || "").trim();
|
|
43
|
+
if (!target) return false;
|
|
44
|
+
try {
|
|
45
|
+
const stat = fs.statSync(target);
|
|
46
|
+
if (!stat.isFile()) return false;
|
|
47
|
+
if (isWindowsPlatform()) return true;
|
|
48
|
+
fs.accessSync(target, fs.constants.X_OK);
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isReadableFile(filePath = "") {
|
|
56
|
+
const target = String(filePath || "").trim();
|
|
57
|
+
if (!target) return false;
|
|
58
|
+
try {
|
|
59
|
+
return fs.statSync(target).isFile();
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveExecutableFromPath(command = "", env = process.env) {
|
|
66
|
+
const text = String(command || "").trim();
|
|
67
|
+
if (!text) return "";
|
|
68
|
+
if (path.isAbsolute(text) || text.includes("/") || text.includes("\\")) {
|
|
69
|
+
return canExecutePath(text) ? path.resolve(text) : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const pathText = String((env && env.PATH) || process.env.PATH || "").trim();
|
|
73
|
+
if (!pathText) return "";
|
|
74
|
+
const dirs = pathText.split(path.delimiter).map((item) => String(item || "").trim()).filter(Boolean);
|
|
75
|
+
if (dirs.length === 0) return "";
|
|
76
|
+
|
|
77
|
+
const hasExplicitExt = /\.[a-zA-Z0-9]+$/.test(text);
|
|
78
|
+
const exts = isWindowsPlatform()
|
|
79
|
+
? String((env && env.PATHEXT) || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
|
|
80
|
+
.split(";")
|
|
81
|
+
.map((item) => item.trim())
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
: [""];
|
|
84
|
+
const suffixes = hasExplicitExt ? [""] : exts;
|
|
85
|
+
|
|
86
|
+
for (const dir of dirs) {
|
|
87
|
+
for (const ext of suffixes) {
|
|
88
|
+
const candidate = path.join(dir, `${text}${ext}`);
|
|
89
|
+
if (canExecutePath(candidate)) return candidate;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function tokenizeCommand(raw = "") {
|
|
96
|
+
const text = String(raw || "");
|
|
97
|
+
const tokens = [];
|
|
98
|
+
let current = "";
|
|
99
|
+
let quote = "";
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
102
|
+
const ch = text[i];
|
|
103
|
+
if (quote) {
|
|
104
|
+
if (quote === "\"") {
|
|
105
|
+
if (ch === "\\") {
|
|
106
|
+
if (i + 1 < text.length) {
|
|
107
|
+
const next = text[i + 1];
|
|
108
|
+
if (next === "\"" || next === "\\") {
|
|
109
|
+
current += next;
|
|
110
|
+
i += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
current += "\\";
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
} else if (quote === "'" && ch === "\\") {
|
|
118
|
+
current += "\\";
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (ch === quote) {
|
|
122
|
+
quote = "";
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
current += ch;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (ch === "'" || ch === "\"") {
|
|
130
|
+
quote = ch;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (ch === "\\") {
|
|
134
|
+
if (i + 1 < text.length) {
|
|
135
|
+
const next = text[i + 1];
|
|
136
|
+
if (/\s/.test(next) || next === "'" || next === "\"" || next === "\\") {
|
|
137
|
+
current += next;
|
|
138
|
+
i += 1;
|
|
139
|
+
} else {
|
|
140
|
+
current += "\\";
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
current += "\\";
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (/\s/.test(ch)) {
|
|
148
|
+
if (current) {
|
|
149
|
+
tokens.push(current);
|
|
150
|
+
current = "";
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
current += ch;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (quote) {
|
|
158
|
+
return String(raw || "").trim().split(/\s+/).filter(Boolean);
|
|
159
|
+
}
|
|
160
|
+
if (current) tokens.push(current);
|
|
161
|
+
return tokens;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function splitCommand(raw, fallback = "pi") {
|
|
165
|
+
const text = String(raw || "").trim();
|
|
166
|
+
if (!text) return { command: fallback, args: [] };
|
|
167
|
+
const parts = tokenizeCommand(text);
|
|
168
|
+
if (parts.length === 0) return { command: fallback, args: [] };
|
|
169
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasAnyArg(args = [], names = []) {
|
|
173
|
+
if (!Array.isArray(args) || args.length === 0) return false;
|
|
174
|
+
const flags = new Set((Array.isArray(names) ? names : []).filter(Boolean));
|
|
175
|
+
return args.some((arg) => {
|
|
176
|
+
const text = String(arg || "").trim();
|
|
177
|
+
if (!text) return false;
|
|
178
|
+
if (flags.has(text)) return true;
|
|
179
|
+
const eqIdx = text.indexOf("=");
|
|
180
|
+
if (eqIdx <= 0) return false;
|
|
181
|
+
const key = text.slice(0, eqIdx).trim();
|
|
182
|
+
return flags.has(key);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pickBinEntry(binField = {}) {
|
|
187
|
+
if (typeof binField === "string" && binField.trim()) {
|
|
188
|
+
return binField.trim();
|
|
189
|
+
}
|
|
190
|
+
if (!binField || typeof binField !== "object") return "";
|
|
191
|
+
const entries = Object.entries(binField)
|
|
192
|
+
.filter(([, value]) => typeof value === "string" && value.trim());
|
|
193
|
+
if (entries.length === 0) return "";
|
|
194
|
+
const preferred = entries.find(([name]) => /^(ucode|core|cli)$/i.test(String(name)));
|
|
195
|
+
if (preferred) return preferred[1].trim();
|
|
196
|
+
return entries[0][1].trim();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeAppendSystemPromptMode(value = "") {
|
|
200
|
+
const text = String(value || "").trim().toLowerCase();
|
|
201
|
+
if (text === "always" || text === "force" || text === "on" || text === "1" || text === "true") return "always";
|
|
202
|
+
if (text === "never" || text === "off" || text === "0" || text === "false" || text === "disable") return "never";
|
|
203
|
+
return "auto";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isLikelyPiCoreCommand(command = "", args = []) {
|
|
207
|
+
const cmdText = String(command || "").trim();
|
|
208
|
+
const cmdBase = path.basename(cmdText).toLowerCase();
|
|
209
|
+
if (cmdBase === "ucode" || cmdBase === "ucode.exe") return true;
|
|
210
|
+
if (cmdBase === "ucode-core" || cmdBase === "ucode-core.exe") return true;
|
|
211
|
+
|
|
212
|
+
const joined = [cmdText, ...(Array.isArray(args) ? args : [])]
|
|
213
|
+
.map((part) => String(part || "").toLowerCase())
|
|
214
|
+
.join(" ");
|
|
215
|
+
if (!joined) return false;
|
|
216
|
+
if (joined.includes("ucode-core")) return true;
|
|
217
|
+
if (joined.includes("/src/code/agent.js")) return true;
|
|
218
|
+
if (joined.includes("\\src\\code\\agent.js")) return true;
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function readLastArgValue(args = [], flag = "") {
|
|
223
|
+
if (!Array.isArray(args) || !flag) return "";
|
|
224
|
+
let value = "";
|
|
225
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
226
|
+
const item = String(args[i] || "").trim();
|
|
227
|
+
if (!item) continue;
|
|
228
|
+
if (item === flag) {
|
|
229
|
+
const next = String(args[i + 1] || "").trim();
|
|
230
|
+
if (next) value = next;
|
|
231
|
+
i += 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (item.startsWith(`${flag}=`)) {
|
|
235
|
+
const inlineValue = item.slice(flag.length + 1).trim();
|
|
236
|
+
if (inlineValue) value = inlineValue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function resolveCoreFromPath(coreRoot = "") {
|
|
243
|
+
const requestedRoot = String(coreRoot || "").trim();
|
|
244
|
+
if (!requestedRoot) return null;
|
|
245
|
+
let stat;
|
|
246
|
+
try {
|
|
247
|
+
stat = fs.statSync(requestedRoot);
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (!stat.isDirectory()) return null;
|
|
252
|
+
|
|
253
|
+
const candidates = [
|
|
254
|
+
requestedRoot,
|
|
255
|
+
path.join(requestedRoot, "packages", "coding-agent"),
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
for (const root of candidates) {
|
|
259
|
+
const packageFile = path.join(root, "package.json");
|
|
260
|
+
let pkg = null;
|
|
261
|
+
try {
|
|
262
|
+
pkg = JSON.parse(fs.readFileSync(packageFile, "utf8"));
|
|
263
|
+
} catch {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const binRel = pickBinEntry(pkg && pkg.bin ? pkg.bin : {});
|
|
267
|
+
if (!binRel) continue;
|
|
268
|
+
const binAbs = path.resolve(root, binRel);
|
|
269
|
+
if (!fs.existsSync(binAbs)) continue;
|
|
270
|
+
return {
|
|
271
|
+
command: process.execPath,
|
|
272
|
+
args: [binAbs],
|
|
273
|
+
root,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function resolveCandidateCoreRoot({
|
|
280
|
+
env = process.env,
|
|
281
|
+
config = {},
|
|
282
|
+
} = {}) {
|
|
283
|
+
// Native-only mode: external pi-mono path is no longer used as launch fallback.
|
|
284
|
+
// Keep function for compatibility with older diagnostic surfaces.
|
|
285
|
+
void env;
|
|
286
|
+
void config;
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function resolveNativeFallbackCommand({ env = process.env } = {}) {
|
|
291
|
+
const candidates = [
|
|
292
|
+
path.resolve(__dirname, "..", "code", "agent.js"),
|
|
293
|
+
path.resolve(__dirname, "..", "..", "bin", "ucode-core.js"),
|
|
294
|
+
];
|
|
295
|
+
for (const entry of candidates) {
|
|
296
|
+
try {
|
|
297
|
+
if (isReadableFile(entry)) {
|
|
298
|
+
if (entry.endsWith("agent.js")) {
|
|
299
|
+
return {
|
|
300
|
+
command: process.execPath,
|
|
301
|
+
args: [entry],
|
|
302
|
+
root: path.resolve(__dirname, "..", "code"),
|
|
303
|
+
kind: "native",
|
|
304
|
+
available: true,
|
|
305
|
+
resolvedPath: entry,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
command: process.execPath,
|
|
310
|
+
args: [entry, "agent"],
|
|
311
|
+
root: path.resolve(__dirname, "..", "code"),
|
|
312
|
+
kind: "native",
|
|
313
|
+
available: true,
|
|
314
|
+
resolvedPath: entry,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
// ignore
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const resolvedCommand = resolveExecutableFromPath("ucode-core", env);
|
|
322
|
+
if (resolvedCommand) {
|
|
323
|
+
return {
|
|
324
|
+
command: "ucode-core",
|
|
325
|
+
args: ["agent"],
|
|
326
|
+
root: "",
|
|
327
|
+
kind: "native",
|
|
328
|
+
available: true,
|
|
329
|
+
resolvedPath: resolvedCommand,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
command: "ucode-core",
|
|
334
|
+
args: ["agent"],
|
|
335
|
+
root: "",
|
|
336
|
+
kind: "native",
|
|
337
|
+
available: false,
|
|
338
|
+
resolvedPath: "",
|
|
339
|
+
missingReason: "src/code/agent.js not found and ucode-core is not available on PATH",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function resolveUcodeLaunch({
|
|
344
|
+
argv = [],
|
|
345
|
+
env = process.env,
|
|
346
|
+
cwd = process.cwd(),
|
|
347
|
+
loadConfigImpl = loadConfig,
|
|
348
|
+
} = {}) {
|
|
349
|
+
const config = loadConfigImpl(cwd);
|
|
350
|
+
const configuredProvider = String(
|
|
351
|
+
env.UFOO_UCODE_PROVIDER
|
|
352
|
+
|| config.ucodeProvider
|
|
353
|
+
|| ""
|
|
354
|
+
).trim();
|
|
355
|
+
const configuredModel = String(
|
|
356
|
+
env.UFOO_UCODE_MODEL
|
|
357
|
+
|| config.ucodeModel
|
|
358
|
+
|| ""
|
|
359
|
+
).trim();
|
|
360
|
+
|
|
361
|
+
const nativeCore = resolveNativeFallbackCommand({ env });
|
|
362
|
+
const command = nativeCore.command;
|
|
363
|
+
const baseArgs = Array.isArray(nativeCore.args) ? nativeCore.args.slice() : [];
|
|
364
|
+
const passthrough = Array.isArray(argv) ? argv.slice() : [];
|
|
365
|
+
const finalArgs = [...baseArgs, ...passthrough];
|
|
366
|
+
const hasProviderArg = hasAnyArg(finalArgs, ["--provider"]);
|
|
367
|
+
const hasModelArg = hasAnyArg(finalArgs, ["--model"]);
|
|
368
|
+
if (!hasProviderArg && configuredProvider) finalArgs.push("--provider", configuredProvider);
|
|
369
|
+
if (!hasModelArg && configuredModel) finalArgs.push("--model", configuredModel);
|
|
370
|
+
const promptFile = String(
|
|
371
|
+
env.UFOO_UCODE_PROMPT_FILE
|
|
372
|
+
|| config.ucodePromptFile
|
|
373
|
+
|| defaultBundledPromptFile()
|
|
374
|
+
).trim();
|
|
375
|
+
const bootstrapFile = String(
|
|
376
|
+
env.UFOO_UCODE_BOOTSTRAP_FILE
|
|
377
|
+
|| config.ucodeBootstrapFile
|
|
378
|
+
|| path.join(cwd || process.cwd(), ".ufoo", "agent", "ucode", "bootstrap.md")
|
|
379
|
+
).trim();
|
|
380
|
+
const appendSystemPrompt = String(
|
|
381
|
+
env.UFOO_UCODE_APPEND_SYSTEM_PROMPT
|
|
382
|
+
|| config.ucodeAppendSystemPrompt
|
|
383
|
+
|| bootstrapFile
|
|
384
|
+
).trim();
|
|
385
|
+
const appendSystemPromptMode = normalizeAppendSystemPromptMode(
|
|
386
|
+
env.UFOO_UCODE_APPEND_SYSTEM_PROMPT_MODE
|
|
387
|
+
|| config.ucodeAppendSystemPromptMode
|
|
388
|
+
|| "auto"
|
|
389
|
+
);
|
|
390
|
+
const hasSystemPromptArg = hasAnyArg(finalArgs, ["--system-prompt", "--append-system-prompt"]);
|
|
391
|
+
const appendSupported = appendSystemPromptMode === "always"
|
|
392
|
+
|| (
|
|
393
|
+
appendSystemPromptMode === "auto"
|
|
394
|
+
&& (
|
|
395
|
+
nativeCore.kind === "native"
|
|
396
|
+
|| isLikelyPiCoreCommand(command, finalArgs)
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
if (!hasSystemPromptArg && appendSystemPrompt && appendSystemPromptMode !== "never" && appendSupported) {
|
|
400
|
+
finalArgs.push("--append-system-prompt", appendSystemPrompt);
|
|
401
|
+
}
|
|
402
|
+
const effectiveProvider = readLastArgValue(finalArgs, "--provider");
|
|
403
|
+
const effectiveModel = readLastArgValue(finalArgs, "--model");
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
agentType: "ufoo-code",
|
|
407
|
+
command,
|
|
408
|
+
args: finalArgs,
|
|
409
|
+
env: {
|
|
410
|
+
UFOO_UCODE_PROMPT_FILE: promptFile,
|
|
411
|
+
UFOO_UCODE_PROJECT_ROOT: String(cwd || process.cwd()),
|
|
412
|
+
UFOO_UCODE_MODE: "coding-agent",
|
|
413
|
+
UFOO_UCODE_PROTOCOL_VERSION: "1",
|
|
414
|
+
UFOO_UCODE_PROVIDER: effectiveProvider,
|
|
415
|
+
UFOO_UCODE_MODEL: effectiveModel,
|
|
416
|
+
UFOO_UCODE_CORE_ROOT: nativeCore.root || "",
|
|
417
|
+
UFOO_UCODE_CORE_KIND: "native",
|
|
418
|
+
UFOO_UCODE_CORE_AVAILABLE: nativeCore.available === false ? "0" : "1",
|
|
419
|
+
UFOO_UCODE_BOOTSTRAP_FILE: bootstrapFile,
|
|
420
|
+
UFOO_UCODE_APPEND_SYSTEM_PROMPT: appendSystemPrompt,
|
|
421
|
+
UFOO_UCODE_APPEND_SYSTEM_PROMPT_MODE: appendSystemPromptMode,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
module.exports = {
|
|
427
|
+
bundledModuleRoots,
|
|
428
|
+
defaultBundledCoreRoot,
|
|
429
|
+
defaultBundledPromptFile,
|
|
430
|
+
tokenizeCommand,
|
|
431
|
+
splitCommand,
|
|
432
|
+
hasAnyArg,
|
|
433
|
+
pickBinEntry,
|
|
434
|
+
normalizeAppendSystemPromptMode,
|
|
435
|
+
isLikelyPiCoreCommand,
|
|
436
|
+
readLastArgValue,
|
|
437
|
+
resolveCoreFromPath,
|
|
438
|
+
resolveCandidateCoreRoot,
|
|
439
|
+
canExecutePath,
|
|
440
|
+
resolveExecutableFromPath,
|
|
441
|
+
resolveNativeFallbackCommand,
|
|
442
|
+
resolveUcodeLaunch,
|
|
443
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
|
+
|
|
5
|
+
function readFileSafe(filePath = "") {
|
|
6
|
+
if (!filePath) return "";
|
|
7
|
+
try {
|
|
8
|
+
return fs.readFileSync(filePath, "utf8");
|
|
9
|
+
} catch {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveProjectRules(projectRoot = "") {
|
|
15
|
+
const rules = [];
|
|
16
|
+
const agentsFile = path.join(projectRoot, "AGENTS.md");
|
|
17
|
+
const claudeFile = path.join(projectRoot, "CLAUDE.md");
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(agentsFile)) {
|
|
20
|
+
rules.push({ path: agentsFile, content: readFileSafe(agentsFile) });
|
|
21
|
+
} else if (fs.existsSync(claudeFile)) {
|
|
22
|
+
rules.push({ path: claudeFile, content: readFileSafe(claudeFile) });
|
|
23
|
+
}
|
|
24
|
+
return rules.filter((item) => item.content.trim());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function defaultBootstrapPath(projectRoot = "") {
|
|
28
|
+
const dir = path.join(getUfooPaths(projectRoot).agentDir, "ucode");
|
|
29
|
+
return path.join(dir, "bootstrap.md");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildBootstrapContent({
|
|
33
|
+
projectRoot = "",
|
|
34
|
+
promptFile = "",
|
|
35
|
+
promptText = "",
|
|
36
|
+
rules = [],
|
|
37
|
+
} = {}) {
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push("# ucode Bootstrap");
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(`Generated at: ${new Date().toISOString()}`);
|
|
42
|
+
lines.push(`Project root: ${projectRoot}`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
if (promptFile) {
|
|
45
|
+
lines.push(`Source prompt: ${promptFile}`);
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (promptText.trim()) {
|
|
50
|
+
lines.push("## Core Prompt");
|
|
51
|
+
lines.push("");
|
|
52
|
+
lines.push(promptText.trim());
|
|
53
|
+
lines.push("");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (rules.length > 0) {
|
|
57
|
+
lines.push("## Project Rules");
|
|
58
|
+
lines.push("");
|
|
59
|
+
for (const rule of rules) {
|
|
60
|
+
lines.push(`### ${rule.path}`);
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push(rule.content.trim());
|
|
63
|
+
lines.push("");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (rules.length === 0) {
|
|
68
|
+
lines.push("## Project Rules");
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push("No AGENTS.md/CLAUDE.md rules detected.");
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `${lines.join("\n")}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function prepareUcodeBootstrap({
|
|
78
|
+
projectRoot = process.cwd(),
|
|
79
|
+
promptFile = "",
|
|
80
|
+
targetFile = "",
|
|
81
|
+
} = {}) {
|
|
82
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
83
|
+
const resolvedPrompt = String(promptFile || "").trim();
|
|
84
|
+
const resolvedTarget = String(targetFile || "").trim() || defaultBootstrapPath(resolvedProjectRoot);
|
|
85
|
+
|
|
86
|
+
const promptText = readFileSafe(resolvedPrompt);
|
|
87
|
+
const rules = resolveProjectRules(resolvedProjectRoot);
|
|
88
|
+
const content = buildBootstrapContent({
|
|
89
|
+
projectRoot: resolvedProjectRoot,
|
|
90
|
+
promptFile: resolvedPrompt,
|
|
91
|
+
promptText,
|
|
92
|
+
rules,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
fs.mkdirSync(path.dirname(resolvedTarget), { recursive: true });
|
|
96
|
+
fs.writeFileSync(resolvedTarget, content, "utf8");
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
file: resolvedTarget,
|
|
101
|
+
promptFile: resolvedPrompt,
|
|
102
|
+
hasPrompt: Boolean(promptText.trim()),
|
|
103
|
+
rulesCount: rules.length,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
readFileSafe,
|
|
109
|
+
resolveProjectRules,
|
|
110
|
+
defaultBootstrapPath,
|
|
111
|
+
buildBootstrapContent,
|
|
112
|
+
prepareUcodeBootstrap,
|
|
113
|
+
};
|