u-foo 1.0.3 → 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 (179) hide show
  1. package/README.md +110 -11
  2. package/README.zh-CN.md +9 -7
  3. package/SKILLS/ufoo/SKILL.md +132 -0
  4. package/SKILLS/uinit/SKILL.md +78 -0
  5. package/SKILLS/ustatus/SKILL.md +36 -0
  6. package/bin/uclaude.js +13 -0
  7. package/bin/ucode-core.js +15 -0
  8. package/bin/ucode.js +125 -0
  9. package/bin/ucodex.js +13 -0
  10. package/bin/ufoo +9 -31
  11. package/bin/ufoo-assistant-agent.js +5 -0
  12. package/bin/ufoo-engine.js +25 -0
  13. package/bin/ufoo.js +17 -0
  14. package/modules/AGENTS.template.md +29 -11
  15. package/modules/bus/README.md +33 -25
  16. package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
  17. package/modules/context/README.md +18 -40
  18. package/modules/context/SKILLS/uctx/SKILL.md +63 -1
  19. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  20. package/package.json +25 -4
  21. package/scripts/import-pi-mono.js +124 -0
  22. package/scripts/postinstall.js +30 -0
  23. package/scripts/sync-claude-skills.sh +21 -0
  24. package/src/agent/cliRunner.js +554 -33
  25. package/src/agent/internalRunner.js +150 -56
  26. package/src/agent/launcher.js +754 -0
  27. package/src/agent/normalizeOutput.js +1 -1
  28. package/src/agent/notifier.js +340 -0
  29. package/src/agent/ptyRunner.js +847 -0
  30. package/src/agent/ptyWrapper.js +379 -0
  31. package/src/agent/readyDetector.js +175 -0
  32. package/src/agent/ucode.js +443 -0
  33. package/src/agent/ucodeBootstrap.js +113 -0
  34. package/src/agent/ucodeBuild.js +67 -0
  35. package/src/agent/ucodeDoctor.js +184 -0
  36. package/src/agent/ucodeRuntimeConfig.js +129 -0
  37. package/src/agent/ufooAgent.js +46 -42
  38. package/src/assistant/agent.js +260 -0
  39. package/src/assistant/bridge.js +172 -0
  40. package/src/assistant/engine.js +252 -0
  41. package/src/assistant/stdio.js +58 -0
  42. package/src/assistant/ufooEngineCli.js +306 -0
  43. package/src/bus/activate.js +172 -0
  44. package/src/bus/daemon.js +436 -0
  45. package/src/bus/index.js +842 -0
  46. package/src/bus/inject.js +315 -0
  47. package/src/bus/message.js +430 -0
  48. package/src/bus/nickname.js +88 -0
  49. package/src/bus/queue.js +136 -0
  50. package/src/bus/shake.js +26 -0
  51. package/src/bus/store.js +189 -0
  52. package/src/bus/subscriber.js +312 -0
  53. package/src/bus/utils.js +363 -0
  54. package/src/chat/agentBar.js +117 -0
  55. package/src/chat/agentDirectory.js +88 -0
  56. package/src/chat/agentSockets.js +225 -0
  57. package/src/chat/agentViewController.js +298 -0
  58. package/src/chat/chatLogController.js +115 -0
  59. package/src/chat/commandExecutor.js +700 -0
  60. package/src/chat/commands.js +132 -0
  61. package/src/chat/completionController.js +414 -0
  62. package/src/chat/cronScheduler.js +160 -0
  63. package/src/chat/daemonConnection.js +166 -0
  64. package/src/chat/daemonCoordinator.js +64 -0
  65. package/src/chat/daemonMessageRouter.js +257 -0
  66. package/src/chat/daemonReconnect.js +41 -0
  67. package/src/chat/daemonTransport.js +36 -0
  68. package/src/chat/daemonTransportDefaults.js +10 -0
  69. package/src/chat/dashboardKeyController.js +480 -0
  70. package/src/chat/dashboardView.js +154 -0
  71. package/src/chat/index.js +1011 -1392
  72. package/src/chat/inputHistoryController.js +105 -0
  73. package/src/chat/inputListenerController.js +304 -0
  74. package/src/chat/inputMath.js +104 -0
  75. package/src/chat/inputSubmitHandler.js +171 -0
  76. package/src/chat/layout.js +165 -0
  77. package/src/chat/pasteController.js +81 -0
  78. package/src/chat/rawKeyMap.js +42 -0
  79. package/src/chat/settingsController.js +132 -0
  80. package/src/chat/statusLineController.js +177 -0
  81. package/src/chat/streamTracker.js +138 -0
  82. package/src/chat/text.js +70 -0
  83. package/src/chat/transport.js +61 -0
  84. package/src/cli/busCoreCommands.js +59 -0
  85. package/src/cli/ctxCoreCommands.js +199 -0
  86. package/src/cli/onlineCoreCommands.js +379 -0
  87. package/src/cli.js +1162 -96
  88. package/src/code/README.md +29 -0
  89. package/src/code/UCODE_PROMPT.md +32 -0
  90. package/src/code/agent.js +1651 -0
  91. package/src/code/cli.js +158 -0
  92. package/src/code/config +0 -0
  93. package/src/code/dispatch.js +42 -0
  94. package/src/code/index.js +70 -0
  95. package/src/code/nativeRunner.js +1213 -0
  96. package/src/code/runtime.js +154 -0
  97. package/src/code/sessionStore.js +162 -0
  98. package/src/code/taskDecomposer.js +269 -0
  99. package/src/code/tools/bash.js +53 -0
  100. package/src/code/tools/common.js +42 -0
  101. package/src/code/tools/edit.js +70 -0
  102. package/src/code/tools/read.js +44 -0
  103. package/src/code/tools/write.js +35 -0
  104. package/src/code/tui.js +1580 -0
  105. package/src/config.js +56 -3
  106. package/src/context/decisions.js +324 -0
  107. package/src/context/doctor.js +183 -0
  108. package/src/context/index.js +55 -0
  109. package/src/context/sync.js +127 -0
  110. package/src/daemon/agentProcessManager.js +74 -0
  111. package/src/daemon/cronOps.js +241 -0
  112. package/src/daemon/index.js +998 -170
  113. package/src/daemon/ipcServer.js +99 -0
  114. package/src/daemon/ops.js +630 -48
  115. package/src/daemon/promptLoop.js +319 -0
  116. package/src/daemon/promptRequest.js +101 -0
  117. package/src/daemon/providerSessions.js +306 -0
  118. package/src/daemon/reporting.js +90 -0
  119. package/src/daemon/run.js +31 -1
  120. package/src/daemon/status.js +48 -8
  121. package/src/doctor/index.js +50 -0
  122. package/src/init/index.js +318 -0
  123. package/src/online/bridge.js +663 -0
  124. package/src/online/client.js +245 -0
  125. package/src/online/runner.js +253 -0
  126. package/src/online/server.js +992 -0
  127. package/src/online/tokens.js +103 -0
  128. package/src/report/store.js +331 -0
  129. package/src/shared/eventContract.js +35 -0
  130. package/src/shared/ptySocketContract.js +21 -0
  131. package/src/skills/index.js +159 -0
  132. package/src/status/index.js +285 -0
  133. package/src/terminal/adapterContract.js +87 -0
  134. package/src/terminal/adapterRouter.js +84 -0
  135. package/src/terminal/adapters/externalAdapter.js +14 -0
  136. package/src/terminal/adapters/internalAdapter.js +13 -0
  137. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  138. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  139. package/src/terminal/adapters/terminalAdapter.js +31 -0
  140. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  141. package/src/terminal/detect.js +64 -0
  142. package/src/terminal/index.js +8 -0
  143. package/src/terminal/iterm2.js +126 -0
  144. package/src/ufoo/agentsStore.js +107 -0
  145. package/src/ufoo/paths.js +46 -0
  146. package/src/utils/banner.js +76 -0
  147. package/bin/uclaude +0 -65
  148. package/bin/ucodex +0 -65
  149. package/modules/bus/scripts/bus-alert.sh +0 -185
  150. package/modules/bus/scripts/bus-listen.sh +0 -117
  151. package/modules/context/ASSUMPTIONS.md +0 -7
  152. package/modules/context/CONSTRAINTS.md +0 -7
  153. package/modules/context/CONTEXT-STRUCTURE.md +0 -49
  154. package/modules/context/DECISION-PROTOCOL.md +0 -62
  155. package/modules/context/HANDOFF.md +0 -33
  156. package/modules/context/RULES.md +0 -15
  157. package/modules/context/SKILLS/README.md +0 -14
  158. package/modules/context/SYSTEM.md +0 -18
  159. package/modules/context/TEMPLATES/assumptions.md +0 -4
  160. package/modules/context/TEMPLATES/constraints.md +0 -4
  161. package/modules/context/TEMPLATES/decision.md +0 -16
  162. package/modules/context/TEMPLATES/project-context-readme.md +0 -6
  163. package/modules/context/TEMPLATES/system.md +0 -3
  164. package/modules/context/TEMPLATES/terminology.md +0 -4
  165. package/modules/context/TERMINOLOGY.md +0 -10
  166. package/scripts/banner.sh +0 -89
  167. package/scripts/bus-alert.sh +0 -6
  168. package/scripts/bus-autotrigger.sh +0 -6
  169. package/scripts/bus-daemon.sh +0 -231
  170. package/scripts/bus-inject.sh +0 -144
  171. package/scripts/bus-listen.sh +0 -6
  172. package/scripts/bus.sh +0 -984
  173. package/scripts/context-decisions.sh +0 -167
  174. package/scripts/context-doctor.sh +0 -72
  175. package/scripts/context-lint.sh +0 -110
  176. package/scripts/doctor.sh +0 -22
  177. package/scripts/init.sh +0 -247
  178. package/scripts/skills.sh +0 -113
  179. package/scripts/status.sh +0 -125
@@ -0,0 +1,379 @@
1
+ const pty = require("node-pty");
2
+ const fs = require("fs");
3
+ const { isITerm2 } = require("../terminal/detect");
4
+
5
+ /**
6
+ * PTY Wrapper - 包装原始agent命令,提供IO控制和监控
7
+ *
8
+ * 特性:
9
+ * - 透明的PTY包装(保持用户体验一致)
10
+ * - JSONL格式日志记录(utf8优先编码)
11
+ * - 可插拔的IO适配器(未来扩展)
12
+ * - 完善的资源清理(防止泄漏)
13
+ *
14
+ * 参考:
15
+ * - ptyRunner.js - PTY实现参考
16
+ * - codex-44 review反馈 (2026-02-05)
17
+ */
18
+ class PtyWrapper {
19
+ constructor(command, args, options = {}) {
20
+ this.command = command;
21
+ this.args = args;
22
+ this.options = options;
23
+
24
+ // PTY实例
25
+ this.pty = null;
26
+
27
+ // IO流引用(用于cleanup)
28
+ this.stdin = null;
29
+ this.stdout = null;
30
+
31
+ // 日志记录
32
+ this.logger = null;
33
+ this._loggerBroken = false;
34
+ this._loggerErrorHandler = null;
35
+
36
+ // 监控回调
37
+ this.monitor = null;
38
+
39
+ // 退出回调(不直接调用process.exit)
40
+ this.onExit = null;
41
+
42
+ // 可插拔的IO适配器(未来扩展)
43
+ this.ioAdapter = options.ioAdapter || null;
44
+
45
+ // 事件处理器引用(用于cleanup)
46
+ this._stdinHandler = null;
47
+ this._ptyDataHandler = null;
48
+ this._ptyExitHandler = null;
49
+ this._resizeHandler = null;
50
+
51
+ // 清理标志(防止重复清理)
52
+ this._cleaned = false;
53
+ }
54
+
55
+ /**
56
+ * 启动PTY进程
57
+ */
58
+ spawn() {
59
+ if (this.pty) {
60
+ throw new Error("PTY already spawned");
61
+ }
62
+
63
+ // Preserve iTerm2 env vars so child processes can detect the terminal
64
+ const termEnv = {};
65
+ if (isITerm2()) {
66
+ if (process.env.ITERM_SESSION_ID) termEnv.ITERM_SESSION_ID = process.env.ITERM_SESSION_ID;
67
+ if (process.env.TERM_PROGRAM) termEnv.TERM_PROGRAM = process.env.TERM_PROGRAM;
68
+ if (process.env.TERM_PROGRAM_VERSION) termEnv.TERM_PROGRAM_VERSION = process.env.TERM_PROGRAM_VERSION;
69
+ }
70
+
71
+ this.pty = pty.spawn(this.command, this.args, {
72
+ name: "xterm-256color",
73
+ cols: this.stdout?.columns || process.stdout.columns || 80,
74
+ rows: this.stdout?.rows || process.stdout.rows || 24,
75
+ cwd: this.options.cwd || process.cwd(),
76
+ env: { ...process.env, ...termEnv, ...(this.options.env || {}) },
77
+ });
78
+
79
+ return this.pty;
80
+ }
81
+
82
+ /**
83
+ * 连接输入输出流
84
+ *
85
+ * @param {Stream} stdin - 标准输入流
86
+ * @param {Stream} stdout - 标准输出流
87
+ * @param {Stream} stderr - 标准错误流(PTY会合流,此参数保留用于兼容)
88
+ */
89
+ attachStreams(stdin, stdout, stderr) {
90
+ if (!this.pty) {
91
+ throw new Error("PTY not spawned yet. Call spawn() first.");
92
+ }
93
+
94
+ // 保存引用(用于cleanup)
95
+ this.stdin = stdin;
96
+ this.stdout = stdout;
97
+
98
+ if (this.ioAdapter) {
99
+ // 使用IO适配器(未来扩展)
100
+ this.ioAdapter.attach(this.pty, stdin, stdout, stderr);
101
+ } else {
102
+ // 当前:直接连接streams
103
+ this._attachDirectStreams(stdin, stdout);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * 直接连接streams(当前实现)
109
+ * @private
110
+ */
111
+ _attachDirectStreams(stdin, stdout) {
112
+ // PTY输出 -> stdout
113
+ this._ptyDataHandler = (data) => {
114
+ // 1. 输出到terminal
115
+ stdout.write(data);
116
+
117
+ // 2. 可选:日志记录(JSONL格式)
118
+ if (this.logger) {
119
+ const logEntry = {
120
+ ts: Date.now(),
121
+ dir: "out",
122
+ data: this._serializeData(data),
123
+ };
124
+ this.writeLogEntry(logEntry);
125
+ }
126
+
127
+ // 3. 可选:监控回调
128
+ if (this.monitor) {
129
+ try {
130
+ this.monitor.onOutput(data);
131
+ } catch (err) {
132
+ // 监控失败不应影响IO通路
133
+ if (process.env.UFOO_DEBUG) {
134
+ console.error("[PtyWrapper] Monitor error:", err);
135
+ }
136
+ }
137
+ }
138
+ };
139
+ this.pty.onData(this._ptyDataHandler);
140
+
141
+ // stdin -> PTY(支持raw mode和控制字符)
142
+ if (stdin.isTTY && typeof stdin.setRawMode === "function") {
143
+ stdin.setRawMode(true);
144
+ }
145
+ stdin.resume();
146
+
147
+ this._stdinHandler = (data) => {
148
+ this.pty.write(data);
149
+
150
+ // 可选:日志记录
151
+ if (this.logger) {
152
+ const logEntry = {
153
+ ts: Date.now(),
154
+ dir: "in",
155
+ data: this._serializeData(data),
156
+ source: "terminal",
157
+ };
158
+ this.writeLogEntry(logEntry);
159
+ }
160
+ };
161
+ stdin.on("data", this._stdinHandler);
162
+
163
+ // 终端大小变化(codex-44:判断isTTY)
164
+ if (stdout.isTTY) {
165
+ this._resizeHandler = () => {
166
+ if (this.pty && !this.pty._closed) {
167
+ // codex-45:默认值兜底(极端环境可能undefined)
168
+ const cols = stdout.columns || 80;
169
+ const rows = stdout.rows || 24;
170
+ this.pty.resize(cols, rows);
171
+ }
172
+ };
173
+ stdout.on("resize", this._resizeHandler);
174
+ }
175
+
176
+ // 进程退出
177
+ this._ptyExitHandler = ({ exitCode, signal }) => {
178
+ this.cleanup();
179
+
180
+ // 回调给launcher处理退出(不直接process.exit)
181
+ if (this.onExit) {
182
+ this.onExit({ exitCode, signal });
183
+ }
184
+ };
185
+ this.pty.onExit(this._ptyExitHandler);
186
+ }
187
+
188
+ /**
189
+ * 写入数据到PTY(用于外部inject)
190
+ *
191
+ * @param {string|Buffer} data - 要写入的数据
192
+ * @returns {boolean} 是否成功
193
+ */
194
+ write(data) {
195
+ if (!this.pty || this.pty._closed) {
196
+ return false;
197
+ }
198
+ try {
199
+ this.pty.write(data);
200
+ return true;
201
+ } catch (err) {
202
+ if (process.env.UFOO_DEBUG) {
203
+ console.error(`[PtyWrapper] write error: ${err.message}`);
204
+ }
205
+ return false;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * 数据序列化(智能编码)
211
+ *
212
+ * codex-44建议:utf8优先,失败才base64
213
+ *
214
+ * @private
215
+ * @param {Buffer|String} data - 原始数据
216
+ * @returns {Object} 序列化后的数据对象
217
+ */
218
+ _serializeData(data) {
219
+ const buf = Buffer.from(data);
220
+
221
+ // 尝试utf8解码
222
+ try {
223
+ const str = buf.toString("utf8");
224
+ // 验证解码结果(检测replacement character \uFFFD)
225
+ if (!str.includes("\uFFFD")) {
226
+ return {
227
+ text: str,
228
+ encoding: "utf8",
229
+ size: buf.length
230
+ };
231
+ }
232
+ } catch (err) {
233
+ // utf8解码失败,使用base64
234
+ }
235
+
236
+ // 二进制数据使用base64
237
+ return {
238
+ text: buf.toString("base64"),
239
+ encoding: "base64",
240
+ size: buf.length
241
+ };
242
+ }
243
+
244
+ /**
245
+ * 启用日志记录
246
+ *
247
+ * @param {String} logFile - 日志文件路径(JSONL格式)
248
+ */
249
+ enableLogging(logFile) {
250
+ if (this.logger) {
251
+ throw new Error("Logging already enabled");
252
+ }
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
+ }
275
+ }
276
+
277
+ /**
278
+ * 启用监控
279
+ *
280
+ * @param {Function} monitorCallback - 监控回调函数
281
+ */
282
+ enableMonitoring(monitorCallback) {
283
+ this.monitor = {
284
+ onOutput: monitorCallback,
285
+ };
286
+ }
287
+
288
+ /**
289
+ * 清理资源(codex-44:完善的清理逻辑,codex-45:幂等性)
290
+ *
291
+ * 注意:
292
+ * - 处理异常路径(try-catch)
293
+ * - 移除所有监听器(防止泄漏)
294
+ * - 检查PTY状态(已退出则跳过kill)
295
+ * - 恢复terminal状态(raw mode)
296
+ * - 幂等性(可以安全重复调用)
297
+ */
298
+ cleanup() {
299
+ // codex-45:防止重复清理
300
+ if (this._cleaned) {
301
+ return;
302
+ }
303
+ this._cleaned = true;
304
+
305
+ // 1. 关闭日志流
306
+ if (this.logger) {
307
+ try {
308
+ this.logger.end();
309
+ } catch (err) {
310
+ // 忽略错误
311
+ }
312
+ this.logger = null;
313
+ this._loggerErrorHandler = null;
314
+ this._loggerBroken = false;
315
+ }
316
+
317
+ // 2. 清理PTY(codex-44:已退出则跳过,codex-45:移除监听器)
318
+ if (this.pty) {
319
+ // 移除PTY监听器
320
+ try {
321
+ if (this._ptyDataHandler) {
322
+ this.pty.removeListener("data", this._ptyDataHandler);
323
+ this._ptyDataHandler = null;
324
+ }
325
+ if (this._ptyExitHandler) {
326
+ this.pty.removeListener("exit", this._ptyExitHandler);
327
+ this._ptyExitHandler = null;
328
+ }
329
+ } catch (err) {
330
+ // 忽略移除监听器的错误
331
+ }
332
+
333
+ // Kill PTY进程(已退出则跳过)
334
+ if (!this.pty._closed) {
335
+ try {
336
+ this.pty.kill();
337
+ } catch (err) {
338
+ // PTY可能已经退出,忽略错误
339
+ }
340
+ }
341
+ this.pty = null;
342
+ }
343
+
344
+ // 3. 清理stdin(codex-45:使用保存的handler引用)
345
+ if (this.stdin) {
346
+ // 移除data监听器
347
+ if (this._stdinHandler) {
348
+ this.stdin.removeListener("data", this._stdinHandler);
349
+ this._stdinHandler = null;
350
+ }
351
+
352
+ // 恢复terminal状态(codex-44:异常路径也要恢复)
353
+ if (this.stdin.isTTY && typeof this.stdin.setRawMode === "function") {
354
+ try {
355
+ this.stdin.setRawMode(false);
356
+ } catch (err) {
357
+ // 恢复失败不阻塞退出
358
+ }
359
+ }
360
+
361
+ this.stdin = null;
362
+ }
363
+
364
+ // 4. 清理stdout(codex-44:移除resize监听器)
365
+ if (this.stdout) {
366
+ if (this.stdout.isTTY && this._resizeHandler) {
367
+ this.stdout.removeListener("resize", this._resizeHandler);
368
+ this._resizeHandler = null;
369
+ }
370
+ this.stdout = null;
371
+ }
372
+
373
+ // 5. 清理监控回调
374
+ this.monitor = null;
375
+ this.onExit = null;
376
+ }
377
+ }
378
+
379
+ module.exports = PtyWrapper;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Agent Ready检测器
3
+ * 通过分析PTY输出判断agent是否初始化完成并ready接收命令
4
+ */
5
+ class ReadyDetector {
6
+ constructor(agentType) {
7
+ this.agentType = agentType;
8
+ this.ready = false;
9
+ this.buffer = ""; // 缓存最近的输出(用于多行匹配)
10
+ this.maxBufferSize = 2000; // 限制buffer大小
11
+ this.callbacks = [];
12
+ this.createdAt = Date.now(); // 用于性能指标
13
+ this.readyAt = null; // 记录ready的时间
14
+ }
15
+
16
+ /**
17
+ * 注册ready回调
18
+ */
19
+ onReady(callback) {
20
+ if (this.ready) {
21
+ // 已经ready,立即执行
22
+ callback();
23
+ } else {
24
+ this.callbacks.push(callback);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 触发ready状态
30
+ */
31
+ _triggerReady() {
32
+ if (this.ready) return;
33
+ this.ready = true;
34
+ this.readyAt = Date.now();
35
+
36
+ // 计算检测耗时
37
+ const detectionTime = this.readyAt - this.createdAt;
38
+
39
+ if (process.env.UFOO_DEBUG) {
40
+ console.error(`[ReadyDetector] ${this.agentType} ready detected in ${detectionTime}ms`);
41
+ }
42
+
43
+ this.callbacks.forEach((cb) => {
44
+ try {
45
+ cb();
46
+ } catch (err) {
47
+ // 忽略回调错误,但在debug模式下记录
48
+ if (process.env.UFOO_DEBUG) {
49
+ console.error(`[ReadyDetector] callback error:`, err);
50
+ }
51
+ }
52
+ });
53
+ this.callbacks = [];
54
+ }
55
+
56
+ /**
57
+ * 检测claude-code的ready标记
58
+ * 特征:prompt "❯" 或分隔线 "────────"
59
+ */
60
+ _detectClaudeCodeReady(text) {
61
+ // 1. 检测prompt标记(更可靠)
62
+ if (text.includes("❯")) {
63
+ return true;
64
+ }
65
+
66
+ // 2. 检测分隔线(banner完成后的标记)
67
+ if (text.includes("────────") && text.includes("Try")) {
68
+ return true;
69
+ }
70
+
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * 检测codex的ready标记
76
+ */
77
+ _detectCodexReady(text) {
78
+ // Codex的prompt检测(更严格,避免误报)
79
+ // 1. 明确的 "codex>" prompt
80
+ if (text.includes("codex>")) {
81
+ return true;
82
+ }
83
+ // 2. 行首或行尾的单独 ">" prompt(避免匹配JSON/HTML中的>)
84
+ if (/(?:^|\n)>\s*$/.test(text)) {
85
+ return true;
86
+ }
87
+ return false;
88
+ }
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
+
104
+ /**
105
+ * 处理PTY输出数据
106
+ * @param {Buffer|string} data - PTY输出数据
107
+ */
108
+ processOutput(data) {
109
+ if (this.ready) return; // 已经ready,跳过后续检测
110
+
111
+ // 跳过null/undefined
112
+ if (data == null) return;
113
+
114
+ // 转换为字符串
115
+ const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
116
+
117
+ if (!text) return; // 跳过空输入
118
+
119
+ // 追加到buffer
120
+ this.buffer += text;
121
+
122
+ // 限制buffer大小(防止内存泄漏)
123
+ if (this.buffer.length > this.maxBufferSize) {
124
+ const keepSize = Math.floor(this.maxBufferSize * 0.5); // 保留50%
125
+ this.buffer = this.buffer.slice(-keepSize);
126
+
127
+ if (process.env.UFOO_DEBUG) {
128
+ console.error(`[ReadyDetector] buffer trimmed, keeping last ${keepSize} bytes`);
129
+ }
130
+ }
131
+
132
+ // 根据agentType检测ready标记
133
+ let isReady = false;
134
+ if (this.agentType === "claude-code") {
135
+ isReady = this._detectClaudeCodeReady(this.buffer);
136
+ } else if (this.agentType === "codex") {
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);
140
+ }
141
+
142
+ if (isReady) {
143
+ if (process.env.UFOO_DEBUG) {
144
+ console.error(`[ReadyDetector] prompt detected in buffer (${this.buffer.length} bytes)`);
145
+ }
146
+ this._triggerReady();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 强制标记为ready(用于fallback超时)
152
+ */
153
+ forceReady() {
154
+ if (process.env.UFOO_DEBUG && !this.ready) {
155
+ console.error(`[ReadyDetector] force ready triggered after ${Date.now() - this.createdAt}ms`);
156
+ }
157
+ this._triggerReady();
158
+ }
159
+
160
+ /**
161
+ * 获取性能指标
162
+ */
163
+ getMetrics() {
164
+ return {
165
+ agentType: this.agentType,
166
+ ready: this.ready,
167
+ createdAt: this.createdAt,
168
+ readyAt: this.readyAt,
169
+ detectionTimeMs: this.readyAt ? this.readyAt - this.createdAt : null,
170
+ bufferSize: this.buffer.length,
171
+ };
172
+ }
173
+ }
174
+
175
+ module.exports = ReadyDetector;