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,315 @@
1
+ const { spawn } = require("child_process");
2
+ const fs = require("fs");
3
+ const net = require("net");
4
+ const path = require("path");
5
+ const { subscriberToSafeName, isValidTty } = require("./utils");
6
+ const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
7
+
8
+ const SHOULD_LOG_INJECT = process.env.UFOO_INJECT_DEBUG === "1";
9
+ const logInject = (message) => {
10
+ if (SHOULD_LOG_INJECT) {
11
+ console.log(message);
12
+ }
13
+ };
14
+
15
+ /**
16
+ * 命令注入器 - 将命令注入到终端
17
+ *
18
+ * 支持的方式:
19
+ * 1. PTY socket(直接写入,无需macOS权限)
20
+ * 2. tmux send-keys(无需权限)
21
+ */
22
+ class Injector {
23
+ constructor(busDir, agentsFile) {
24
+ this.busDir = busDir;
25
+ this.agentsFile = agentsFile;
26
+ }
27
+
28
+ /**
29
+ * 获取订阅者的 tty 文件路径
30
+ */
31
+ getTtyPath(subscriber) {
32
+ const safeName = subscriberToSafeName(subscriber);
33
+ return path.join(this.busDir, "queues", safeName, "tty");
34
+ }
35
+
36
+ /**
37
+ * 获取订阅者的 tmux pane ID(从 all-agents.json)
38
+ */
39
+ getAgentMeta(subscriber) {
40
+ const agentsFile = this.agentsFile;
41
+ if (!agentsFile || !fs.existsSync(agentsFile)) return null;
42
+
43
+ try {
44
+ const busData = JSON.parse(fs.readFileSync(agentsFile, "utf8"));
45
+ return busData.agents?.[subscriber] || null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ getTmuxPane(subscriber) {
52
+ const agentsFile = this.agentsFile;
53
+ if (!agentsFile || !fs.existsSync(agentsFile)) return null;
54
+
55
+ try {
56
+ const busData = JSON.parse(fs.readFileSync(agentsFile, "utf8"));
57
+ return busData.agents?.[subscriber]?.tmux_pane || null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 读取 tty 设备路径
65
+ */
66
+ readTty(subscriber) {
67
+ const ttyPath = this.getTtyPath(subscriber);
68
+ if (!fs.existsSync(ttyPath)) {
69
+ return null;
70
+ }
71
+ return fs.readFileSync(ttyPath, "utf8").trim();
72
+ }
73
+
74
+ /**
75
+ * 检查 tmux pane 是否存在
76
+ */
77
+ async checkTmuxPane(paneId) {
78
+ return new Promise((resolve) => {
79
+ const proc = spawn("tmux", ["list-panes", "-a", "-F", "#{pane_id}"]);
80
+ let output = "";
81
+
82
+ proc.stdout.on("data", (data) => {
83
+ output += data.toString();
84
+ });
85
+
86
+ proc.on("close", (code) => {
87
+ if (code !== 0) {
88
+ resolve(false);
89
+ return;
90
+ }
91
+ const panes = output.trim().split("\n");
92
+ resolve(panes.includes(paneId));
93
+ });
94
+
95
+ proc.on("error", () => resolve(false));
96
+ });
97
+ }
98
+
99
+ /**
100
+ * 根据 tty 查找 tmux pane
101
+ */
102
+ async findTmuxPaneByTty(tty) {
103
+ return new Promise((resolve) => {
104
+ const proc = spawn("tmux", ["list-panes", "-a", "-F", "#{pane_id} #{pane_tty}"]);
105
+ let output = "";
106
+
107
+ proc.stdout.on("data", (data) => {
108
+ output += data.toString();
109
+ });
110
+
111
+ proc.on("close", (code) => {
112
+ if (code !== 0) {
113
+ resolve(null);
114
+ return;
115
+ }
116
+ const lines = output.trim().split("\n");
117
+ for (const line of lines) {
118
+ const parts = line.trim().split(/\s+/);
119
+ if (parts.length >= 2 && parts[1] === tty) {
120
+ resolve(parts[0]);
121
+ return;
122
+ }
123
+ }
124
+ resolve(null);
125
+ });
126
+
127
+ proc.on("error", () => resolve(null));
128
+ });
129
+ }
130
+
131
+ /**
132
+ * 使用 tmux send-keys 注入命令
133
+ */
134
+ async injectTmux(paneId, command) {
135
+ return new Promise((resolve, reject) => {
136
+ // 检查是否需要发送中断信号
137
+ if (process.env.UFOO_INJECT_INTERRUPT === "1") {
138
+ spawn("tmux", ["send-keys", "-t", paneId, "C-c"]);
139
+ setTimeout(() => {
140
+ this.sendTmuxKeys(paneId, command, resolve, reject);
141
+ }, 100);
142
+ } else {
143
+ this.sendTmuxKeys(paneId, command, resolve, reject);
144
+ }
145
+ });
146
+ }
147
+
148
+ /**
149
+ * 发送 tmux 按键(先发文本,延迟后发 Enter)
150
+ */
151
+ sendTmuxKeys(paneId, command, resolve, reject) {
152
+ const textProc = spawn("tmux", ["send-keys", "-t", paneId, command]);
153
+ let stderr = "";
154
+
155
+ textProc.stderr.on("data", (data) => {
156
+ stderr += data.toString();
157
+ });
158
+
159
+ textProc.on("close", (code) => {
160
+ if (code !== 0) {
161
+ reject(new Error(stderr || "tmux send-keys failed"));
162
+ return;
163
+ }
164
+ // Delay before sending Enter — gives the target app time to process input
165
+ setTimeout(() => {
166
+ const enterProc = spawn("tmux", ["send-keys", "-t", paneId, "Enter"]);
167
+ enterProc.on("close", (enterCode) => {
168
+ if (enterCode === 0) resolve();
169
+ else reject(new Error("tmux send-keys Enter failed"));
170
+ });
171
+ enterProc.on("error", reject);
172
+ }, 150);
173
+ });
174
+
175
+ textProc.on("error", reject);
176
+ }
177
+
178
+ /**
179
+ * 获取订阅者的 inject socket 路径
180
+ */
181
+ getInjectSockPath(subscriber) {
182
+ const safeName = subscriberToSafeName(subscriber);
183
+ return path.join(this.busDir, "queues", safeName, "inject.sock");
184
+ }
185
+
186
+ /**
187
+ * 使用 PTY socket 直接注入命令(无需macOS权限)
188
+ */
189
+ async injectPty(subscriber, command) {
190
+ const sockPath = this.getInjectSockPath(subscriber);
191
+
192
+ if (!fs.existsSync(sockPath)) {
193
+ throw new Error(`Inject socket not found: ${sockPath}`);
194
+ }
195
+
196
+ return new Promise((resolve, reject) => {
197
+ const client = net.createConnection(sockPath, () => {
198
+ // 发送inject请求
199
+ client.write(JSON.stringify({ type: "inject", command }) + "\n");
200
+ });
201
+
202
+ let buffer = "";
203
+ const timeout = setTimeout(() => {
204
+ client.destroy();
205
+ reject(new Error("PTY inject timeout"));
206
+ }, 5000);
207
+
208
+ client.on("data", (data) => {
209
+ buffer += data.toString("utf8");
210
+ const lines = buffer.split("\n");
211
+ buffer = lines.pop() || "";
212
+
213
+ for (const line of lines) {
214
+ if (!line.trim()) continue;
215
+ clearTimeout(timeout);
216
+ try {
217
+ const res = JSON.parse(line);
218
+ client.end();
219
+ if (res.ok) {
220
+ resolve();
221
+ } else {
222
+ reject(new Error(res.error || "PTY inject failed"));
223
+ }
224
+ } catch (err) {
225
+ client.end();
226
+ reject(err);
227
+ }
228
+ return;
229
+ }
230
+ });
231
+
232
+ client.on("error", (err) => {
233
+ clearTimeout(timeout);
234
+ reject(err);
235
+ });
236
+
237
+ client.on("close", () => {
238
+ clearTimeout(timeout);
239
+ });
240
+ });
241
+ }
242
+
243
+ /**
244
+ * 注入命令到订阅者的终端
245
+ *
246
+ * 优先级:
247
+ * 1. PTY socket(直接写入,无需macOS权限)
248
+ * 2. tmux send-keys(无需权限)
249
+ */
250
+ async inject(subscriber, commandOverride = "") {
251
+ if (String(subscriber || "").startsWith("ufoo-code:")) {
252
+ throw new Error(`Inject disabled for ${subscriber}. ufoo-code consumes bus internally.`);
253
+ }
254
+
255
+ // 确定注入命令(codex 用 "ubus",claude-code 用 "/ubus")
256
+ const command = commandOverride
257
+ ? String(commandOverride)
258
+ : (subscriber.startsWith("codex:") ? "ubus" : "/ubus");
259
+
260
+ const meta = this.getAgentMeta(subscriber) || {};
261
+ const launchMode = meta.launch_mode || "";
262
+ const adapterRouter = createTerminalAdapterRouter();
263
+ const adapter = adapterRouter.getAdapter({ launchMode, agentId: subscriber });
264
+ const supportsSocket = adapter.capabilities.supportsSocketProtocol;
265
+ const supportsNotifier = adapter.capabilities.supportsNotifierInjector;
266
+
267
+ // 1. 优先尝试 PTY socket(无需任何macOS权限)
268
+ const injectSockPath = this.getInjectSockPath(subscriber);
269
+ if (fs.existsSync(injectSockPath)) {
270
+ try {
271
+ if (!supportsSocket) {
272
+ logInject(`[inject] PTY socket present but unsupported for launch_mode=${launchMode}`);
273
+ } else {
274
+ logInject(`[inject] Using PTY socket: ${injectSockPath}`);
275
+ }
276
+ await this.injectPty(subscriber, command);
277
+ logInject("[inject] PTY inject success");
278
+ return;
279
+ } catch (err) {
280
+ logInject(`[inject] PTY socket failed: ${err.message}, trying tmux`);
281
+ }
282
+ }
283
+
284
+ // 读取 tty(tmux 需要)
285
+ const tty = this.readTty(subscriber);
286
+
287
+ if (supportsNotifier) {
288
+ // 2. 尝试 tmux(无需权限)
289
+ const tmuxPane = meta.tmux_pane || this.getTmuxPane(subscriber);
290
+ if (tmuxPane) {
291
+ const paneExists = await this.checkTmuxPane(tmuxPane);
292
+ if (paneExists) {
293
+ logInject(`[inject] Using tmux send-keys for pane: ${tmuxPane}`);
294
+ await this.injectTmux(tmuxPane, command);
295
+ return;
296
+ }
297
+ }
298
+
299
+ // 尝试通过 tty 查找 tmux pane
300
+ if (tty && isValidTty(tty)) {
301
+ const fallbackPane = await this.findTmuxPaneByTty(tty);
302
+ if (fallbackPane) {
303
+ logInject(`[inject] Using tmux send-keys for pane: ${fallbackPane}`);
304
+ await this.injectTmux(fallbackPane, command);
305
+ return;
306
+ }
307
+ }
308
+ }
309
+
310
+ // 没有可用的注入方式
311
+ throw new Error(`No inject method available for ${subscriber}. PTY socket or tmux required.`);
312
+ }
313
+ }
314
+
315
+ module.exports = Injector;