u-foo 1.0.6 → 1.2.0

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 +247 -23
  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 +168 -28
  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 +157 -0
  64. package/src/chat/index.js +938 -2910
  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 +133 -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 +1587 -0
  98. package/src/config.js +50 -2
  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 +662 -489
  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,67 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { loadConfig } = require("../config");
4
+ const {
5
+ resolveNativeFallbackCommand,
6
+ } = require("./ucode");
7
+
8
+ function resolveCoreRoot({
9
+ env = process.env,
10
+ config = {},
11
+ } = {}) {
12
+ void env;
13
+ void config;
14
+ const native = resolveNativeFallbackCommand();
15
+ return native.root || path.resolve(__dirname, "..", "code");
16
+ }
17
+
18
+ function inspectUcodeBuildSetup({
19
+ projectRoot = process.cwd(),
20
+ env = process.env,
21
+ loadConfigImpl = loadConfig,
22
+ } = {}) {
23
+ const root = path.resolve(projectRoot);
24
+ const config = loadConfigImpl(root);
25
+ const coreRoot = resolveCoreRoot({ env, config });
26
+ const native = resolveNativeFallbackCommand();
27
+ const workspaceRoot = root;
28
+ const distCliPath = Array.isArray(native.args) && native.args[0] ? path.resolve(native.args[0]) : "";
29
+ const distCliExists = Boolean(distCliPath && fs.existsSync(distCliPath));
30
+ const nodeModulesPath = "";
31
+ return {
32
+ projectRoot: root,
33
+ coreRoot,
34
+ workspaceRoot,
35
+ distCliPath,
36
+ distCliExists,
37
+ nodeModulesPath,
38
+ nodeModulesExists: Boolean(nodeModulesPath && fs.existsSync(nodeModulesPath)),
39
+ };
40
+ }
41
+
42
+ function buildUcodeCore({
43
+ projectRoot = process.cwd(),
44
+ env = process.env,
45
+ loadConfigImpl = loadConfig,
46
+ installIfMissing = true,
47
+ stdio = "inherit",
48
+ } = {}) {
49
+ const before = inspectUcodeBuildSetup({
50
+ projectRoot,
51
+ env,
52
+ loadConfigImpl,
53
+ });
54
+ void installIfMissing;
55
+ void stdio;
56
+ void env;
57
+ if (!before.distCliExists) {
58
+ throw new Error(`ucode native core entry missing: ${before.distCliPath || "(unknown)"}`);
59
+ }
60
+ return { ...before, steps: ["native-check"] };
61
+ }
62
+
63
+ module.exports = {
64
+ inspectUcodeBuildSetup,
65
+ buildUcodeCore,
66
+ resolveCoreRoot,
67
+ };
@@ -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
+
@@ -4,6 +4,11 @@ const { runCliAgent } = require("./cliRunner");
4
4
  const { normalizeCliOutput } = require("./normalizeOutput");
5
5
  const { buildStatus } = require("../daemon/status");
6
6
  const { getUfooPaths } = require("../ufoo/paths");
7
+ const {
8
+ resolveRuntimeConfig,
9
+ resolveCompletionUrl,
10
+ resolveAnthropicMessagesUrl,
11
+ } = require("../code/nativeRunner");
7
12
 
8
13
  function loadSessionState(projectRoot) {
9
14
  const dir = getUfooPaths(projectRoot).agentDir;
@@ -75,16 +80,18 @@ function buildSystemPrompt(context) {
75
80
  const hasAgents = context.agents && context.agents.length > 0;
76
81
  const agentGuidance = hasAgents
77
82
  ? ""
78
- : "\n- IMPORTANT: No agents are currently online. To execute tasks, you MUST launch agents using ops.launch.\n- Example: {\"reply\":\"Creating agent\",\"ops\":[{\"action\":\"launch\",\"agent\":\"claude\",\"count\":1}]}";
83
+ : "\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
84
 
80
85
  return [
81
86
  "You are ufoo-agent, a headless routing controller.",
87
+ "You can call a private execution helper via top-level assistant_call (not visible on bus).",
82
88
  "Return ONLY valid JSON. No extra text.",
83
89
  "Schema:",
84
90
  "{",
85
91
  ' "reply": "string",',
92
+ ' "assistant_call": {"kind":"explore|bash|mixed","task":"string","context":"optional","expect":"optional","provider":"codex|claude|ufoo (optional)","model":"optional","timeout_ms":60000},',
86
93
  ' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string"}],',
87
- ' "ops": [{"action":"launch|close|rename","agent":"codex|claude","count":1,"agent_id":"id","nickname":"optional"}],',
94
+ ' "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"}],',
88
95
  ' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}',
89
96
  "}",
90
97
  "Rules:",
@@ -92,6 +99,13 @@ function buildSystemPrompt(context) {
92
99
  "- If multiple possible agents, use disambiguate with candidates and no dispatch.",
93
100
  "- If user specifies a nickname for a new agent, include ops.launch with nickname so daemon can rename.",
94
101
  "- If user requests rename, use ops.rename with agent_id and nickname (do NOT launch).",
102
+ "- For scheduled follow-up (cron/corn), use ops.cron with operation=start and include every+target(s)+prompt.",
103
+ "- To check scheduled tasks, use ops.cron with operation=list.",
104
+ "- To stop scheduled tasks, use ops.cron with operation=stop and id (or id=all).",
105
+ "- Use top-level assistant_call for project exploration, temporary shell tasks, and quick execution support.",
106
+ "- assistant_call fields: kind (explore|bash|mixed), task (required), context/expect (optional), provider (codex|claude|ufoo, optional), model/timeout_ms (optional).",
107
+ "- Prefer assistant_call over launching coding agents when the task is short-lived.",
108
+ "- Legacy compatibility: if model emits ops.assistant_call, daemon will still process it.",
95
109
  "- If no action needed, return reply with empty dispatch/ops.",
96
110
  agentGuidance,
97
111
  "",
@@ -144,6 +158,111 @@ function extractNickname(prompt) {
144
158
  return "";
145
159
  }
146
160
 
161
+ function isUcodeProvider(value = "") {
162
+ const text = String(value || "").trim().toLowerCase();
163
+ return text === "ucode" || text === "ufoo" || text === "ufoo-code";
164
+ }
165
+
166
+ function stripMarkdownFence(text = "") {
167
+ const raw = String(text || "").trim();
168
+ const match = raw.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
169
+ if (match) return match[1].trim();
170
+ return raw;
171
+ }
172
+
173
+ function clipText(value = "", maxChars = 500) {
174
+ const text = String(value || "");
175
+ if (text.length <= maxChars) return text;
176
+ return `${text.slice(0, maxChars)}...[truncated]`;
177
+ }
178
+
179
+ async function runNativeRouterCall({ projectRoot, prompt, systemPrompt, model: requestedModel, timeoutMs = 120000 }) {
180
+ const runtime = resolveRuntimeConfig({
181
+ workspaceRoot: projectRoot,
182
+ provider: "",
183
+ model: requestedModel,
184
+ });
185
+
186
+ const requestModel = String(runtime.model || "").trim();
187
+ if (!requestModel) {
188
+ return { ok: false, error: "ucode model is not configured" };
189
+ }
190
+
191
+ const isAnthropic = runtime.transport === "anthropic-messages";
192
+ const url = isAnthropic
193
+ ? resolveAnthropicMessagesUrl(runtime.baseUrl)
194
+ : resolveCompletionUrl(runtime.baseUrl);
195
+
196
+ if (!url) {
197
+ return { ok: false, error: "ucode baseUrl is not configured" };
198
+ }
199
+
200
+ const headers = { "content-type": "application/json" };
201
+ let body;
202
+
203
+ if (isAnthropic) {
204
+ headers["anthropic-version"] = "2023-06-01";
205
+ if (runtime.apiKey) headers["x-api-key"] = runtime.apiKey;
206
+ body = JSON.stringify({
207
+ model: requestModel,
208
+ max_tokens: 4096,
209
+ system: String(systemPrompt || ""),
210
+ messages: [{ role: "user", content: String(prompt || "") }],
211
+ temperature: 0,
212
+ });
213
+ } else {
214
+ if (runtime.apiKey) headers.authorization = `Bearer ${runtime.apiKey}`;
215
+ const messages = [];
216
+ if (systemPrompt) messages.push({ role: "system", content: String(systemPrompt) });
217
+ messages.push({ role: "user", content: String(prompt || "") });
218
+ body = JSON.stringify({
219
+ model: requestModel,
220
+ messages,
221
+ temperature: 0,
222
+ });
223
+ }
224
+
225
+ const controller = new AbortController();
226
+ const timer = setTimeout(() => { try { controller.abort(); } catch {} }, timeoutMs);
227
+
228
+ try {
229
+ const response = await fetch(url, {
230
+ method: "POST",
231
+ headers,
232
+ body,
233
+ signal: controller.signal,
234
+ });
235
+
236
+ if (!response.ok) {
237
+ const errBody = await response.text().catch(() => "");
238
+ return { ok: false, error: `provider request failed (${response.status}): ${clipText(errBody)}` };
239
+ }
240
+
241
+ const data = await response.json();
242
+
243
+ let text = "";
244
+ if (isAnthropic) {
245
+ const content = Array.isArray(data.content) ? data.content : [];
246
+ text = content
247
+ .filter((item) => item && item.type === "text")
248
+ .map((item) => String(item.text || ""))
249
+ .join("");
250
+ } else {
251
+ const choice = data.choices && data.choices[0];
252
+ text = choice && choice.message && typeof choice.message.content === "string"
253
+ ? choice.message.content
254
+ : "";
255
+ }
256
+
257
+ return { ok: true, output: text.trim() };
258
+ } catch (err) {
259
+ const message = err && err.message ? err.message : "native router call failed";
260
+ return { ok: false, error: message };
261
+ } finally {
262
+ clearTimeout(timer);
263
+ }
264
+ }
265
+
147
266
  async function runUfooAgent({ projectRoot, prompt, provider, model }) {
148
267
  const state = loadSessionState(projectRoot);
149
268
  const bus = loadBusSummary(projectRoot);
@@ -152,36 +271,57 @@ async function runUfooAgent({ projectRoot, prompt, provider, model }) {
152
271
  const historyPrompt = buildHistoryPrompt(history);
153
272
  const fullPrompt = historyPrompt ? `${historyPrompt}User: ${prompt}` : prompt;
154
273
 
155
- let res = await runCliAgent({
156
- provider,
157
- model,
158
- prompt: fullPrompt,
159
- systemPrompt,
160
- sessionId: state.data?.sessionId,
161
- disableSession: provider === "claude-cli",
162
- cwd: projectRoot,
163
- });
274
+ let res;
164
275
 
165
- if (!res.ok) {
166
- const msg = (res.error || "").toLowerCase();
167
- if (msg.includes("session id") || msg.includes("session-id") || msg.includes("already in use")) {
168
- res = await runCliAgent({
169
- provider,
170
- model,
171
- prompt: fullPrompt,
172
- systemPrompt,
173
- sessionId: undefined,
174
- disableSession: provider === "claude-cli",
175
- cwd: projectRoot,
176
- });
276
+ if (isUcodeProvider(provider)) {
277
+ // Native path: direct HTTP to LLM API, no CLI binary needed
278
+ res = await runNativeRouterCall({
279
+ projectRoot,
280
+ prompt: fullPrompt,
281
+ systemPrompt,
282
+ model,
283
+ });
284
+ if (!res.ok) {
285
+ return { ok: false, error: res.error };
177
286
  }
178
- }
287
+ // Native path returns { ok, output } where output is raw text
288
+ res = { ok: true, output: res.output, sessionId: "" };
289
+ } else {
290
+ // CLI path: spawn codex/claude binary
291
+ res = await runCliAgent({
292
+ provider,
293
+ model,
294
+ prompt: fullPrompt,
295
+ systemPrompt,
296
+ sessionId: state.data?.sessionId,
297
+ disableSession: provider === "claude-cli",
298
+ cwd: projectRoot,
299
+ });
179
300
 
180
- if (!res.ok) {
181
- return { ok: false, error: res.error };
301
+ if (!res.ok) {
302
+ const msg = (res.error || "").toLowerCase();
303
+ if (msg.includes("session id") || msg.includes("session-id") || msg.includes("already in use")) {
304
+ res = await runCliAgent({
305
+ provider,
306
+ model,
307
+ prompt: fullPrompt,
308
+ systemPrompt,
309
+ sessionId: undefined,
310
+ disableSession: provider === "claude-cli",
311
+ cwd: projectRoot,
312
+ });
313
+ }
314
+ }
315
+
316
+ if (!res.ok) {
317
+ return { ok: false, error: res.error };
318
+ }
182
319
  }
183
320
 
184
- const text = normalizeCliOutput(res.output);
321
+ const rawText = isUcodeProvider(provider)
322
+ ? String(res.output || "").trim()
323
+ : normalizeCliOutput(res.output);
324
+ const text = stripMarkdownFence(rawText);
185
325
  let payload = null;
186
326
  try {
187
327
  payload = JSON.parse(text);
@@ -205,7 +345,7 @@ async function runUfooAgent({ projectRoot, prompt, provider, model }) {
205
345
  saveSessionState(projectRoot, {
206
346
  provider,
207
347
  model,
208
- sessionId: res.sessionId,
348
+ sessionId: res.sessionId || "",
209
349
  updated_at: new Date().toISOString(),
210
350
  });
211
351