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,184 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { loadConfig } = require("../config");
4
+ const {
5
+ resolveNativeFallbackCommand,
6
+ defaultBundledPromptFile,
7
+ } = require("./ucode");
8
+ const { inspectUcodeBuildSetup } = require("./ucodeBuild");
9
+ const { inspectUcodeRuntimeConfig } = require("./ucodeRuntimeConfig");
10
+ const { prepareUcodeBootstrap } = require("./ucodeBootstrap");
11
+
12
+ function inspectUcodeSetup({
13
+ projectRoot = process.cwd(),
14
+ env = process.env,
15
+ loadConfigImpl = loadConfig,
16
+ resolveNativeImpl = resolveNativeFallbackCommand,
17
+ } = {}) {
18
+ const root = path.resolve(projectRoot);
19
+ const config = loadConfigImpl(root);
20
+ const native = resolveNativeImpl({ env, config, cwd: root });
21
+ const coreAvailable = Boolean(native && native.available !== false && native.command);
22
+ const core = native ? {
23
+ root: native.root || path.resolve(__dirname, "..", "code"),
24
+ command: native.command,
25
+ args: native.args || [],
26
+ available: coreAvailable,
27
+ missingReason: String(native.missingReason || "").trim(),
28
+ resolvedPath: String(native.resolvedPath || "").trim(),
29
+ } : null;
30
+ const promptFile = String(
31
+ env.UFOO_UCODE_PROMPT_FILE
32
+ || config.ucodePromptFile
33
+ || defaultBundledPromptFile()
34
+ ).trim();
35
+ const bootstrapFile = String(
36
+ env.UFOO_UCODE_BOOTSTRAP_FILE
37
+ || config.ucodeBootstrapFile
38
+ || path.join(root, ".ufoo", "agent", "ucode", "bootstrap.md")
39
+ ).trim();
40
+ const build = inspectUcodeBuildSetup({
41
+ projectRoot: root,
42
+ env,
43
+ loadConfigImpl,
44
+ });
45
+ const runtime = inspectUcodeRuntimeConfig({
46
+ projectRoot: root,
47
+ env,
48
+ loadConfigImpl,
49
+ });
50
+ let importMeta = null;
51
+ if (core && core.root) {
52
+ const candidates = [
53
+ path.join(core.root, ".ufoo-import.json"),
54
+ path.join(core.root, "..", ".ufoo-import.json"),
55
+ path.join(core.root, "..", "..", ".ufoo-import.json"),
56
+ path.join(core.root, "..", "..", "..", ".ufoo-import.json"),
57
+ ].map((item) => path.resolve(item));
58
+ for (const candidate of candidates) {
59
+ try {
60
+ if (!fs.existsSync(candidate)) continue;
61
+ const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
62
+ if (parsed && typeof parsed === "object") {
63
+ importMeta = {
64
+ file: candidate,
65
+ importedAt: String(parsed.imported_at || "").trim(),
66
+ upstreamCommit: String(parsed.upstream_commit || "").trim(),
67
+ upstreamBranch: String(parsed.upstream_branch || "").trim(),
68
+ upstreamRemote: String(parsed.upstream_remote || "").trim(),
69
+ };
70
+ break;
71
+ }
72
+ } catch {
73
+ // ignore malformed metadata
74
+ }
75
+ }
76
+ }
77
+
78
+ return {
79
+ projectRoot: root,
80
+ expectedBundledCoreRoot: core ? core.root : "",
81
+ core: {
82
+ found: Boolean(core && core.available),
83
+ root: core ? core.root : "",
84
+ command: core ? core.command : "",
85
+ args: core ? core.args : [],
86
+ available: Boolean(core && core.available),
87
+ missingReason: core ? core.missingReason : "",
88
+ resolvedPath: core ? core.resolvedPath : "",
89
+ },
90
+ promptFile,
91
+ promptExists: Boolean(promptFile && fs.existsSync(promptFile)),
92
+ bootstrapFile,
93
+ build,
94
+ runtime,
95
+ importMeta,
96
+ configuredCommand: String(
97
+ env.UFOO_UCODE_CMD
98
+ || env.UFOO_UFOO_CODE_CMD
99
+ || config.ucodeCommand
100
+ || config.ufooCodeCommand
101
+ || ""
102
+ ).trim(),
103
+ };
104
+ }
105
+
106
+ function formatUcodeDoctor(result = {}) {
107
+ const lines = [];
108
+ lines.push("=== ucode doctor ===");
109
+ lines.push(`project: ${result.projectRoot || process.cwd()}`);
110
+ lines.push(`core: ${result.core && result.core.found ? "ready" : "missing"}`);
111
+ if (result.core && result.core.found) {
112
+ lines.push(` root: ${result.core.root}`);
113
+ lines.push(` launch: ${result.core.command} ${(result.core.args || []).join(" ")}`.trim());
114
+ if (result.core.resolvedPath) {
115
+ lines.push(` resolved path: ${result.core.resolvedPath}`);
116
+ }
117
+ } else {
118
+ if (result.expectedBundledCoreRoot) {
119
+ lines.push(` expected bundled root: ${result.expectedBundledCoreRoot}`);
120
+ }
121
+ if (result.core && result.core.command) {
122
+ lines.push(` attempted launch: ${result.core.command} ${(result.core.args || []).join(" ")}`.trim());
123
+ }
124
+ if (result.core && result.core.missingReason) {
125
+ lines.push(` missing reason: ${result.core.missingReason}`);
126
+ } else {
127
+ lines.push(" missing reason: native executable is unavailable");
128
+ }
129
+ }
130
+ if (result.configuredCommand) {
131
+ lines.push(`configured command override (ignored in native-only mode): ${result.configuredCommand}`);
132
+ }
133
+ lines.push(`prompt: ${result.promptFile || "(none)"}${result.promptExists ? "" : " (missing)"}`);
134
+ lines.push(`bootstrap: ${result.bootstrapFile || "(none)"}`);
135
+ if (result.build && result.build.coreRoot) {
136
+ lines.push(`build: ${result.build.distCliExists ? "ready" : "missing dist"}`);
137
+ lines.push(` core root: ${result.build.coreRoot}`);
138
+ lines.push(` workspace root: ${result.build.workspaceRoot || "(none)"}`);
139
+ lines.push(` dist entry: ${result.build.distCliPath || "(none)"}${result.build.distCliExists ? "" : " (missing)"}`);
140
+ } else {
141
+ lines.push("build: unresolved core root");
142
+ }
143
+ if (result.importMeta) {
144
+ lines.push(`import metadata: ${result.importMeta.file || "(unknown)"}`);
145
+ lines.push(` imported at: ${result.importMeta.importedAt || "(unknown)"}`);
146
+ lines.push(` upstream commit: ${result.importMeta.upstreamCommit || "(unknown)"}`);
147
+ lines.push(` upstream branch: ${result.importMeta.upstreamBranch || "(unknown)"}`);
148
+ lines.push(` upstream remote: ${result.importMeta.upstreamRemote ? "(set)" : "(unset)"}`);
149
+ }
150
+ if (result.runtime) {
151
+ lines.push(`runtime: ${result.runtime.agentDir || "(none)"}`);
152
+ lines.push(` provider: ${result.runtime.provider || "(unset)"}`);
153
+ lines.push(` model: ${result.runtime.model || "(unset)"}`);
154
+ lines.push(` base url: ${result.runtime.baseUrl ? "(set)" : "(unset)"}`);
155
+ lines.push(` api key: ${result.runtime.apiKey ? "(set)" : "(unset)"}`);
156
+ }
157
+ if (!(result.core && result.core.found) && !result.configuredCommand) {
158
+ lines.push("hint: verify native entry exists at src/code/agent.js");
159
+ }
160
+ return lines.join("\n");
161
+ }
162
+
163
+ function prepareAndInspectUcode({
164
+ projectRoot = process.cwd(),
165
+ env = process.env,
166
+ loadConfigImpl = loadConfig,
167
+ } = {}) {
168
+ const inspection = inspectUcodeSetup({ projectRoot, env, loadConfigImpl });
169
+ const prepared = prepareUcodeBootstrap({
170
+ projectRoot: inspection.projectRoot,
171
+ promptFile: inspection.promptFile,
172
+ targetFile: inspection.bootstrapFile,
173
+ });
174
+ return {
175
+ ...inspection,
176
+ bootstrapPrepared: prepared,
177
+ };
178
+ }
179
+
180
+ module.exports = {
181
+ inspectUcodeSetup,
182
+ formatUcodeDoctor,
183
+ prepareAndInspectUcode,
184
+ };
@@ -0,0 +1,129 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { loadConfig } = require("../config");
4
+
5
+ function readJson(filePath = "", fallback = {}) {
6
+ if (!filePath) return fallback;
7
+ try {
8
+ const raw = fs.readFileSync(filePath, "utf8");
9
+ const parsed = JSON.parse(raw);
10
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return fallback;
11
+ return parsed;
12
+ } catch {
13
+ return fallback;
14
+ }
15
+ }
16
+
17
+ function writeJson(filePath = "", data = {}) {
18
+ if (!filePath) return;
19
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
20
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
21
+ }
22
+
23
+ function resolveRuntimeValues({
24
+ env = process.env,
25
+ config = {},
26
+ projectRoot = process.cwd(),
27
+ } = {}) {
28
+ const root = path.resolve(projectRoot);
29
+ const provider = String(env.UFOO_UCODE_PROVIDER || config.ucodeProvider || "").trim();
30
+ const model = String(env.UFOO_UCODE_MODEL || config.ucodeModel || "").trim();
31
+ const apiKey = String(env.UFOO_UCODE_API_KEY || config.ucodeApiKey || "").trim();
32
+ const baseUrl = String(env.UFOO_UCODE_BASE_URL || config.ucodeBaseUrl || "").trim();
33
+ const agentDir = path.resolve(
34
+ String(
35
+ env.PI_CODING_AGENT_DIR
36
+ || env.UFOO_UCODE_AGENT_DIR
37
+ || config.ucodeAgentDir
38
+ || path.join(root, ".ufoo", "agent", "ucode", "pi-agent")
39
+ ).trim()
40
+ );
41
+ return {
42
+ projectRoot: root,
43
+ provider,
44
+ model,
45
+ apiKey,
46
+ baseUrl,
47
+ agentDir,
48
+ };
49
+ }
50
+
51
+ function inspectUcodeRuntimeConfig({
52
+ projectRoot = process.cwd(),
53
+ env = process.env,
54
+ loadConfigImpl = loadConfig,
55
+ } = {}) {
56
+ const root = path.resolve(projectRoot);
57
+ const config = loadConfigImpl(root);
58
+ const resolved = resolveRuntimeValues({
59
+ env,
60
+ config,
61
+ projectRoot: root,
62
+ });
63
+ const settingsFile = path.join(resolved.agentDir, "settings.json");
64
+ const authFile = path.join(resolved.agentDir, "auth.json");
65
+ const modelsFile = path.join(resolved.agentDir, "models.json");
66
+ return {
67
+ ...resolved,
68
+ settingsFile,
69
+ authFile,
70
+ modelsFile,
71
+ settingsExists: fs.existsSync(settingsFile),
72
+ authExists: fs.existsSync(authFile),
73
+ modelsExists: fs.existsSync(modelsFile),
74
+ };
75
+ }
76
+
77
+ function prepareUcodeRuntimeConfig({
78
+ projectRoot = process.cwd(),
79
+ env = process.env,
80
+ loadConfigImpl = loadConfig,
81
+ } = {}) {
82
+ const inspection = inspectUcodeRuntimeConfig({
83
+ projectRoot,
84
+ env,
85
+ loadConfigImpl,
86
+ });
87
+
88
+ const settings = readJson(inspection.settingsFile, {});
89
+ if (inspection.provider) settings.defaultProvider = inspection.provider;
90
+ if (inspection.model) settings.defaultModel = inspection.model;
91
+ writeJson(inspection.settingsFile, settings);
92
+
93
+ if (inspection.provider && inspection.apiKey) {
94
+ const auth = readJson(inspection.authFile, {});
95
+ auth[inspection.provider] = {
96
+ type: "api_key",
97
+ key: inspection.apiKey,
98
+ };
99
+ writeJson(inspection.authFile, auth);
100
+ }
101
+
102
+ if (inspection.provider && inspection.baseUrl) {
103
+ const models = readJson(inspection.modelsFile, {});
104
+ if (!models.providers || typeof models.providers !== "object" || Array.isArray(models.providers)) {
105
+ models.providers = {};
106
+ }
107
+ const providerConfig = models.providers[inspection.provider];
108
+ const nextProviderConfig = (providerConfig && typeof providerConfig === "object" && !Array.isArray(providerConfig))
109
+ ? { ...providerConfig }
110
+ : {};
111
+ nextProviderConfig.baseUrl = inspection.baseUrl;
112
+ models.providers[inspection.provider] = nextProviderConfig;
113
+ writeJson(inspection.modelsFile, models);
114
+ }
115
+
116
+ return {
117
+ ...inspection,
118
+ env: {
119
+ PI_CODING_AGENT_DIR: inspection.agentDir,
120
+ },
121
+ };
122
+ }
123
+
124
+ module.exports = {
125
+ resolveRuntimeValues,
126
+ inspectUcodeRuntimeConfig,
127
+ prepareUcodeRuntimeConfig,
128
+ };
129
+
@@ -2,9 +2,11 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { runCliAgent } = require("./cliRunner");
4
4
  const { normalizeCliOutput } = require("./normalizeOutput");
5
+ const { buildStatus } = require("../daemon/status");
6
+ const { getUfooPaths } = require("../ufoo/paths");
5
7
 
6
8
  function loadSessionState(projectRoot) {
7
- const dir = path.join(projectRoot, ".ufoo", "agent");
9
+ const dir = getUfooPaths(projectRoot).agentDir;
8
10
  const file = path.join(dir, "ufoo-agent.json");
9
11
  try {
10
12
  const data = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -15,52 +17,38 @@ function loadSessionState(projectRoot) {
15
17
  }
16
18
 
17
19
  function saveSessionState(projectRoot, state) {
18
- const dir = path.join(projectRoot, ".ufoo", "agent");
20
+ const dir = getUfooPaths(projectRoot).agentDir;
19
21
  fs.mkdirSync(dir, { recursive: true });
20
22
  fs.writeFileSync(path.join(dir, "ufoo-agent.json"), JSON.stringify(state, null, 2));
21
23
  }
22
24
 
23
- function isPidAlive(pid) {
24
- if (!pid || typeof pid !== "number") return false;
25
- try {
26
- process.kill(pid, 0);
27
- return true;
28
- } catch (err) {
29
- return Boolean(err && err.code === "EPERM");
30
- }
31
- }
32
-
33
25
  function loadBusSummary(projectRoot, maxLines = 20) {
34
- const busPath = path.join(projectRoot, ".ufoo", "bus", "bus.json");
35
- let subscribers = [];
26
+ // Use daemon's buildStatus as the single source of truth
27
+ let agents = [];
36
28
  let nicknames = {};
37
29
  try {
38
- const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
39
- subscribers = Object.entries(bus.subscribers || {})
40
- .map(([id, meta]) => {
41
- const pid = typeof meta.pid === "number" ? meta.pid : Number(meta.pid || 0);
42
- const status = meta.status || "unknown";
43
- const online = status === "active" && isPidAlive(pid);
44
- const nickname = meta.nickname || "";
45
- if (nickname) {
46
- nicknames[nickname] = id;
47
- }
48
- return {
49
- id,
50
- status,
51
- online,
52
- agent_type: meta.agent_type || "",
53
- nickname,
54
- last_heartbeat: meta.last_heartbeat || "",
55
- };
56
- })
57
- .filter((item) => item.online);
30
+ const status = buildStatus(projectRoot);
31
+ const activeMeta = status.active_meta || [];
32
+ agents = activeMeta.map((item) => {
33
+ const nickname = item.nickname || "";
34
+ if (nickname) {
35
+ nicknames[nickname] = item.id;
36
+ }
37
+ return {
38
+ id: item.id,
39
+ status: "active",
40
+ online: true,
41
+ agent_type: "", // Not included in active_meta, but not needed
42
+ nickname,
43
+ last_heartbeat: "",
44
+ };
45
+ });
58
46
  } catch {
59
- subscribers = [];
47
+ agents = [];
60
48
  nicknames = {};
61
49
  }
62
50
 
63
- const eventsDir = path.join(projectRoot, ".ufoo", "bus", "events");
51
+ const eventsDir = getUfooPaths(projectRoot).busEventsDir;
64
52
  let recent = [];
65
53
  try {
66
54
  const files = fs
@@ -80,25 +68,41 @@ function loadBusSummary(projectRoot, maxLines = 20) {
80
68
  recent = [];
81
69
  }
82
70
 
83
- return { subscribers, nicknames, recent };
71
+ return { agents, nicknames, recent };
84
72
  }
85
73
 
86
74
  function buildSystemPrompt(context) {
75
+ const hasAgents = context.agents && context.agents.length > 0;
76
+ const agentGuidance = hasAgents
77
+ ? ""
78
+ : "\n- IMPORTANT: No coding agents are currently online. For lightweight exploration or temporary command execution, prefer top-level assistant_call.\n- Use ops.launch only when persistent coding-agent sessions are necessary.";
79
+
87
80
  return [
88
81
  "You are ufoo-agent, a headless routing controller.",
82
+ "You can call a private execution helper via top-level assistant_call (not visible on bus).",
89
83
  "Return ONLY valid JSON. No extra text.",
90
84
  "Schema:",
91
85
  "{",
92
86
  ' "reply": "string",',
87
+ ' "assistant_call": {"kind":"explore|bash|mixed","task":"string","context":"optional","expect":"optional","provider":"codex|claude|ufoo (optional)","model":"optional","timeout_ms":60000},',
93
88
  ' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string"}],',
94
- ' "ops": [{"action":"spawn|close","agent":"codex|claude","count":1,"agent_id":"id","nickname":"optional"}],',
89
+ ' "ops": [{"action":"launch|close|rename|cron","agent":"codex|claude|ucode","count":1,"agent_id":"id","nickname":"optional","operation":"start|list|stop","every":"30m","interval_ms":1800000,"target":"agent-id|nickname|csv","targets":["agent-id"],"prompt":"message","id":"task-id|all"}],',
95
90
  ' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}',
96
91
  "}",
97
92
  "Rules:",
98
93
  "- target must be 'broadcast', concrete agent-id, or a known nickname",
99
94
  "- If multiple possible agents, use disambiguate with candidates and no dispatch.",
100
- "- If user specifies a nickname for a new agent, include ops.spawn with nickname so daemon can rename.",
95
+ "- If user specifies a nickname for a new agent, include ops.launch with nickname so daemon can rename.",
96
+ "- If user requests rename, use ops.rename with agent_id and nickname (do NOT launch).",
97
+ "- For scheduled follow-up (cron/corn), use ops.cron with operation=start and include every+target(s)+prompt.",
98
+ "- To check scheduled tasks, use ops.cron with operation=list.",
99
+ "- To stop scheduled tasks, use ops.cron with operation=stop and id (or id=all).",
100
+ "- Use top-level assistant_call for project exploration, temporary shell tasks, and quick execution support.",
101
+ "- assistant_call fields: kind (explore|bash|mixed), task (required), context/expect (optional), provider (codex|claude|ufoo, optional), model/timeout_ms (optional).",
102
+ "- Prefer assistant_call over launching coding agents when the task is short-lived.",
103
+ "- Legacy compatibility: if model emits ops.assistant_call, daemon will still process it.",
101
104
  "- If no action needed, return reply with empty dispatch/ops.",
105
+ agentGuidance,
102
106
  "",
103
107
  "Context: online agents and recent bus events:",
104
108
  JSON.stringify(context),
@@ -106,7 +110,7 @@ function buildSystemPrompt(context) {
106
110
  }
107
111
 
108
112
  function loadHistory(projectRoot, maxTurns = 6) {
109
- const file = path.join(projectRoot, ".ufoo", "agent", "ufoo-agent.history.jsonl");
113
+ const file = path.join(getUfooPaths(projectRoot).agentDir, "ufoo-agent.history.jsonl");
110
114
  try {
111
115
  const lines = fs.readFileSync(file, "utf8").trim().split(/\r?\n/).filter(Boolean);
112
116
  const items = lines.map((l) => JSON.parse(l));
@@ -117,7 +121,7 @@ function loadHistory(projectRoot, maxTurns = 6) {
117
121
  }
118
122
 
119
123
  function appendHistory(projectRoot, item) {
120
- const dir = path.join(projectRoot, ".ufoo", "agent");
124
+ const dir = getUfooPaths(projectRoot).agentDir;
121
125
  fs.mkdirSync(dir, { recursive: true });
122
126
  const file = path.join(dir, "ufoo-agent.history.jsonl");
123
127
  fs.appendFileSync(file, `${JSON.stringify(item)}\n`);
@@ -200,7 +204,7 @@ async function runUfooAgent({ projectRoot, prompt, provider, model }) {
200
204
  const fallbackNickname = extractNickname(prompt);
201
205
  if (fallbackNickname && payload && Array.isArray(payload.ops)) {
202
206
  for (const op of payload.ops) {
203
- if (op && op.action === "spawn" && !op.nickname) {
207
+ if (op && (op.action === "launch" || op.action === "rename") && !op.nickname) {
204
208
  op.nickname = fallbackNickname;
205
209
  break;
206
210
  }