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.
Files changed (149) hide show
  1. package/README.md +44 -4
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +11 -2
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +154 -0
  64. package/src/chat/index.js +935 -2909
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +132 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1580 -0
  98. package/src/config.js +47 -1
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +661 -488
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. package/src/bus/API_DESIGN.md +0 -204
@@ -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.logger.write(JSON.stringify(logEntry) + "\n");
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.logger.write(JSON.stringify(logEntry) + "\n");
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.logger = fs.createWriteStream(logFile, { flags: "a" });
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
+ };