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,103 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const crypto = require("crypto");
5
+
6
+ function defaultTokensPath() {
7
+ return path.join(os.homedir(), ".ufoo", "online", "tokens.json");
8
+ }
9
+
10
+ function normalizeTokensData(raw) {
11
+ if (!raw || typeof raw !== "object") {
12
+ return { agents: {} };
13
+ }
14
+ if (raw.agents && typeof raw.agents === "object") {
15
+ return { agents: raw.agents };
16
+ }
17
+ // Legacy: flat object mapping id -> token
18
+ return { agents: raw };
19
+ }
20
+
21
+ function generateToken(bytes = 24) {
22
+ return crypto.randomBytes(bytes).toString("base64url");
23
+ }
24
+
25
+ function hashToken(token) {
26
+ if (!token) return "";
27
+ return crypto.createHash("sha256").update(String(token)).digest("hex");
28
+ }
29
+
30
+ function loadTokens(filePath = defaultTokensPath()) {
31
+ try {
32
+ const raw = fs.readFileSync(filePath, "utf8");
33
+ const parsed = JSON.parse(raw);
34
+ return normalizeTokensData(parsed);
35
+ } catch {
36
+ return { agents: {} };
37
+ }
38
+ }
39
+
40
+ function saveTokens(filePath = defaultTokensPath(), data = { agents: {} }) {
41
+ const dir = path.dirname(filePath);
42
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
43
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), { mode: 0o600 });
44
+ }
45
+
46
+ function setToken(filePath, agentId, token, server = "", extra = {}) {
47
+ if (!agentId || !token) throw new Error("agentId and token are required");
48
+ const data = loadTokens(filePath);
49
+ data.agents[agentId] = {
50
+ token,
51
+ token_hash: hashToken(token),
52
+ server,
53
+ nickname: extra.nickname || data.agents[agentId]?.nickname || "",
54
+ updated_at: new Date().toISOString(),
55
+ };
56
+ saveTokens(filePath, data);
57
+ return data.agents[agentId];
58
+ }
59
+
60
+ function removeToken(filePath, agentId) {
61
+ const data = loadTokens(filePath);
62
+ if (data.agents[agentId]) {
63
+ delete data.agents[agentId];
64
+ saveTokens(filePath, data);
65
+ }
66
+ return data;
67
+ }
68
+
69
+ function getToken(filePath, agentId) {
70
+ const data = loadTokens(filePath);
71
+ return data.agents[agentId] || null;
72
+ }
73
+
74
+ function getTokenByNickname(filePath, nickname) {
75
+ const data = loadTokens(filePath);
76
+ let best = null;
77
+ for (const entry of Object.values(data.agents)) {
78
+ if (entry.nickname === nickname) {
79
+ if (!best || (entry.updated_at || "") > (best.updated_at || "")) {
80
+ best = entry;
81
+ }
82
+ }
83
+ }
84
+ return best;
85
+ }
86
+
87
+ function listTokens(filePath) {
88
+ const data = loadTokens(filePath);
89
+ return Object.entries(data.agents).map(([id, entry]) => ({ id, ...entry }));
90
+ }
91
+
92
+ module.exports = {
93
+ defaultTokensPath,
94
+ generateToken,
95
+ hashToken,
96
+ loadTokens,
97
+ saveTokens,
98
+ setToken,
99
+ removeToken,
100
+ getToken,
101
+ getTokenByNickname,
102
+ listTokens,
103
+ };
@@ -0,0 +1,331 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { getUfooPaths } = require("../ufoo/paths");
4
+
5
+ const REPORT_PHASES = {
6
+ START: "start",
7
+ PROGRESS: "progress",
8
+ DONE: "done",
9
+ ERROR: "error",
10
+ };
11
+
12
+ function getReportPaths(projectRoot) {
13
+ const { agentDir } = getUfooPaths(projectRoot);
14
+ return {
15
+ reportsFile: path.join(agentDir, "reports.jsonl"),
16
+ stateFile: path.join(agentDir, "report-state.json"),
17
+ };
18
+ }
19
+
20
+ function ensureReportDir(projectRoot) {
21
+ const { agentDir } = getUfooPaths(projectRoot);
22
+ fs.mkdirSync(agentDir, { recursive: true });
23
+ }
24
+
25
+ function normalizePhase(value = "") {
26
+ const phase = String(value || "").trim().toLowerCase();
27
+ if (phase === REPORT_PHASES.START) return REPORT_PHASES.START;
28
+ if (phase === REPORT_PHASES.PROGRESS) return REPORT_PHASES.PROGRESS;
29
+ if (phase === REPORT_PHASES.ERROR) return REPORT_PHASES.ERROR;
30
+ return REPORT_PHASES.DONE;
31
+ }
32
+
33
+ function normalizeScope(value = "") {
34
+ const scope = String(value || "").trim().toLowerCase();
35
+ if (scope === "private") return "private";
36
+ return "public";
37
+ }
38
+
39
+ function normalizeReportInput(input = {}, options = {}) {
40
+ const ts = options.ts || new Date().toISOString();
41
+ let phase = normalizePhase(input.phase || options.phase);
42
+ const entryId = String(
43
+ input.entry_id
44
+ || input.entryId
45
+ || options.entry_id
46
+ || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
47
+ ).trim();
48
+
49
+ const taskId = String(
50
+ input.task_id
51
+ || input.taskId
52
+ || input.task
53
+ || options.task_id
54
+ || options.taskId
55
+ || `task-${Date.now()}`
56
+ ).trim();
57
+ const agentId = String(
58
+ input.agent_id
59
+ || input.agentId
60
+ || options.agent_id
61
+ || options.agentId
62
+ || "unknown-agent"
63
+ ).trim() || "unknown-agent";
64
+ const message = String(input.message || "").trim();
65
+ const summary = String(input.summary || "").trim();
66
+ const error = String(input.error || "").trim();
67
+ const source = String(input.source || options.source || "cli").trim() || "cli";
68
+ const scope = normalizeScope(input.scope || options.scope);
69
+ const controllerId = String(
70
+ input.controller_id
71
+ || input.controllerId
72
+ || options.controller_id
73
+ || options.controllerId
74
+ || "ufoo-agent"
75
+ ).trim() || "ufoo-agent";
76
+ const ok = input.ok !== false && phase !== REPORT_PHASES.ERROR && !error;
77
+ if (!ok && phase !== REPORT_PHASES.START && phase !== REPORT_PHASES.PROGRESS) {
78
+ phase = REPORT_PHASES.ERROR;
79
+ }
80
+
81
+ const meta = input.meta && typeof input.meta === "object" ? input.meta : {};
82
+
83
+ return {
84
+ entry_id: entryId,
85
+ ts,
86
+ phase,
87
+ task_id: taskId,
88
+ agent_id: agentId,
89
+ message,
90
+ summary,
91
+ error,
92
+ ok,
93
+ source,
94
+ scope,
95
+ controller_id: controllerId,
96
+ meta,
97
+ };
98
+ }
99
+
100
+ function appendReport(projectRoot, entry) {
101
+ ensureReportDir(projectRoot);
102
+ const { reportsFile } = getReportPaths(projectRoot);
103
+ fs.appendFileSync(reportsFile, `${JSON.stringify(entry)}\n`, "utf8");
104
+ }
105
+
106
+ function parseJsonLines(file) {
107
+ if (!fs.existsSync(file)) return [];
108
+ const raw = fs.readFileSync(file, "utf8");
109
+ if (!raw.trim()) return [];
110
+ const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
111
+ const rows = [];
112
+ for (const line of lines) {
113
+ try {
114
+ rows.push(JSON.parse(line));
115
+ } catch {
116
+ // ignore malformed lines
117
+ }
118
+ }
119
+ return rows;
120
+ }
121
+
122
+ function listReports(projectRoot, options = {}) {
123
+ const { reportsFile } = getReportPaths(projectRoot);
124
+ const num = Number.isFinite(options.num) && options.num > 0 ? options.num : 20;
125
+ const filterAgent = String(options.agent || options.agent_id || "").trim();
126
+ let rows = parseJsonLines(reportsFile);
127
+ if (filterAgent) {
128
+ rows = rows.filter((row) => String(row.agent_id || "").trim() === filterAgent);
129
+ }
130
+ rows.sort((a, b) => {
131
+ const left = new Date(a.ts || 0).getTime();
132
+ const right = new Date(b.ts || 0).getTime();
133
+ return right - left;
134
+ });
135
+ return rows.slice(0, num);
136
+ }
137
+
138
+ function loadReportState(projectRoot) {
139
+ const { stateFile } = getReportPaths(projectRoot);
140
+ try {
141
+ const parsed = JSON.parse(fs.readFileSync(stateFile, "utf8"));
142
+ if (!parsed || typeof parsed !== "object") return { updated_at: "", agents: {} };
143
+ if (!parsed.agents || typeof parsed.agents !== "object") parsed.agents = {};
144
+ if (typeof parsed.updated_at !== "string") parsed.updated_at = "";
145
+ return parsed;
146
+ } catch {
147
+ return { updated_at: "", agents: {} };
148
+ }
149
+ }
150
+
151
+ function saveReportState(projectRoot, state) {
152
+ ensureReportDir(projectRoot);
153
+ const { stateFile } = getReportPaths(projectRoot);
154
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
155
+ }
156
+
157
+ function updateReportState(projectRoot, entry) {
158
+ const state = loadReportState(projectRoot);
159
+ const current = state.agents[entry.agent_id] && typeof state.agents[entry.agent_id] === "object"
160
+ ? state.agents[entry.agent_id]
161
+ : {};
162
+ const pending = current.pending && typeof current.pending === "object"
163
+ ? { ...current.pending }
164
+ : {};
165
+
166
+ if (entry.phase === REPORT_PHASES.START || entry.phase === REPORT_PHASES.PROGRESS) {
167
+ pending[entry.task_id] = entry;
168
+ } else {
169
+ delete pending[entry.task_id];
170
+ }
171
+
172
+ state.agents[entry.agent_id] = {
173
+ ...current,
174
+ pending,
175
+ pending_count: Object.keys(pending).length,
176
+ last: entry,
177
+ updated_at: entry.ts,
178
+ };
179
+ state.updated_at = entry.ts;
180
+ saveReportState(projectRoot, state);
181
+ return state;
182
+ }
183
+
184
+ function isSummaryHiddenEntry(entry) {
185
+ if (!entry || typeof entry !== "object") return false;
186
+ const scope = normalizeScope(entry.scope);
187
+ if (scope !== "private") return false;
188
+ const controllerId = String(entry.controller_id || "").trim();
189
+ return controllerId === "ufoo-agent";
190
+ }
191
+
192
+ function controllerToSafeName(controllerId = "") {
193
+ return String(controllerId || "ufoo-agent")
194
+ .trim()
195
+ .replace(/[^a-zA-Z0-9._-]/g, "_");
196
+ }
197
+
198
+ function getControllerInboxFile(projectRoot, controllerId = "ufoo-agent") {
199
+ const { agentDir } = getUfooPaths(projectRoot);
200
+ const inboxDir = path.join(agentDir, "private-inbox");
201
+ const safe = controllerToSafeName(controllerId);
202
+ return path.join(inboxDir, `${safe}.jsonl`);
203
+ }
204
+
205
+ function appendControllerInboxEntry(projectRoot, controllerId, entry) {
206
+ const file = getControllerInboxFile(projectRoot, controllerId);
207
+ fs.mkdirSync(path.dirname(file), { recursive: true });
208
+ fs.appendFileSync(file, `${JSON.stringify(entry)}\n`, "utf8");
209
+ }
210
+
211
+ function listControllerInboxEntries(projectRoot, controllerId = "ufoo-agent", options = {}) {
212
+ const file = getControllerInboxFile(projectRoot, controllerId);
213
+ const rows = parseJsonLines(file);
214
+ const num = Number.isFinite(options.num) && options.num > 0 ? options.num : rows.length;
215
+ if (rows.length <= num) return rows;
216
+ return rows.slice(rows.length - num);
217
+ }
218
+
219
+ function clearControllerInbox(projectRoot, controllerId = "ufoo-agent") {
220
+ const file = getControllerInboxFile(projectRoot, controllerId);
221
+ try {
222
+ if (fs.existsSync(file)) fs.rmSync(file, { force: true });
223
+ } catch {
224
+ // ignore clear errors
225
+ }
226
+ }
227
+
228
+ function consumeControllerInboxEntries(projectRoot, controllerId = "ufoo-agent", consumed = []) {
229
+ const file = getControllerInboxFile(projectRoot, controllerId);
230
+ if (!fs.existsSync(file)) return { removed: 0, remaining: 0 };
231
+ const list = Array.isArray(consumed) ? consumed : [];
232
+ if (list.length === 0) {
233
+ const current = parseJsonLines(file);
234
+ return { removed: 0, remaining: current.length };
235
+ }
236
+
237
+ const idSet = new Set();
238
+ const legacySerialized = new Set();
239
+ for (const item of list) {
240
+ if (!item) continue;
241
+ if (typeof item === "string") {
242
+ idSet.add(item);
243
+ continue;
244
+ }
245
+ if (typeof item === "object") {
246
+ if (item.entry_id) {
247
+ idSet.add(String(item.entry_id));
248
+ } else {
249
+ legacySerialized.add(JSON.stringify(item));
250
+ }
251
+ }
252
+ }
253
+
254
+ const raw = fs.readFileSync(file, "utf8");
255
+ const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
256
+ if (lines.length === 0) {
257
+ return { removed: 0, remaining: 0 };
258
+ }
259
+
260
+ const keptLines = [];
261
+ let removed = 0;
262
+ for (const line of lines) {
263
+ let parsed = null;
264
+ try {
265
+ parsed = JSON.parse(line);
266
+ } catch {
267
+ parsed = null;
268
+ }
269
+ if (parsed && typeof parsed === "object") {
270
+ const id = parsed.entry_id ? String(parsed.entry_id) : "";
271
+ if (id && idSet.has(id)) {
272
+ removed += 1;
273
+ continue;
274
+ }
275
+ if (!id && legacySerialized.has(JSON.stringify(parsed))) {
276
+ removed += 1;
277
+ continue;
278
+ }
279
+ }
280
+ keptLines.push(line);
281
+ }
282
+
283
+ if (keptLines.length === 0) {
284
+ fs.rmSync(file, { force: true });
285
+ return { removed, remaining: 0 };
286
+ }
287
+
288
+ fs.writeFileSync(file, `${keptLines.join("\n")}\n`, "utf8");
289
+ return { removed, remaining: keptLines.length };
290
+ }
291
+
292
+ function readReportSummary(projectRoot) {
293
+ const state = loadReportState(projectRoot);
294
+ const agents = Object.entries(state.agents || {}).map(([agentId, value]) => {
295
+ const pending = value && value.pending && typeof value.pending === "object"
296
+ ? Object.values(value.pending).filter((entry) => !isSummaryHiddenEntry(entry))
297
+ : [];
298
+ return {
299
+ agent_id: agentId,
300
+ pending_count: pending.length,
301
+ pending,
302
+ last: value && value.last ? value.last : null,
303
+ updated_at: value && value.updated_at ? value.updated_at : "",
304
+ };
305
+ });
306
+ return {
307
+ updated_at: state.updated_at || "",
308
+ pending_total: agents.reduce((sum, item) => sum + item.pending_count, 0),
309
+ agents,
310
+ };
311
+ }
312
+
313
+ module.exports = {
314
+ REPORT_PHASES,
315
+ getReportPaths,
316
+ normalizePhase,
317
+ normalizeScope,
318
+ normalizeReportInput,
319
+ appendReport,
320
+ listReports,
321
+ loadReportState,
322
+ saveReportState,
323
+ updateReportState,
324
+ readReportSummary,
325
+ controllerToSafeName,
326
+ getControllerInboxFile,
327
+ appendControllerInboxEntry,
328
+ listControllerInboxEntries,
329
+ clearControllerInbox,
330
+ consumeControllerInboxEntries,
331
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ const IPC_REQUEST_TYPES = {
4
+ STATUS: "status",
5
+ PROMPT: "prompt",
6
+ BUS_SEND: "bus_send",
7
+ CLOSE_AGENT: "close_agent",
8
+ LAUNCH_AGENT: "launch_agent",
9
+ RESUME_AGENTS: "resume_agents",
10
+ LIST_RECOVERABLE_AGENTS: "list_recoverable_agents",
11
+ REGISTER_AGENT: "register_agent",
12
+ AGENT_READY: "agent_ready",
13
+ AGENT_REPORT: "agent_report",
14
+ };
15
+
16
+ const IPC_RESPONSE_TYPES = {
17
+ STATUS: "status",
18
+ RESPONSE: "response",
19
+ BUS: "bus",
20
+ ERROR: "error",
21
+ BUS_SEND_OK: "bus_send_ok",
22
+ REGISTER_OK: "register_ok",
23
+ };
24
+
25
+ const BUS_STATUS_PHASES = {
26
+ START: "start",
27
+ DONE: "done",
28
+ ERROR: "error",
29
+ };
30
+
31
+ module.exports = {
32
+ IPC_REQUEST_TYPES,
33
+ IPC_RESPONSE_TYPES,
34
+ BUS_STATUS_PHASES,
35
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ const PTY_SOCKET_MESSAGE_TYPES = {
4
+ OUTPUT: "output",
5
+ REPLAY: "replay",
6
+ SNAPSHOT: "snapshot",
7
+ SUBSCRIBED: "subscribed",
8
+ SUBSCRIBE: "subscribe",
9
+ RAW: "raw",
10
+ RESIZE: "resize",
11
+ };
12
+
13
+ const PTY_SOCKET_SUBSCRIBE_MODES = {
14
+ FULL: "full",
15
+ SCREEN: "screen",
16
+ };
17
+
18
+ module.exports = {
19
+ PTY_SOCKET_MESSAGE_TYPES,
20
+ PTY_SOCKET_SUBSCRIBE_MODES,
21
+ };
@@ -0,0 +1,159 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * 技能管理
6
+ */
7
+ class SkillsManager {
8
+ constructor(repoRoot) {
9
+ this.repoRoot = repoRoot;
10
+ this.skillRoots = this.findSkillRoots();
11
+ }
12
+
13
+ /**
14
+ * 查找所有技能根目录
15
+ */
16
+ findSkillRoots() {
17
+ const roots = [];
18
+
19
+ // 检查 SKILLS 目录
20
+ const mainSkills = path.join(this.repoRoot, "SKILLS");
21
+ if (fs.existsSync(mainSkills)) {
22
+ roots.push(mainSkills);
23
+ }
24
+
25
+ // 检查 modules 中的 SKILLS
26
+ const modulesDir = path.join(this.repoRoot, "modules");
27
+ if (fs.existsSync(modulesDir)) {
28
+ const modules = fs.readdirSync(modulesDir);
29
+ for (const module of modules) {
30
+ const moduleSkills = path.join(modulesDir, module, "SKILLS");
31
+ if (fs.existsSync(moduleSkills)) {
32
+ roots.push(moduleSkills);
33
+ }
34
+ }
35
+ }
36
+
37
+ return roots;
38
+ }
39
+
40
+ /**
41
+ * 列出所有技能
42
+ */
43
+ list() {
44
+ const skills = new Set();
45
+
46
+ for (const root of this.skillRoots) {
47
+ if (!fs.existsSync(root)) {
48
+ continue;
49
+ }
50
+
51
+ const entries = fs.readdirSync(root, { withFileTypes: true });
52
+ for (const entry of entries) {
53
+ if (entry.isDirectory()) {
54
+ skills.add(entry.name);
55
+ }
56
+ }
57
+ }
58
+
59
+ return Array.from(skills).sort();
60
+ }
61
+
62
+ /**
63
+ * 查找技能路径
64
+ */
65
+ findSkill(name) {
66
+ for (const root of this.skillRoots) {
67
+ const skillPath = path.join(root, name);
68
+ if (fs.existsSync(skillPath)) {
69
+ return skillPath;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * 安装技能
77
+ */
78
+ async install(name, options = {}) {
79
+ // 确定目标目录
80
+ let target = options.target;
81
+
82
+ if (!target) {
83
+ if (options.codex) {
84
+ const codexHome = process.env.CODEX_HOME || path.join(process.env.HOME, ".codex");
85
+ target = path.join(codexHome, "skills");
86
+ } else if (options.agents) {
87
+ target = path.join(process.env.HOME, ".agents", "skills");
88
+ } else {
89
+ target = path.join(process.env.HOME, ".claude", "skills");
90
+ }
91
+ }
92
+
93
+ console.log(`Installing to: ${target}`);
94
+
95
+ // 确保目标目录存在
96
+ if (!fs.existsSync(target)) {
97
+ fs.mkdirSync(target, { recursive: true });
98
+ }
99
+
100
+ if (name === "all") {
101
+ // 安装所有技能
102
+ const skills = this.list();
103
+ for (const skill of skills) {
104
+ await this.installOne(skill, target);
105
+ }
106
+ console.log(`\nInstalled ${skills.length} skills to ${target}`);
107
+ } else {
108
+ // 安装单个技能
109
+ await this.installOne(name, target);
110
+ console.log(`\nInstalled "${name}" to ${target}`);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * 安装单个技能
116
+ */
117
+ async installOne(name, target) {
118
+ const sourcePath = this.findSkill(name);
119
+ if (!sourcePath) {
120
+ throw new Error(`Skill not found: ${name}`);
121
+ }
122
+
123
+ const targetPath = path.join(target, name);
124
+
125
+ // 如果目标已存在,先删除
126
+ if (fs.existsSync(targetPath)) {
127
+ fs.rmSync(targetPath, { recursive: true, force: true });
128
+ }
129
+
130
+ // 复制技能目录
131
+ console.log(` - ${name}`);
132
+ this.copyRecursive(sourcePath, targetPath);
133
+ }
134
+
135
+ /**
136
+ * 递归复制目录
137
+ */
138
+ copyRecursive(src, dest) {
139
+ // 创建目标目录
140
+ if (!fs.existsSync(dest)) {
141
+ fs.mkdirSync(dest, { recursive: true });
142
+ }
143
+
144
+ const entries = fs.readdirSync(src, { withFileTypes: true });
145
+
146
+ for (const entry of entries) {
147
+ const srcPath = path.join(src, entry.name);
148
+ const destPath = path.join(dest, entry.name);
149
+
150
+ if (entry.isDirectory()) {
151
+ this.copyRecursive(srcPath, destPath);
152
+ } else {
153
+ fs.copyFileSync(srcPath, destPath);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ module.exports = SkillsManager;