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.
- package/README.md +44 -4
- package/SKILLS/ufoo/SKILL.md +17 -2
- package/SKILLS/uinit/SKILL.md +8 -3
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +4 -0
- package/modules/AGENTS.template.md +14 -4
- package/modules/bus/README.md +8 -5
- package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
- package/modules/context/SKILLS/uctx/SKILL.md +3 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +12 -3
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +20 -49
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +524 -31
- package/src/agent/internalRunner.js +76 -9
- package/src/agent/launcher.js +97 -45
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +144 -4
- package/src/agent/ptyRunner.js +480 -10
- package/src/agent/ptyWrapper.js +28 -3
- package/src/agent/readyDetector.js +16 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +11 -2
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +27 -11
- package/src/bus/daemon.js +133 -5
- package/src/bus/index.js +137 -80
- package/src/bus/inject.js +47 -17
- package/src/bus/message.js +145 -17
- package/src/bus/nickname.js +3 -1
- package/src/bus/queue.js +6 -1
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +20 -4
- package/src/bus/utils.js +9 -3
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +935 -2909
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +741 -238
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +47 -1
- package/src/context/decisions.js +12 -2
- package/src/context/index.js +18 -1
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +661 -488
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +417 -179
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +32 -17
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +2 -5
- package/src/daemon/status.js +24 -1
- package/src/init/index.js +68 -14
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/status/index.js +50 -17
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/ufoo/agentsStore.js +69 -3
- package/src/utils/banner.js +5 -2
- package/scripts/.archived/bash-to-js-migration/README.md +0 -46
- package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
- package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
- package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
- package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
- package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
- package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
- package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
- package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
- package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
- package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
- package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
- package/scripts/banner.sh +0 -2
- 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
|
+
|
package/src/agent/ufooAgent.js
CHANGED
|
@@ -75,16 +75,18 @@ function buildSystemPrompt(context) {
|
|
|
75
75
|
const hasAgents = context.agents && context.agents.length > 0;
|
|
76
76
|
const agentGuidance = hasAgents
|
|
77
77
|
? ""
|
|
78
|
-
: "\n- IMPORTANT: No agents are currently online.
|
|
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
79
|
|
|
80
80
|
return [
|
|
81
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).",
|
|
82
83
|
"Return ONLY valid JSON. No extra text.",
|
|
83
84
|
"Schema:",
|
|
84
85
|
"{",
|
|
85
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},',
|
|
86
88
|
' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string"}],',
|
|
87
|
-
' "ops": [{"action":"launch|close|rename","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"}],',
|
|
88
90
|
' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}',
|
|
89
91
|
"}",
|
|
90
92
|
"Rules:",
|
|
@@ -92,6 +94,13 @@ function buildSystemPrompt(context) {
|
|
|
92
94
|
"- If multiple possible agents, use disambiguate with candidates and no dispatch.",
|
|
93
95
|
"- If user specifies a nickname for a new agent, include ops.launch with nickname so daemon can rename.",
|
|
94
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.",
|
|
95
104
|
"- If no action needed, return reply with empty dispatch/ops.",
|
|
96
105
|
agentGuidance,
|
|
97
106
|
"",
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { runCliAgent } = require("../agent/cliRunner");
|
|
4
|
+
const { normalizeCliOutput } = require("../agent/normalizeOutput");
|
|
5
|
+
const { resolveAssistantEngine, runExternalAssistantEngine } = require("./engine");
|
|
6
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
|
+
|
|
8
|
+
const ASSISTANT_JSON_SCHEMA = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
ok: { type: "boolean" },
|
|
12
|
+
summary: { type: "string" },
|
|
13
|
+
artifacts: {
|
|
14
|
+
type: "array",
|
|
15
|
+
items: { type: "string" },
|
|
16
|
+
},
|
|
17
|
+
logs: {
|
|
18
|
+
type: "array",
|
|
19
|
+
items: { type: "string" },
|
|
20
|
+
},
|
|
21
|
+
error: { type: "string" },
|
|
22
|
+
metrics: {
|
|
23
|
+
type: "object",
|
|
24
|
+
additionalProperties: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ["ok", "summary"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function parseTaskPayload(payload = {}) {
|
|
31
|
+
const projectRoot = typeof payload.project_root === "string" ? payload.project_root : process.cwd();
|
|
32
|
+
const provider = typeof payload.provider === "string" ? payload.provider : "";
|
|
33
|
+
const fallbackProvider = typeof payload.fallback_provider === "string" ? payload.fallback_provider : "";
|
|
34
|
+
const model = typeof payload.model === "string" ? payload.model : "";
|
|
35
|
+
const task = typeof payload.task === "string" ? payload.task.trim() : "";
|
|
36
|
+
const kind = typeof payload.kind === "string" && payload.kind ? payload.kind : "mixed";
|
|
37
|
+
const context = typeof payload.context === "string" ? payload.context : "";
|
|
38
|
+
const expectText = typeof payload.expect === "string" ? payload.expect : "";
|
|
39
|
+
const timeoutMs = Number.isFinite(payload.timeout_ms) ? payload.timeout_ms : 60000;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
projectRoot,
|
|
43
|
+
provider,
|
|
44
|
+
fallbackProvider,
|
|
45
|
+
model,
|
|
46
|
+
task,
|
|
47
|
+
kind,
|
|
48
|
+
context,
|
|
49
|
+
expect: expectText,
|
|
50
|
+
timeoutMs,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeAssistantPayload(parsed, fallbackError = "") {
|
|
55
|
+
if (!parsed || typeof parsed !== "object") {
|
|
56
|
+
const text = String(parsed || "").trim();
|
|
57
|
+
if (text) {
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
summary: text,
|
|
61
|
+
artifacts: [],
|
|
62
|
+
logs: [],
|
|
63
|
+
error: "",
|
|
64
|
+
metrics: {},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
summary: "",
|
|
70
|
+
artifacts: [],
|
|
71
|
+
logs: [],
|
|
72
|
+
error: fallbackError || "assistant returned invalid payload",
|
|
73
|
+
metrics: {},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
ok: parsed.ok !== false,
|
|
79
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : "",
|
|
80
|
+
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
81
|
+
logs: Array.isArray(parsed.logs) ? parsed.logs : [],
|
|
82
|
+
error: typeof parsed.error === "string" ? parsed.error : "",
|
|
83
|
+
metrics: parsed.metrics && typeof parsed.metrics === "object" ? parsed.metrics : {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildAssistantSystemPrompt(taskInput) {
|
|
88
|
+
return [
|
|
89
|
+
"You are ufoo-assistant-agent, a private helper for ufoo-agent.",
|
|
90
|
+
"You are NOT exposed on the event bus.",
|
|
91
|
+
"Execute the requested task using local project context and shell/tool access as needed.",
|
|
92
|
+
"Return ONLY JSON that matches schema: {ok, summary, artifacts, logs, error, metrics}.",
|
|
93
|
+
"Rules:",
|
|
94
|
+
"- summary: concise factual result for ufoo-agent to consume.",
|
|
95
|
+
"- artifacts: key files/commands/findings (short strings).",
|
|
96
|
+
"- logs: optional concise trace points.",
|
|
97
|
+
"- error: non-empty only when ok=false.",
|
|
98
|
+
"- Do not include markdown or prose outside JSON.",
|
|
99
|
+
"",
|
|
100
|
+
"Task input:",
|
|
101
|
+
JSON.stringify(taskInput),
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getAssistantStatePaths(projectRoot) {
|
|
106
|
+
const dir = getUfooPaths(projectRoot).agentDir;
|
|
107
|
+
return {
|
|
108
|
+
sessionDir: path.join(dir, "sessions"),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getAssistantSessionStateFile(projectRoot, engine = "assistant") {
|
|
113
|
+
const { sessionDir } = getAssistantStatePaths(projectRoot);
|
|
114
|
+
return path.join(sessionDir, `ufoo-assistant-${engine}.json`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function loadAssistantState(projectRoot, engine = "assistant") {
|
|
118
|
+
const file = getAssistantSessionStateFile(projectRoot, engine);
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function saveAssistantState(projectRoot, engine = "assistant", state = {}) {
|
|
127
|
+
const { sessionDir } = getAssistantStatePaths(projectRoot);
|
|
128
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
129
|
+
const file = getAssistantSessionStateFile(projectRoot, engine);
|
|
130
|
+
fs.writeFileSync(file, JSON.stringify(state, null, 2));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isSessionError(errorText = "") {
|
|
134
|
+
const text = String(errorText || "").toLowerCase();
|
|
135
|
+
return text.includes("session id")
|
|
136
|
+
|| text.includes("session-id")
|
|
137
|
+
|| text.includes("already in use");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runAssistantAgentTask(payload = {}) {
|
|
141
|
+
const taskInput = parseTaskPayload(payload);
|
|
142
|
+
const startedAt = Date.now();
|
|
143
|
+
|
|
144
|
+
if (!taskInput.task) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
summary: "",
|
|
148
|
+
artifacts: [],
|
|
149
|
+
logs: [],
|
|
150
|
+
error: "missing task",
|
|
151
|
+
metrics: { duration_ms: Date.now() - startedAt },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const systemPrompt = buildAssistantSystemPrompt(taskInput);
|
|
156
|
+
const engine = resolveAssistantEngine({
|
|
157
|
+
projectRoot: taskInput.projectRoot,
|
|
158
|
+
requestedProvider: taskInput.provider,
|
|
159
|
+
requestedModel: taskInput.model,
|
|
160
|
+
fallbackProvider: taskInput.fallbackProvider,
|
|
161
|
+
});
|
|
162
|
+
const assistantState = loadAssistantState(taskInput.projectRoot, engine.engine);
|
|
163
|
+
|
|
164
|
+
let cliRes = null;
|
|
165
|
+
if (engine.kind === "external") {
|
|
166
|
+
cliRes = await runExternalAssistantEngine({
|
|
167
|
+
engine,
|
|
168
|
+
timeoutMs: taskInput.timeoutMs,
|
|
169
|
+
payload: {
|
|
170
|
+
request_type: "assistant_task",
|
|
171
|
+
schema_version: 1,
|
|
172
|
+
engine: engine.engine,
|
|
173
|
+
project_root: taskInput.projectRoot,
|
|
174
|
+
task: taskInput.task,
|
|
175
|
+
kind: taskInput.kind,
|
|
176
|
+
context: taskInput.context,
|
|
177
|
+
expect: taskInput.expect,
|
|
178
|
+
model: engine.model || "",
|
|
179
|
+
session_id: assistantState && typeof assistantState.sessionId === "string" ? assistantState.sessionId : "",
|
|
180
|
+
timeout_ms: taskInput.timeoutMs,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
const runCli = async (sessionId) => runCliAgent({
|
|
185
|
+
provider: engine.provider,
|
|
186
|
+
model: engine.model,
|
|
187
|
+
prompt: taskInput.task,
|
|
188
|
+
systemPrompt,
|
|
189
|
+
jsonSchema: ASSISTANT_JSON_SCHEMA,
|
|
190
|
+
disableSession: false,
|
|
191
|
+
sessionId,
|
|
192
|
+
cwd: taskInput.projectRoot,
|
|
193
|
+
timeoutMs: taskInput.timeoutMs,
|
|
194
|
+
sandbox: taskInput.kind === "explore" ? "read-only" : "workspace-write",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const preferredSession = assistantState && typeof assistantState.sessionId === "string"
|
|
198
|
+
? assistantState.sessionId
|
|
199
|
+
: undefined;
|
|
200
|
+
cliRes = await runCli(preferredSession);
|
|
201
|
+
if (!cliRes.ok && preferredSession && isSessionError(cliRes.error)) {
|
|
202
|
+
cliRes = await runCli(undefined);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!cliRes || cliRes.ok === false) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
summary: "",
|
|
210
|
+
artifacts: [],
|
|
211
|
+
logs: [],
|
|
212
|
+
error: (cliRes && cliRes.error) || "assistant cli failed",
|
|
213
|
+
metrics: { duration_ms: Date.now() - startedAt },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let result;
|
|
218
|
+
if (engine.kind === "external") {
|
|
219
|
+
result = normalizeAssistantPayload(cliRes);
|
|
220
|
+
} else {
|
|
221
|
+
const normalized = normalizeCliOutput(cliRes.output);
|
|
222
|
+
let parsed;
|
|
223
|
+
try {
|
|
224
|
+
parsed = JSON.parse(normalized);
|
|
225
|
+
} catch {
|
|
226
|
+
parsed = normalized;
|
|
227
|
+
}
|
|
228
|
+
result = normalizeAssistantPayload(parsed);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
result.metrics = {
|
|
232
|
+
...result.metrics,
|
|
233
|
+
duration_ms: Date.now() - startedAt,
|
|
234
|
+
};
|
|
235
|
+
if (!result.ok && !result.error) {
|
|
236
|
+
result.error = "assistant task failed";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
saveAssistantState(taskInput.projectRoot, engine.engine, {
|
|
240
|
+
engine: engine.engine,
|
|
241
|
+
provider: engine.provider || "",
|
|
242
|
+
model: engine.model || "",
|
|
243
|
+
sessionId: cliRes && typeof cliRes.sessionId === "string" ? cliRes.sessionId : "",
|
|
244
|
+
updated_at: new Date().toISOString(),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
runAssistantAgentTask,
|
|
252
|
+
parseTaskPayload,
|
|
253
|
+
normalizeAssistantPayload,
|
|
254
|
+
buildAssistantSystemPrompt,
|
|
255
|
+
getAssistantSessionStateFile,
|
|
256
|
+
loadAssistantState,
|
|
257
|
+
saveAssistantState,
|
|
258
|
+
isSessionError,
|
|
259
|
+
ASSISTANT_JSON_SCHEMA,
|
|
260
|
+
};
|