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
@@ -3,7 +3,7 @@ function extractTextFromObject(obj) {
3
3
  if (obj.structured_output && typeof obj.structured_output === "object") {
4
4
  return JSON.stringify(obj.structured_output);
5
5
  }
6
- const candidates = ["output", "text", "message", "content", "output_text", "result"];
6
+ const candidates = ["reply", "output", "text", "message", "content", "output_text", "result"];
7
7
  for (const key of candidates) {
8
8
  const val = obj[key];
9
9
  if (typeof val === "string") return val;
@@ -0,0 +1,340 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const EventBus = require("../bus");
4
+ const Injector = require("../bus/inject");
5
+ const { getUfooPaths } = require("../ufoo/paths");
6
+ const { shakeTerminalByTty } = require("../bus/shake");
7
+ const { isITerm2 } = require("../terminal/detect");
8
+ const iterm2 = require("../terminal/iterm2");
9
+
10
+ /**
11
+ * Agent 消息通知监听器
12
+ * 监控 pending.jsonl 队列文件,当有新消息时发出通知并自动触发
13
+ */
14
+ class AgentNotifier {
15
+ constructor(projectRoot, subscriber) {
16
+ this.projectRoot = projectRoot;
17
+ this.subscriber = subscriber;
18
+ this.interval = 2000; // 2秒轮询一次
19
+ this.lastCount = 0;
20
+ this.timer = null;
21
+ this.stopped = false;
22
+ this.autoTrigger = process.env.UFOO_AUTO_TRIGGER !== "0"; // 默认启用自动触发
23
+ this.lastNickname = "";
24
+ this.lastUbusWakeCount = -1;
25
+
26
+ // 计算队列文件路径
27
+ const safeSub = subscriber.replace(/:/g, "_");
28
+ const paths = getUfooPaths(projectRoot);
29
+ this.queueFile = path.join(
30
+ paths.busQueuesDir,
31
+ safeSub,
32
+ "pending.jsonl"
33
+ );
34
+ this.agentsFile = paths.agentsFile;
35
+
36
+ // 初始化 injector
37
+ const busDir = paths.busDir;
38
+ this.injector = new Injector(busDir, paths.agentsFile);
39
+ this.eventBus = new EventBus(projectRoot);
40
+ }
41
+
42
+ isUfooCodeSubscriber() {
43
+ return String(this.subscriber || "").startsWith("ufoo-code:");
44
+ }
45
+
46
+ /**
47
+ * 读取当前订阅者昵称
48
+ */
49
+ getNickname() {
50
+ try {
51
+ if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return "";
52
+ const data = JSON.parse(fs.readFileSync(this.agentsFile, "utf8"));
53
+ const meta = data.agents && data.agents[this.subscriber];
54
+ return (meta && meta.nickname) ? String(meta.nickname) : "";
55
+ } catch {
56
+ return "";
57
+ }
58
+ }
59
+
60
+ /**
61
+ * 设置终端标题为昵称
62
+ * iTerm2: 同时设置 badge 和 cwd
63
+ */
64
+ setTitle(nickname) {
65
+ if (!nickname) return;
66
+ if (!process.stdout || !process.stdout.isTTY) return;
67
+ process.stdout.write(`\x1b]0;${nickname}\x07`);
68
+ if (isITerm2()) {
69
+ iterm2.setBadge(nickname);
70
+ iterm2.setCwd(this.projectRoot);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 检查昵称变化并更新标题
76
+ */
77
+ refreshTitle() {
78
+ const nickname = this.getNickname();
79
+ if (!nickname || nickname === this.lastNickname) return;
80
+ this.lastNickname = nickname;
81
+ this.setTitle(nickname);
82
+ }
83
+
84
+ /**
85
+ * 更新心跳时间戳(last_seen)
86
+ */
87
+ updateHeartbeat() {
88
+ try {
89
+ if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return;
90
+ const data = JSON.parse(fs.readFileSync(this.agentsFile, "utf8"));
91
+ if (data.agents && data.agents[this.subscriber]) {
92
+ data.agents[this.subscriber].last_seen = new Date().toISOString();
93
+ fs.writeFileSync(this.agentsFile, JSON.stringify(data, null, 2));
94
+ }
95
+ } catch {
96
+ // 心跳更新失败时静默忽略
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 获取当前队列中的消息数量
102
+ */
103
+ getMessageCount() {
104
+ try {
105
+ if (!fs.existsSync(this.queueFile)) return 0;
106
+ const content = fs.readFileSync(this.queueFile, "utf8");
107
+ if (!content.trim()) return 0;
108
+ return content.split("\n").filter((line) => line.trim()).length;
109
+ } catch {
110
+ return 0;
111
+ }
112
+ }
113
+
114
+ drainPending() {
115
+ if (!fs.existsSync(this.queueFile)) return [];
116
+ const processingFile = `${this.queueFile}.processing.${process.pid}.${Date.now()}`;
117
+ let content = "";
118
+ let readOk = false;
119
+ try {
120
+ fs.renameSync(this.queueFile, processingFile);
121
+ content = fs.readFileSync(processingFile, "utf8");
122
+ readOk = true;
123
+ } catch {
124
+ try {
125
+ if (fs.existsSync(processingFile)) {
126
+ fs.renameSync(processingFile, this.queueFile);
127
+ }
128
+ } catch {
129
+ // ignore rollback errors
130
+ }
131
+ return [];
132
+ } finally {
133
+ if (readOk) {
134
+ try {
135
+ if (fs.existsSync(processingFile)) {
136
+ fs.rmSync(processingFile, { force: true });
137
+ }
138
+ } catch {
139
+ // ignore cleanup errors
140
+ }
141
+ }
142
+ }
143
+ if (!content.trim()) return [];
144
+ return content.split(/\r?\n/).filter(Boolean).map((line) => {
145
+ try {
146
+ return JSON.parse(line);
147
+ } catch {
148
+ return null;
149
+ }
150
+ }).filter(Boolean);
151
+ }
152
+
153
+ normalizePublisher(publisher) {
154
+ if (!publisher) return "";
155
+ if (typeof publisher === "string") return publisher;
156
+ if (typeof publisher === "object") {
157
+ return publisher.subscriber || publisher.nickname || "";
158
+ }
159
+ return String(publisher);
160
+ }
161
+
162
+ async emitDelivery(evt, status, errorMessage = "") {
163
+ const publisher = this.normalizePublisher(evt.publisher);
164
+ if (!publisher) return;
165
+ const data = {
166
+ target: this.subscriber,
167
+ seq: evt.seq,
168
+ status,
169
+ };
170
+ if (errorMessage) data.error = errorMessage;
171
+ // Provide a human-readable message for chat UI
172
+ if (status === "ok") {
173
+ data.message = `delivered to ${this.lastNickname || this.subscriber}`;
174
+ } else {
175
+ data.message = `delivery failed to ${this.lastNickname || this.subscriber}: ${errorMessage || "unknown error"}`;
176
+ }
177
+ try {
178
+ await this.eventBus.send(publisher, "", this.subscriber, { event: "delivery", data });
179
+ } catch {
180
+ // ignore delivery emit failures
181
+ }
182
+ }
183
+
184
+ async deliverPending() {
185
+ if (this.isUfooCodeSubscriber()) {
186
+ // ufoo-code consumes bus queue internally; notifier must not inject text/commands.
187
+ return 0;
188
+ }
189
+
190
+ const events = this.drainPending();
191
+ if (events.length === 0) return 0;
192
+ const failed = [];
193
+ let delivered = 0;
194
+ for (const evt of events) {
195
+ if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
196
+ continue;
197
+ }
198
+ const message = String(evt.data.message);
199
+ try {
200
+ // Inject the actual message text into the terminal/tmux agent
201
+ // (Bus is the source of truth; inject is the delivery adapter.)
202
+ // eslint-disable-next-line no-await-in-loop
203
+ await this.injector.inject(this.subscriber, message);
204
+ delivered += 1;
205
+ // eslint-disable-next-line no-await-in-loop
206
+ await this.emitDelivery(evt, "ok");
207
+ } catch (err) {
208
+ failed.push(evt);
209
+ // eslint-disable-next-line no-await-in-loop
210
+ await this.emitDelivery(evt, "error", err.message || "inject failed");
211
+ }
212
+ }
213
+ if (failed.length > 0) {
214
+ try {
215
+ const content = failed.map((e) => JSON.stringify(e)).join("\n") + "\n";
216
+ fs.appendFileSync(this.queueFile, content, "utf8");
217
+ } catch {
218
+ // ignore requeue failures
219
+ }
220
+ }
221
+ return delivered;
222
+ }
223
+
224
+ /**
225
+ * 发送终端通知
226
+ * iTerm2: 使用 OSC 9 原生通知
227
+ */
228
+ notify(newCount) {
229
+ if (isITerm2()) {
230
+ const nick = this.lastNickname || this.subscriber;
231
+ iterm2.notify(`${nick}: ${newCount} new message(s)`);
232
+ }
233
+ const tty = this.injector.readTty(this.subscriber);
234
+ if (tty) {
235
+ shakeTerminalByTty(tty);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 自动触发终端输入
241
+ */
242
+ async autoTriggerInput() {
243
+ if (!this.autoTrigger) return;
244
+
245
+ try {
246
+ await this.deliverPending();
247
+ } catch (err) {
248
+ // 自动触发失败时静默忽略,用户仍可手动输入
249
+ // console.error("[notifier] Auto-trigger failed:", err.message);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * 轮询检查队列
255
+ */
256
+ async poll() {
257
+ if (this.stopped) return;
258
+
259
+ const currentCount = this.getMessageCount();
260
+
261
+ // 有新消息
262
+ if (currentCount > this.lastCount) {
263
+ const newCount = currentCount - this.lastCount;
264
+ this.notify(newCount);
265
+
266
+ // 自动触发终端输入(非阻塞)
267
+ this.autoTriggerInput().catch(() => {
268
+ // 忽略触发失败
269
+ });
270
+ }
271
+
272
+ // Ensure pending delivery happens even if count doesn't change
273
+ if (this.autoTrigger && currentCount > 0) {
274
+ if (this.isUfooCodeSubscriber()) {
275
+ if (this.lastUbusWakeCount !== currentCount) {
276
+ try {
277
+ await this.autoTriggerInput();
278
+ this.lastUbusWakeCount = currentCount;
279
+ } catch {
280
+ // ignore delivery errors
281
+ }
282
+ }
283
+ } else {
284
+ try {
285
+ await this.deliverPending();
286
+ } catch {
287
+ // ignore delivery errors
288
+ }
289
+ }
290
+ }
291
+ if (currentCount <= 0) {
292
+ this.lastUbusWakeCount = -1;
293
+ }
294
+
295
+ this.lastCount = this.getMessageCount();
296
+ this.refreshTitle();
297
+ this.updateHeartbeat();
298
+ }
299
+
300
+ /**
301
+ * 启动监听
302
+ */
303
+ start() {
304
+ // 获取初始计数
305
+ this.lastCount = this.getMessageCount();
306
+ this.lastNickname = this.getNickname();
307
+ if (this.lastNickname) {
308
+ this.setTitle(this.lastNickname);
309
+ }
310
+
311
+ // 启动轮询
312
+ this.timer = setInterval(() => {
313
+ this.poll().catch(() => {});
314
+ }, this.interval);
315
+
316
+ // 注册清理
317
+ process.on("exit", () => this.stop());
318
+ process.on("SIGINT", () => {
319
+ this.stop();
320
+ process.exit(0);
321
+ });
322
+ process.on("SIGTERM", () => {
323
+ this.stop();
324
+ process.exit(0);
325
+ });
326
+ }
327
+
328
+ /**
329
+ * 停止监听
330
+ */
331
+ stop() {
332
+ this.stopped = true;
333
+ if (this.timer) {
334
+ clearInterval(this.timer);
335
+ this.timer = null;
336
+ }
337
+ }
338
+ }
339
+
340
+ module.exports = AgentNotifier;