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
@@ -1,7 +1,8 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
+ const { getUfooPaths } = require("../ufoo/paths");
3
4
  const { spawnSync } = require("child_process");
4
- const { randomBytes } = require("crypto");
5
+ const EventBus = require("../bus");
5
6
  const { runCliAgent } = require("./cliRunner");
6
7
  const { normalizeCliOutput } = require("./normalizeOutput");
7
8
 
@@ -9,77 +10,114 @@ function sleep(ms) {
9
10
  return new Promise((resolve) => setTimeout(resolve, ms));
10
11
  }
11
12
 
12
- function generateSessionId() {
13
- return randomBytes(4).toString("hex");
14
- }
15
-
16
13
  function buildEnv(agentType, sessionId, publisher, nickname) {
17
14
  const env = { ...process.env };
18
- if (agentType === "codex") {
19
- env.CODEX_SESSION_ID = sessionId;
20
- env.CLAUDE_SESSION_ID = "";
21
- } else {
22
- env.CLAUDE_SESSION_ID = sessionId;
23
- env.CODEX_SESSION_ID = "";
24
- }
25
15
  env.AI_BUS_PUBLISHER = publisher || env.AI_BUS_PUBLISHER || "";
26
16
  env.UFOO_NICKNAME = nickname || env.UFOO_NICKNAME || "";
27
17
  env.UFOO_PARENT_PID = String(process.pid);
28
18
  return env;
29
19
  }
30
20
 
31
- function joinBus(projectRoot, agentType, sessionId, nickname) {
32
- const env = buildEnv(agentType, sessionId, "", nickname);
33
- const args = ["bus", "join", sessionId, agentType === "codex" ? "codex" : "claude-code"];
34
- if (nickname) args.push(nickname);
35
- const res = spawnSync("ufoo", args, {
36
- cwd: projectRoot,
37
- env,
38
- stdio: ["ignore", "pipe", "pipe"],
39
- });
40
- if (res.status !== 0) {
41
- const err = (res.stderr || res.stdout || "").toString("utf8").trim();
42
- throw new Error(err || "bus join failed");
21
+ function parseSubscriberId() {
22
+ // Daemon 已经注册,直接使用
23
+ if (process.env.UFOO_SUBSCRIBER_ID) {
24
+ const parts = process.env.UFOO_SUBSCRIBER_ID.split(":");
25
+ if (parts.length === 2) {
26
+ return {
27
+ subscriber: process.env.UFOO_SUBSCRIBER_ID,
28
+ agentType: parts[0],
29
+ sessionId: parts[1],
30
+ };
31
+ }
43
32
  }
44
- const out = (res.stdout || "").toString("utf8").trim().split(/\r?\n/);
45
- const subscriber = out[out.length - 1];
46
- return { subscriber, env };
33
+
34
+ throw new Error("Internal runner requires UFOO_SUBSCRIBER_ID set by daemon");
47
35
  }
48
36
 
49
37
  function safeSubscriber(subscriber) {
50
38
  return subscriber.replace(/:/g, "_");
51
39
  }
52
40
 
53
- function readQueue(queueFile) {
54
- if (!fs.existsSync(queueFile)) return [];
55
- try {
56
- const content = fs.readFileSync(queueFile, "utf8");
57
- if (!content.trim()) return [];
58
- return content.split(/\r?\n/).filter(Boolean);
59
- } catch {
60
- return [];
41
+ function createBusSender(projectRoot, subscriber) {
42
+ const eventBus = new EventBus(projectRoot);
43
+ let sendQueue = Promise.resolve();
44
+
45
+ function enqueue(target, message) {
46
+ if (!target || !message) return;
47
+ sendQueue = sendQueue
48
+ .then(() => eventBus.send(target, message, subscriber))
49
+ .catch(() => {
50
+ // ignore per-message bus send errors to keep runner loop alive
51
+ });
52
+ }
53
+
54
+ async function flush() {
55
+ try {
56
+ await sendQueue;
57
+ } catch {
58
+ // ignore flush errors
59
+ }
61
60
  }
61
+
62
+ return { enqueue, flush };
62
63
  }
63
64
 
64
- function truncateQueue(queueFile) {
65
+ function drainQueue(queueFile) {
66
+ if (!fs.existsSync(queueFile)) return [];
67
+ const processingFile = `${queueFile}.processing.${process.pid}.${Date.now()}`;
68
+ let content = "";
69
+ let readOk = false;
65
70
  try {
66
- fs.truncateSync(queueFile, 0);
71
+ fs.renameSync(queueFile, processingFile);
72
+ content = fs.readFileSync(processingFile, "utf8");
73
+ readOk = true;
67
74
  } catch {
68
- // ignore
75
+ try {
76
+ if (fs.existsSync(processingFile)) {
77
+ fs.renameSync(processingFile, queueFile);
78
+ }
79
+ } catch {
80
+ // ignore rollback errors
81
+ }
82
+ return [];
83
+ } finally {
84
+ if (readOk) {
85
+ try {
86
+ if (fs.existsSync(processingFile)) {
87
+ fs.rmSync(processingFile, { force: true });
88
+ }
89
+ } catch {
90
+ // ignore cleanup errors
91
+ }
92
+ }
69
93
  }
94
+ if (!content.trim()) return [];
95
+ return content.split(/\r?\n/).filter(Boolean);
70
96
  }
71
97
 
72
- async function handleEvent(projectRoot, agentType, provider, model, subscriber, sessionId, nickname, evt, cliSessionState) {
98
+ async function handleEvent(projectRoot, agentType, provider, model, subscriber, nickname, evt, cliSessionState, busSender) {
73
99
  if (!evt || !evt.data || !evt.data.message) return;
74
100
  const prompt = evt.data.message;
75
101
  const publisher = evt.publisher || "unknown";
102
+ const sandbox = "workspace-write";
103
+ const streamState = { emitted: false, lastChar: "" };
104
+
105
+ const emitStreamDelta = (delta) => {
106
+ const text = String(delta || "");
107
+ if (!text) return;
108
+ streamState.emitted = true;
109
+ streamState.lastChar = text.slice(-1);
110
+ busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: text }));
111
+ };
76
112
 
77
113
  let res = await runCliAgent({
78
114
  provider,
79
115
  model,
80
116
  prompt,
81
117
  sessionId: cliSessionState.cliSessionId,
118
+ sandbox,
82
119
  cwd: projectRoot,
120
+ onStreamDelta: emitStreamDelta,
83
121
  });
84
122
 
85
123
  // Handle session errors with immediate retry (only for claude)
@@ -95,7 +133,9 @@ async function handleEvent(projectRoot, agentType, provider, model, subscriber,
95
133
  model,
96
134
  prompt,
97
135
  sessionId: null, // Let runCliAgent generate new session
136
+ sandbox,
98
137
  cwd: projectRoot,
138
+ onStreamDelta: emitStreamDelta,
99
139
  });
100
140
  }
101
141
  }
@@ -113,32 +153,46 @@ async function handleEvent(projectRoot, agentType, provider, model, subscriber,
113
153
  reply = `[internal:${agentType}] error: ${res.error || "unknown error"}`;
114
154
  }
115
155
 
156
+ if (streamState.emitted) {
157
+ if (!res.ok) {
158
+ if (streamState.lastChar !== "\n") {
159
+ busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: "\n" }));
160
+ }
161
+ busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: reply }));
162
+ }
163
+ busSender.enqueue(
164
+ publisher,
165
+ JSON.stringify({ stream: true, done: true, reason: res.ok ? "complete" : "error" })
166
+ );
167
+ await busSender.flush();
168
+ return;
169
+ }
170
+
116
171
  if (!reply) return;
117
172
 
118
- spawnSync("ufoo", ["bus", "send", publisher, reply], {
119
- cwd: projectRoot,
120
- env: buildEnv(agentType, sessionId, subscriber, nickname),
121
- stdio: "ignore",
122
- });
173
+ busSender.enqueue(publisher, reply);
174
+ await busSender.flush();
123
175
  }
124
176
 
125
177
  async function runInternalRunner({ projectRoot, agentType = "codex" }) {
126
- const sessionId = generateSessionId();
178
+ // Internal runner 必须由 daemon 启动,UFOO_SUBSCRIBER_ID 应该已经设置
179
+ const { subscriber, agentType: parsedAgentType, sessionId } = parseSubscriberId();
127
180
  const nickname = process.env.UFOO_NICKNAME || "";
128
- const { subscriber } = joinBus(projectRoot, agentType, sessionId, nickname);
129
- if (!subscriber) {
130
- throw new Error("Failed to join bus for internal runner");
131
- }
132
181
 
133
- const queueDir = path.join(projectRoot, ".ufoo", "bus", "queues", safeSubscriber(subscriber));
182
+ const queueDir = path.join(getUfooPaths(projectRoot).busQueuesDir, safeSubscriber(subscriber));
134
183
  const queueFile = path.join(queueDir, "pending.jsonl");
135
- const provider = agentType === "codex" ? "codex-cli" : "claude-cli";
184
+ const normalizedAgentType = String(agentType || "").trim().toLowerCase();
185
+ if (normalizedAgentType === "ufoo" || normalizedAgentType === "ucode" || normalizedAgentType === "ufoo-code") {
186
+ throw new Error("ufoo core is not supported by headless internal runner; use internal-pty");
187
+ }
188
+ const provider = normalizedAgentType === "codex" ? "codex-cli" : "claude-cli";
136
189
  const model = process.env.UFOO_AGENT_MODEL || "";
190
+ const busSender = createBusSender(projectRoot, subscriber);
137
191
 
138
192
  // Session state management for CLI continuity
139
193
  // Use stable path based on nickname (if exists) or agent type, NOT subscriber ID
140
194
  const stableKey = nickname || `${agentType}-default`;
141
- const sessionDir = path.join(projectRoot, ".ufoo", "agent", "sessions");
195
+ const sessionDir = path.join(getUfooPaths(projectRoot).agentDir, "sessions");
142
196
  fs.mkdirSync(sessionDir, { recursive: true });
143
197
  const stateFile = path.join(sessionDir, `${stableKey}.json`);
144
198
 
@@ -155,6 +209,8 @@ async function runInternalRunner({ projectRoot, agentType = "codex" }) {
155
209
 
156
210
  let running = true;
157
211
  let processing = false;
212
+ let lastHeartbeat = 0;
213
+ const HEARTBEAT_INTERVAL = 30000; // 30秒心跳间隔
158
214
 
159
215
  const stop = () => {
160
216
  running = false;
@@ -165,11 +221,32 @@ async function runInternalRunner({ projectRoot, agentType = "codex" }) {
165
221
 
166
222
  const cliSessionState = { cliSessionId, needsSave: false };
167
223
 
224
+ // 心跳更新函数
225
+ const updateHeartbeat = () => {
226
+ try {
227
+ spawnSync("ufoo", ["bus", "check", subscriber], {
228
+ cwd: projectRoot,
229
+ env: { ...process.env, UFOO_SUBSCRIBER_ID: subscriber },
230
+ stdio: "ignore",
231
+ timeout: 5000,
232
+ });
233
+ } catch {
234
+ // ignore heartbeat errors
235
+ }
236
+ };
237
+
168
238
  while (running) {
239
+ // 定期心跳更新
240
+ const now = Date.now();
241
+ if (now - lastHeartbeat > HEARTBEAT_INTERVAL) {
242
+ updateHeartbeat();
243
+ lastHeartbeat = now;
244
+ }
245
+
169
246
  if (!processing) {
170
247
  processing = true;
171
248
  try {
172
- const lines = readQueue(queueFile);
249
+ const lines = drainQueue(queueFile);
173
250
  if (lines.length > 0) {
174
251
  const events = [];
175
252
  for (const line of lines) {
@@ -179,11 +256,20 @@ async function runInternalRunner({ projectRoot, agentType = "codex" }) {
179
256
  // ignore malformed line
180
257
  }
181
258
  }
182
- truncateQueue(queueFile);
183
259
 
184
260
  for (const evt of events) {
185
261
  // eslint-disable-next-line no-await-in-loop
186
- await handleEvent(projectRoot, agentType, provider, model, subscriber, sessionId, nickname, evt, cliSessionState);
262
+ await handleEvent(
263
+ projectRoot,
264
+ parsedAgentType,
265
+ provider,
266
+ model,
267
+ subscriber,
268
+ nickname,
269
+ evt,
270
+ cliSessionState,
271
+ busSender
272
+ );
187
273
  }
188
274
 
189
275
  // Persist CLI session state after processing (only if changed and for claude)
@@ -199,6 +285,10 @@ async function runInternalRunner({ projectRoot, agentType = "codex" }) {
199
285
  // ignore save errors
200
286
  }
201
287
  }
288
+
289
+ // 处理消息后更新心跳
290
+ updateHeartbeat();
291
+ lastHeartbeat = now;
202
292
  }
203
293
  } finally {
204
294
  processing = false;
@@ -209,4 +299,8 @@ async function runInternalRunner({ projectRoot, agentType = "codex" }) {
209
299
  }
210
300
  }
211
301
 
212
- module.exports = { runInternalRunner };
302
+ module.exports = {
303
+ runInternalRunner,
304
+ createBusSender,
305
+ handleEvent,
306
+ };