u-foo 1.0.6 → 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 (149) hide show
  1. package/README.md +44 -4
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +11 -2
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +154 -0
  64. package/src/chat/index.js +935 -2909
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +132 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1580 -0
  98. package/src/config.js +47 -1
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +661 -488
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. package/src/bus/API_DESIGN.md +0 -204
@@ -0,0 +1,700 @@
1
+ const path = require("path");
2
+ const EventBus = require("../bus");
3
+ const { IPC_REQUEST_TYPES } = require("../shared/eventContract");
4
+ const UfooInit = require("../init");
5
+ const { loadConfig: loadProjectConfig, saveConfig: saveProjectConfig } = require("../config");
6
+ const { resolveTransport } = require("../code/nativeRunner");
7
+ const { parseIntervalMs, formatIntervalMs } = require("./cronScheduler");
8
+
9
+ function defaultCreateDoctor(projectRoot) {
10
+ const UfooDoctor = require("../doctor");
11
+ return new UfooDoctor(projectRoot);
12
+ }
13
+
14
+ function defaultCreateContext(projectRoot) {
15
+ const UfooContext = require("../context");
16
+ return new UfooContext(projectRoot);
17
+ }
18
+
19
+ function defaultCreateSkills(projectRoot) {
20
+ const UfooSkills = require("../skills");
21
+ return new UfooSkills(projectRoot);
22
+ }
23
+
24
+ async function withCapturedConsole(capture, fn) {
25
+ const originalLog = console.log;
26
+ const originalError = console.error;
27
+
28
+ if (capture.log) {
29
+ console.log = (...args) => capture.log(...args);
30
+ }
31
+ if (capture.error) {
32
+ console.error = (...args) => capture.error(...args);
33
+ }
34
+
35
+ try {
36
+ return await fn();
37
+ } finally {
38
+ console.log = originalLog;
39
+ console.error = originalError;
40
+ }
41
+ }
42
+
43
+ function createCommandExecutor(options = {}) {
44
+ const {
45
+ projectRoot,
46
+ parseCommand = () => null,
47
+ escapeBlessed = (value) => String(value || ""),
48
+ logMessage = () => {},
49
+ renderScreen = () => {},
50
+ getActiveAgents = () => [],
51
+ getActiveAgentMetaMap = () => new Map(),
52
+ getAgentLabel = (id) => id,
53
+ isDaemonRunning = () => false,
54
+ startDaemon = () => {},
55
+ stopDaemon = () => {},
56
+ restartDaemon = async () => {},
57
+ send = () => {},
58
+ requestStatus = () => {},
59
+ createBus = (root) => new EventBus(root),
60
+ createInit = (repoRoot) => new UfooInit(repoRoot),
61
+ createDoctor = defaultCreateDoctor,
62
+ createContext = defaultCreateContext,
63
+ createSkills = defaultCreateSkills,
64
+ activateAgent = async () => {},
65
+ loadConfig = loadProjectConfig,
66
+ saveConfig = saveProjectConfig,
67
+ createCronTask = () => null,
68
+ listCronTasks = () => [],
69
+ stopCronTask = () => false,
70
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
71
+ schedule = (fn, ms) => setTimeout(fn, ms),
72
+ } = options;
73
+
74
+ if (!projectRoot) {
75
+ throw new Error("createCommandExecutor requires projectRoot");
76
+ }
77
+
78
+ async function handleDoctorCommand() {
79
+ logMessage("system", "{white-fg}⚙{/white-fg} Running health check...");
80
+
81
+ await withCapturedConsole(
82
+ {
83
+ log: (...args) => logMessage("system", args.join(" ")),
84
+ error: (...args) => logMessage("error", args.join(" ")),
85
+ },
86
+ async () => {
87
+ try {
88
+ const doctor = createDoctor(projectRoot);
89
+ const result = await Promise.resolve(doctor.run());
90
+
91
+ if (result) {
92
+ logMessage("system", "{white-fg}✓{/white-fg} System healthy");
93
+ } else {
94
+ logMessage("error", "{white-fg}✗{/white-fg} Health check failed");
95
+ }
96
+ renderScreen();
97
+ } catch (err) {
98
+ logMessage("error", `{white-fg}✗{/white-fg} Doctor check failed: ${escapeBlessed(err.message)}`);
99
+ renderScreen();
100
+ }
101
+ }
102
+ );
103
+ }
104
+
105
+ async function handleStatusCommand() {
106
+ const activeAgents = getActiveAgents();
107
+ const activeAgentMetaMap = getActiveAgentMetaMap();
108
+
109
+ if (activeAgents.length === 0) {
110
+ logMessage("system", "{cyan-fg}Status:{/cyan-fg} No active agents");
111
+ } else {
112
+ logMessage("system", `{cyan-fg}Status:{/cyan-fg} ${activeAgents.length} active agent(s)`);
113
+ for (const id of activeAgents) {
114
+ const label = getAgentLabel(id);
115
+ const meta = activeAgentMetaMap.get(id);
116
+ const mode = meta && meta.launch_mode ? meta.launch_mode : "unknown";
117
+ logMessage("system", ` • {cyan-fg}${label}{/cyan-fg} {white-fg}[${mode}]{/white-fg}`);
118
+ }
119
+ }
120
+
121
+ if (isDaemonRunning(projectRoot)) {
122
+ logMessage("system", "{white-fg}✓{/white-fg} Daemon is running");
123
+ } else {
124
+ logMessage("system", "{white-fg}✗{/white-fg} Daemon is not running");
125
+ }
126
+ }
127
+
128
+ async function handleDaemonCommand(args = []) {
129
+ const subcommand = args[0];
130
+
131
+ if (subcommand === "start") {
132
+ if (isDaemonRunning(projectRoot)) {
133
+ logMessage("system", "{white-fg}⚠{/white-fg} Daemon already running");
134
+ } else {
135
+ logMessage("system", "{white-fg}⚙{/white-fg} Starting daemon...");
136
+ startDaemon(projectRoot);
137
+ await sleep(1000);
138
+ if (isDaemonRunning(projectRoot)) {
139
+ logMessage("system", "{white-fg}✓{/white-fg} Daemon started");
140
+ } else {
141
+ logMessage("error", "{white-fg}✗{/white-fg} Failed to start daemon");
142
+ }
143
+ }
144
+ return;
145
+ }
146
+
147
+ if (subcommand === "stop") {
148
+ logMessage("system", "{white-fg}⚙{/white-fg} Stopping daemon...");
149
+ stopDaemon(projectRoot);
150
+ await sleep(1000);
151
+ if (!isDaemonRunning(projectRoot)) {
152
+ logMessage("system", "{white-fg}✓{/white-fg} Daemon stopped");
153
+ } else {
154
+ logMessage("error", "{white-fg}✗{/white-fg} Failed to stop daemon");
155
+ }
156
+ return;
157
+ }
158
+
159
+ if (subcommand === "restart") {
160
+ logMessage("system", "{white-fg}⚙{/white-fg} Restarting daemon...");
161
+ await restartDaemon();
162
+ return;
163
+ }
164
+
165
+ if (subcommand === "status") {
166
+ if (isDaemonRunning(projectRoot)) {
167
+ logMessage("system", "{white-fg}✓{/white-fg} Daemon is running");
168
+ } else {
169
+ logMessage("system", "{white-fg}✗{/white-fg} Daemon is not running");
170
+ }
171
+ return;
172
+ }
173
+
174
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown daemon command. Use: start, stop, restart, status");
175
+ }
176
+
177
+ async function handleInitCommand(args = []) {
178
+ logMessage("system", "{white-fg}⚙{/white-fg} Initializing ufoo modules...");
179
+
180
+ await withCapturedConsole(
181
+ {
182
+ log: (...logArgs) => {
183
+ const msg = logArgs.join(" ");
184
+ logMessage("system", msg);
185
+ },
186
+ error: (...errorArgs) => {
187
+ logMessage("error", errorArgs.join(" "));
188
+ },
189
+ },
190
+ async () => {
191
+ try {
192
+ const repoRoot = path.join(__dirname, "..", "..");
193
+ const init = createInit(repoRoot);
194
+ const modules = args.length > 0 ? args.join(",") : "context,bus";
195
+ await init.init({ modules, project: projectRoot });
196
+
197
+ logMessage("system", "{white-fg}✓{/white-fg} Initialization complete");
198
+ renderScreen();
199
+ } catch (err) {
200
+ logMessage("error", `{white-fg}✗{/white-fg} Init failed: ${escapeBlessed(err.message)}`);
201
+ if (err.stack) {
202
+ logMessage("error", escapeBlessed(err.stack));
203
+ }
204
+ renderScreen();
205
+ }
206
+ }
207
+ );
208
+ }
209
+
210
+ async function handleBusCommand(args = []) {
211
+ const subcommand = args[0];
212
+
213
+ try {
214
+ if (subcommand === "send") {
215
+ if (args.length < 3) {
216
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /bus send <target> <message>");
217
+ return;
218
+ }
219
+ const target = args[1];
220
+ const message = args.slice(2).join(" ");
221
+ send({ type: IPC_REQUEST_TYPES.BUS_SEND, target, message });
222
+ logMessage("system", `{white-fg}✓{/white-fg} Message sent to ${target}`);
223
+ return;
224
+ }
225
+
226
+ const bus = createBus(projectRoot);
227
+
228
+ if (subcommand === "rename") {
229
+ if (args.length < 3) {
230
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /bus rename <agent> <nickname>");
231
+ return;
232
+ }
233
+ const agentId = args[1];
234
+ const nickname = args[2];
235
+ await bus.rename(agentId, nickname);
236
+ logMessage("system", `{white-fg}✓{/white-fg} Renamed ${agentId} to ${nickname}`);
237
+ requestStatus();
238
+ return;
239
+ }
240
+
241
+ if (subcommand === "list") {
242
+ bus.ensureBus();
243
+ bus.loadBusData();
244
+ const subscribers = Object.entries((bus.busData && bus.busData.agents) || {});
245
+ if (subscribers.length === 0) {
246
+ logMessage("system", "{white-fg}No active agents{/white-fg}");
247
+ } else {
248
+ logMessage("system", "{cyan-fg}Active agents:{/cyan-fg}");
249
+ for (const [id, meta] of subscribers) {
250
+ const nickname = meta && meta.nickname ? ` (${meta.nickname})` : "";
251
+ const status = meta && meta.status ? meta.status : "unknown";
252
+ logMessage("system", ` • ${id}${nickname} {white-fg}[${status}]{/white-fg}`);
253
+ }
254
+ }
255
+ return;
256
+ }
257
+
258
+ if (subcommand === "status") {
259
+ bus.ensureBus();
260
+ bus.loadBusData();
261
+ const count = Object.keys((bus.busData && bus.busData.agents) || {}).length;
262
+ logMessage("system", `{cyan-fg}Bus status:{/cyan-fg} ${count} agent(s) registered`);
263
+ return;
264
+ }
265
+
266
+ if (subcommand === "activate") {
267
+ if (args.length < 2) {
268
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /bus activate <agent>");
269
+ return;
270
+ }
271
+ const target = args[1];
272
+ await activateAgent(target);
273
+ logMessage("system", `{white-fg}✓{/white-fg} Activated ${target}`);
274
+ return;
275
+ }
276
+
277
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown bus command. Use: send, rename, list, status, activate");
278
+ } catch (err) {
279
+ logMessage("error", `{white-fg}✗{/white-fg} Bus command failed: ${escapeBlessed(err.message)}`);
280
+ }
281
+ }
282
+
283
+ async function handleCtxCommand(args = []) {
284
+ logMessage("system", "{white-fg}⚙{/white-fg} Running context check...");
285
+
286
+ await withCapturedConsole(
287
+ {
288
+ log: (...logArgs) => logMessage("system", logArgs.join(" ")),
289
+ error: (...errorArgs) => logMessage("error", errorArgs.join(" ")),
290
+ },
291
+ async () => {
292
+ try {
293
+ const ctx = createContext(projectRoot);
294
+
295
+ if (args.length === 0 || args[0] === "doctor") {
296
+ await ctx.doctor();
297
+ } else if (args[0] === "decisions") {
298
+ await ctx.listDecisions();
299
+ } else {
300
+ await ctx.status();
301
+ }
302
+
303
+ renderScreen();
304
+ } catch (err) {
305
+ logMessage("error", `{white-fg}✗{/white-fg} Context check failed: ${escapeBlessed(err.message)}`);
306
+ renderScreen();
307
+ }
308
+ }
309
+ );
310
+ }
311
+
312
+ async function handleSkillsCommand(args = []) {
313
+ const subcommand = args[0];
314
+
315
+ await withCapturedConsole(
316
+ {
317
+ log: (...logArgs) => logMessage("system", logArgs.join(" ")),
318
+ },
319
+ async () => {
320
+ try {
321
+ const skills = createSkills(projectRoot);
322
+
323
+ if (subcommand === "list") {
324
+ const skillList = skills.list();
325
+ if (skillList.length === 0) {
326
+ logMessage("system", "{white-fg}No skills found{/white-fg}");
327
+ } else {
328
+ logMessage("system", `{cyan-fg}Available skills:{/cyan-fg} ${skillList.length}`);
329
+ for (const skill of skillList) {
330
+ logMessage("system", ` • ${skill}`);
331
+ }
332
+ }
333
+ } else if (subcommand === "install") {
334
+ const target = args[1] || "all";
335
+ logMessage("system", `{white-fg}⚙{/white-fg} Installing skills: ${target}...`);
336
+ await skills.install(target);
337
+ logMessage("system", "{white-fg}✓{/white-fg} Skills installed");
338
+ } else {
339
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown skills command. Use: list, install");
340
+ }
341
+
342
+ renderScreen();
343
+ } catch (err) {
344
+ logMessage("error", `{white-fg}✗{/white-fg} Skills command failed: ${escapeBlessed(err.message)}`);
345
+ renderScreen();
346
+ }
347
+ }
348
+ );
349
+ }
350
+
351
+ async function handleLaunchCommand(args = []) {
352
+ if (args.length === 0) {
353
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /launch <claude|codex|ucode> [nickname=<name>] [count=<n>]");
354
+ return;
355
+ }
356
+
357
+ const agentType = String(args[0] || "").trim().toLowerCase();
358
+ if (agentType !== "claude" && agentType !== "codex" && agentType !== "ucode") {
359
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown agent type. Use: claude, codex, or ucode");
360
+ return;
361
+ }
362
+ const normalizedAgent = agentType === "ucode" ? "ufoo" : agentType;
363
+
364
+ const parsedOptions = {};
365
+ for (let i = 1; i < args.length; i += 1) {
366
+ const arg = args[i];
367
+ if (arg.includes("=")) {
368
+ const [key, value] = arg.split("=", 2);
369
+ parsedOptions[key] = value;
370
+ }
371
+ }
372
+
373
+ const nickname = parsedOptions.nickname || "";
374
+ const count = parseInt(parsedOptions.count || "1", 10);
375
+ if (nickname && count > 1) {
376
+ logMessage("error", "{white-fg}✗{/white-fg} nickname requires count=1");
377
+ return;
378
+ }
379
+
380
+ try {
381
+ const label = nickname ? ` (${nickname})` : "";
382
+ logMessage("system", `{white-fg}⚙{/white-fg} Launching ${normalizedAgent}${label}...`);
383
+ send({
384
+ type: IPC_REQUEST_TYPES.LAUNCH_AGENT,
385
+ agent: normalizedAgent,
386
+ count: Number.isFinite(count) ? count : 1,
387
+ nickname,
388
+ });
389
+ schedule(requestStatus, 1000);
390
+ } catch (err) {
391
+ logMessage("error", `{white-fg}✗{/white-fg} Launch failed: ${escapeBlessed(err.message)}`);
392
+ }
393
+ }
394
+
395
+ async function handleResumeCommand(args = []) {
396
+ const action = String(args[0] || "").toLowerCase();
397
+ if (action === "list" || action === "ls") {
398
+ const target = args[1] || "";
399
+ const label = target ? ` (${target})` : "";
400
+ logMessage("system", `{white-fg}⚙{/white-fg} Listing recoverable agents${label}...`);
401
+ send({ type: IPC_REQUEST_TYPES.LIST_RECOVERABLE_AGENTS, target });
402
+ schedule(requestStatus, 1000);
403
+ return;
404
+ }
405
+
406
+ const target = args[0] || "";
407
+ const label = target ? ` (${target})` : "";
408
+ logMessage("system", `{white-fg}⚙{/white-fg} Resuming agents${label}...`);
409
+ send({ type: IPC_REQUEST_TYPES.RESUME_AGENTS, target });
410
+ schedule(requestStatus, 1000);
411
+ }
412
+
413
+ function parseCronTargets(raw = "") {
414
+ return String(raw || "")
415
+ .split(",")
416
+ .map((item) => item.trim())
417
+ .filter(Boolean);
418
+ }
419
+
420
+ async function handleCornCommand(args = []) {
421
+ const action = String(args[0] || "").trim().toLowerCase();
422
+ if (action === "list" || action === "ls") {
423
+ const tasks = Array.isArray(listCronTasks()) ? listCronTasks() : [];
424
+ if (tasks.length === 0) {
425
+ logMessage("system", "{cyan-fg}Cron:{/cyan-fg} none");
426
+ return;
427
+ }
428
+ logMessage("system", `{cyan-fg}Cron:{/cyan-fg} ${tasks.length} task(s)`);
429
+ for (const task of tasks) {
430
+ logMessage("system", ` • ${task.summary || task.id}`);
431
+ }
432
+ return;
433
+ }
434
+
435
+ if (action === "stop" || action === "rm" || action === "remove") {
436
+ const target = String(args[1] || "").trim();
437
+ if (!target) {
438
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /corn stop <id|all>");
439
+ return;
440
+ }
441
+ if (target === "all") {
442
+ const tasks = Array.isArray(listCronTasks()) ? listCronTasks() : [];
443
+ let stopped = 0;
444
+ for (const task of tasks) {
445
+ if (task && task.id && stopCronTask(task.id)) stopped += 1;
446
+ }
447
+ logMessage("system", `{white-fg}✓{/white-fg} Stopped ${stopped} cron task(s)`);
448
+ return;
449
+ }
450
+ if (!stopCronTask(target)) {
451
+ logMessage("error", `{white-fg}✗{/white-fg} Cron task not found: ${target}`);
452
+ return;
453
+ }
454
+ logMessage("system", `{white-fg}✓{/white-fg} Stopped cron task ${target}`);
455
+ return;
456
+ }
457
+
458
+ const startArgs = action === "start" ? args.slice(1) : args;
459
+ const kv = parseUcodeConfigKv(startArgs);
460
+ const nonKvParts = startArgs.filter((item) => !String(item || "").includes("="));
461
+
462
+ const intervalRaw = String(
463
+ kv.every || kv.interval || kv.interval_ms || kv.ms || ""
464
+ ).trim();
465
+ const targetsRaw = String(
466
+ kv.target || kv.targets || kv.agent || kv.agents || ""
467
+ ).trim();
468
+ const prompt = String(
469
+ kv.prompt || kv.message || kv.msg || nonKvParts.join(" ") || ""
470
+ ).trim();
471
+
472
+ if (!intervalRaw || !targetsRaw || !prompt) {
473
+ logMessage(
474
+ "error",
475
+ "{white-fg}✗{/white-fg} Usage: /corn start every=<10s|5m> target=<agent1,agent2> prompt=\"...\""
476
+ );
477
+ return;
478
+ }
479
+
480
+ const intervalMs = parseIntervalMs(intervalRaw);
481
+ if (!Number.isFinite(intervalMs) || intervalMs < 1000) {
482
+ logMessage("error", "{white-fg}✗{/white-fg} Invalid interval (min 1s)");
483
+ return;
484
+ }
485
+
486
+ const targets = parseCronTargets(targetsRaw);
487
+ if (targets.length === 0) {
488
+ logMessage("error", "{white-fg}✗{/white-fg} At least one target agent is required");
489
+ return;
490
+ }
491
+
492
+ const task = createCronTask({
493
+ intervalMs,
494
+ targets,
495
+ prompt,
496
+ });
497
+ if (!task) {
498
+ logMessage("error", "{white-fg}✗{/white-fg} Failed to create cron task");
499
+ return;
500
+ }
501
+
502
+ logMessage(
503
+ "system",
504
+ `{white-fg}✓{/white-fg} Cron started ${task.id}: every ${formatIntervalMs(intervalMs)} -> ${targets.join(", ")}`
505
+ );
506
+ }
507
+
508
+ async function handleSettingsCommand(args = []) {
509
+ const section = String(args[0] || "").trim().toLowerCase();
510
+ if (!section) {
511
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings ucode [show|set|clear ...]");
512
+ return;
513
+ }
514
+
515
+ if (section === "ucode") {
516
+ const subArgs = args.slice(1);
517
+ if (subArgs.length === 0) {
518
+ await handleUcodeConfigCommand(["show"]);
519
+ } else {
520
+ await handleUcodeConfigCommand(subArgs);
521
+ }
522
+ return;
523
+ }
524
+
525
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown settings section. Use: ucode");
526
+ }
527
+
528
+ function parseUcodeConfigKv(args = []) {
529
+ const parsed = {};
530
+ for (const raw of args) {
531
+ if (!raw || !String(raw).includes("=")) continue;
532
+ const [keyRaw, ...valueParts] = String(raw).split("=");
533
+ const key = String(keyRaw || "").trim().toLowerCase();
534
+ const value = valueParts.join("=").trim();
535
+ if (!key) continue;
536
+ parsed[key] = value;
537
+ }
538
+ return parsed;
539
+ }
540
+
541
+ function maskSecret(value = "") {
542
+ const text = String(value || "");
543
+ if (!text) return "(unset)";
544
+ if (text.length <= 8) return "***";
545
+ return `${text.slice(0, 4)}...${text.slice(-4)}`;
546
+ }
547
+
548
+ function inferUcodeTransport(provider = "", url = "") {
549
+ return resolveTransport({
550
+ provider: String(provider || "").trim(),
551
+ baseUrl: String(url || "").trim(),
552
+ });
553
+ }
554
+
555
+ async function handleUcodeConfigCommand(args = []) {
556
+ const first = String(args[0] || "").trim().toLowerCase();
557
+ const hasInlineKv = args.some((item) => String(item || "").includes("="));
558
+ const action = (!first || hasInlineKv) ? "set" : first;
559
+
560
+ if (action === "show" || action === "status") {
561
+ const config = loadConfig(projectRoot) || {};
562
+ const provider = String(config.ucodeProvider || "").trim();
563
+ const model = String(config.ucodeModel || "").trim();
564
+ const url = String(config.ucodeBaseUrl || "").trim();
565
+ const key = String(config.ucodeApiKey || "").trim();
566
+ const transport = inferUcodeTransport(provider, url);
567
+ logMessage("system", "{cyan-fg}ucode config:{/cyan-fg}");
568
+ logMessage("system", ` • provider: ${provider || "(unset)"}`);
569
+ logMessage("system", ` • model: ${model || "(unset)"}`);
570
+ logMessage("system", ` • url: ${url || "(unset)"}`);
571
+ logMessage("system", ` • key: ${maskSecret(key)}`);
572
+ logMessage("system", ` • transport: ${transport} (auto)`);
573
+ logMessage("system", " • tip: url supports generic gateway base, transport is auto-detected");
574
+ return;
575
+ }
576
+
577
+ if (action === "set") {
578
+ const kvArgs = hasInlineKv ? args : args.slice(1);
579
+ const kv = parseUcodeConfigKv(kvArgs);
580
+ const updates = {};
581
+ if (Object.prototype.hasOwnProperty.call(kv, "provider")) updates.ucodeProvider = String(kv.provider || "").trim();
582
+ if (Object.prototype.hasOwnProperty.call(kv, "model")) updates.ucodeModel = String(kv.model || "").trim();
583
+ if (Object.prototype.hasOwnProperty.call(kv, "url")) updates.ucodeBaseUrl = String(kv.url || "").trim();
584
+ if (Object.prototype.hasOwnProperty.call(kv, "baseurl")) updates.ucodeBaseUrl = String(kv.baseurl || "").trim();
585
+ if (Object.prototype.hasOwnProperty.call(kv, "base_url")) updates.ucodeBaseUrl = String(kv.base_url || "").trim();
586
+ if (Object.prototype.hasOwnProperty.call(kv, "key")) updates.ucodeApiKey = String(kv.key || "").trim();
587
+ if (Object.prototype.hasOwnProperty.call(kv, "apikey")) updates.ucodeApiKey = String(kv.apikey || "").trim();
588
+ if (Object.prototype.hasOwnProperty.call(kv, "api_key")) updates.ucodeApiKey = String(kv.api_key || "").trim();
589
+ if (Object.prototype.hasOwnProperty.call(kv, "token")) updates.ucodeApiKey = String(kv.token || "").trim();
590
+
591
+ if (Object.keys(updates).length === 0) {
592
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings ucode set provider=<openai|anthropic> model=<id> url=<baseUrl> key=<apiKey>");
593
+ return;
594
+ }
595
+ saveConfig(projectRoot, updates);
596
+ logMessage("system", "{white-fg}✓{/white-fg} ucode config updated");
597
+ if (Object.prototype.hasOwnProperty.call(updates, "ucodeProvider")) {
598
+ logMessage("system", ` • provider: ${updates.ucodeProvider || "(unset)"}`);
599
+ }
600
+ if (Object.prototype.hasOwnProperty.call(updates, "ucodeModel")) {
601
+ logMessage("system", ` • model: ${updates.ucodeModel || "(unset)"}`);
602
+ }
603
+ if (Object.prototype.hasOwnProperty.call(updates, "ucodeBaseUrl")) {
604
+ logMessage("system", ` • url: ${updates.ucodeBaseUrl || "(unset)"}`);
605
+ }
606
+ if (Object.prototype.hasOwnProperty.call(updates, "ucodeApiKey")) {
607
+ logMessage("system", ` • key: ${maskSecret(updates.ucodeApiKey)}`);
608
+ }
609
+ const nextConfig = loadConfig(projectRoot) || {};
610
+ logMessage("system", ` • transport: ${inferUcodeTransport(nextConfig.ucodeProvider, nextConfig.ucodeBaseUrl)} (auto)`);
611
+ return;
612
+ }
613
+
614
+ if (action === "clear") {
615
+ const fieldsRaw = args.slice(1).map((item) => String(item || "").trim().toLowerCase()).filter(Boolean);
616
+ const fields = fieldsRaw.length === 0 ? ["all"] : fieldsRaw;
617
+ const updates = {};
618
+ const clearAll = fields.includes("all");
619
+ if (clearAll || fields.includes("provider")) updates.ucodeProvider = "";
620
+ if (clearAll || fields.includes("model")) updates.ucodeModel = "";
621
+ if (clearAll || fields.includes("url") || fields.includes("baseurl") || fields.includes("base_url")) updates.ucodeBaseUrl = "";
622
+ if (clearAll || fields.includes("key") || fields.includes("apikey") || fields.includes("api_key") || fields.includes("token")) updates.ucodeApiKey = "";
623
+ if (Object.keys(updates).length === 0) {
624
+ logMessage("error", "{white-fg}✗{/white-fg} Usage: /settings ucode clear [provider|model|url|key|all]");
625
+ return;
626
+ }
627
+ saveConfig(projectRoot, updates);
628
+ logMessage("system", "{white-fg}✓{/white-fg} ucode config cleared");
629
+ return;
630
+ }
631
+
632
+ logMessage("error", "{white-fg}✗{/white-fg} Unknown settings ucode action. Use: show, set, clear");
633
+ }
634
+
635
+ async function executeCommand(text) {
636
+ const parsed = parseCommand(text);
637
+ if (!parsed) return false;
638
+
639
+ const { command, args } = parsed;
640
+
641
+ switch (command) {
642
+ case "doctor":
643
+ await handleDoctorCommand();
644
+ return true;
645
+ case "status":
646
+ await handleStatusCommand();
647
+ return true;
648
+ case "daemon":
649
+ await handleDaemonCommand(args);
650
+ return true;
651
+ case "init":
652
+ await handleInitCommand(args);
653
+ return true;
654
+ case "bus":
655
+ await handleBusCommand(args);
656
+ return true;
657
+ case "ctx":
658
+ await handleCtxCommand(args);
659
+ return true;
660
+ case "skills":
661
+ await handleSkillsCommand(args);
662
+ return true;
663
+ case "launch":
664
+ await handleLaunchCommand(args);
665
+ return true;
666
+ case "resume":
667
+ await handleResumeCommand(args);
668
+ return true;
669
+ case "corn":
670
+ await handleCornCommand(args);
671
+ return true;
672
+ case "settings":
673
+ await handleSettingsCommand(args);
674
+ return true;
675
+ default:
676
+ logMessage("error", `{white-fg}✗{/white-fg} Unknown command: /${command}`);
677
+ return true;
678
+ }
679
+ }
680
+
681
+ return {
682
+ executeCommand,
683
+ handleDoctorCommand,
684
+ handleStatusCommand,
685
+ handleDaemonCommand,
686
+ handleInitCommand,
687
+ handleBusCommand,
688
+ handleCtxCommand,
689
+ handleSkillsCommand,
690
+ handleLaunchCommand,
691
+ handleResumeCommand,
692
+ handleCornCommand,
693
+ handleSettingsCommand,
694
+ handleUcodeConfigCommand,
695
+ };
696
+ }
697
+
698
+ module.exports = {
699
+ createCommandExecutor,
700
+ };