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.
- package/README.md +247 -23
- 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 +168 -28
- 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 +157 -0
- package/src/chat/index.js +938 -2910
- 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 +133 -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 +1587 -0
- package/src/config.js +50 -2
- 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 +662 -489
- 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,58 @@
|
|
|
1
|
+
const { runAssistantAgentTask } = require("./agent");
|
|
2
|
+
|
|
3
|
+
function readStdin() {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
let data = "";
|
|
6
|
+
process.stdin.setEncoding("utf8");
|
|
7
|
+
process.stdin.on("data", (chunk) => {
|
|
8
|
+
data += chunk;
|
|
9
|
+
});
|
|
10
|
+
process.stdin.on("end", () => resolve(data));
|
|
11
|
+
process.stdin.resume();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function runAssistantStdio() {
|
|
16
|
+
const startedAt = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
const input = await readStdin();
|
|
19
|
+
const line = String(input || "")
|
|
20
|
+
.split(/\r?\n/)
|
|
21
|
+
.map((part) => part.trim())
|
|
22
|
+
.find(Boolean);
|
|
23
|
+
|
|
24
|
+
if (!line) {
|
|
25
|
+
process.stdout.write(
|
|
26
|
+
`${JSON.stringify({
|
|
27
|
+
ok: false,
|
|
28
|
+
summary: "",
|
|
29
|
+
artifacts: [],
|
|
30
|
+
logs: [],
|
|
31
|
+
error: "missing request payload",
|
|
32
|
+
metrics: { duration_ms: Date.now() - startedAt },
|
|
33
|
+
})}\n`
|
|
34
|
+
);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const payload = JSON.parse(line);
|
|
40
|
+
const result = await runAssistantAgentTask(payload);
|
|
41
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
42
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stdout.write(
|
|
45
|
+
`${JSON.stringify({
|
|
46
|
+
ok: false,
|
|
47
|
+
summary: "",
|
|
48
|
+
artifacts: [],
|
|
49
|
+
logs: [],
|
|
50
|
+
error: err && err.message ? err.message : "assistant stdio failed",
|
|
51
|
+
metrics: { duration_ms: Date.now() - startedAt },
|
|
52
|
+
})}\n`
|
|
53
|
+
);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { runAssistantStdio };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
const { runCliAgent } = require("../agent/cliRunner");
|
|
2
|
+
const { normalizeCliOutput } = require("../agent/normalizeOutput");
|
|
3
|
+
const { loadConfig } = require("../config");
|
|
4
|
+
|
|
5
|
+
function normalizeProvider(value, fallback = "codex-cli") {
|
|
6
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
7
|
+
if (!raw) return fallback;
|
|
8
|
+
if (raw === "codex" || raw === "codex-cli") return "codex-cli";
|
|
9
|
+
if (raw === "claude" || raw === "claude-cli") return "claude-cli";
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseAssistantTaskArgs(argv = []) {
|
|
14
|
+
const options = {
|
|
15
|
+
assistantTask: false,
|
|
16
|
+
json: false,
|
|
17
|
+
cwd: "",
|
|
18
|
+
model: "",
|
|
19
|
+
sessionId: "",
|
|
20
|
+
provider: "",
|
|
21
|
+
kind: "mixed",
|
|
22
|
+
context: "",
|
|
23
|
+
expect: "",
|
|
24
|
+
task: "",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
28
|
+
const rest = [];
|
|
29
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg === "--assistant-task") {
|
|
32
|
+
options.assistantTask = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === "--json") {
|
|
36
|
+
options.json = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === "--cwd") {
|
|
40
|
+
options.cwd = args[++i] || "";
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--model") {
|
|
44
|
+
options.model = args[++i] || "";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === "--session-id") {
|
|
48
|
+
options.sessionId = args[++i] || "";
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === "--provider") {
|
|
52
|
+
options.provider = args[++i] || "";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === "--kind") {
|
|
56
|
+
options.kind = args[++i] || "mixed";
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === "--context") {
|
|
60
|
+
options.context = args[++i] || "";
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === "--expect") {
|
|
64
|
+
options.expect = args[++i] || "";
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
rest.push(arg);
|
|
68
|
+
}
|
|
69
|
+
options.task = rest.join(" ").trim();
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildPrompt({ kind = "mixed", context = "", task = "", expect = "" } = {}) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(`Task kind: ${kind || "mixed"}`);
|
|
76
|
+
if (context) {
|
|
77
|
+
lines.push("Context:");
|
|
78
|
+
lines.push(context);
|
|
79
|
+
}
|
|
80
|
+
lines.push("Task:");
|
|
81
|
+
lines.push(task || "");
|
|
82
|
+
if (expect) {
|
|
83
|
+
lines.push("Expected result:");
|
|
84
|
+
lines.push(expect);
|
|
85
|
+
}
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildSystemPrompt() {
|
|
90
|
+
return [
|
|
91
|
+
"You are ufoo-engine, a self-hosted assistant core.",
|
|
92
|
+
"Return ONLY valid JSON.",
|
|
93
|
+
"Schema:",
|
|
94
|
+
"{",
|
|
95
|
+
' "ok": true|false,',
|
|
96
|
+
' "summary": "string",',
|
|
97
|
+
' "artifacts": ["string"],',
|
|
98
|
+
' "logs": ["string"],',
|
|
99
|
+
' "error": "string",',
|
|
100
|
+
' "metrics": {"key":"value"}',
|
|
101
|
+
"}",
|
|
102
|
+
"Rules:",
|
|
103
|
+
"- summary should be concise and actionable.",
|
|
104
|
+
"- error must be non-empty only when ok=false.",
|
|
105
|
+
"- Do not output markdown wrappers.",
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeEngineResult(parsed, fallbackError = "") {
|
|
110
|
+
if (!parsed || typeof parsed !== "object") {
|
|
111
|
+
const text = String(parsed || "").trim();
|
|
112
|
+
if (text) {
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
summary: text,
|
|
116
|
+
artifacts: [],
|
|
117
|
+
logs: [],
|
|
118
|
+
error: "",
|
|
119
|
+
metrics: {},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
summary: "",
|
|
125
|
+
artifacts: [],
|
|
126
|
+
logs: [],
|
|
127
|
+
error: fallbackError || "ufoo-engine invalid response",
|
|
128
|
+
metrics: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
ok: parsed.ok !== false,
|
|
134
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : "",
|
|
135
|
+
artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
|
|
136
|
+
logs: Array.isArray(parsed.logs) ? parsed.logs : [],
|
|
137
|
+
error: typeof parsed.error === "string" ? parsed.error : "",
|
|
138
|
+
metrics: parsed.metrics && typeof parsed.metrics === "object" ? parsed.metrics : {},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isSessionError(errorText = "") {
|
|
143
|
+
const text = String(errorText || "").toLowerCase();
|
|
144
|
+
return text.includes("session id")
|
|
145
|
+
|| text.includes("session-id")
|
|
146
|
+
|| text.includes("already in use");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function parseStdinPayload(stdinText = "") {
|
|
150
|
+
const line = String(stdinText || "")
|
|
151
|
+
.split(/\r?\n/)
|
|
152
|
+
.map((part) => part.trim())
|
|
153
|
+
.find(Boolean);
|
|
154
|
+
if (!line) return null;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(line);
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function runEngineTask(taskInput, deps = {}) {
|
|
163
|
+
const {
|
|
164
|
+
runCliAgentImpl = runCliAgent,
|
|
165
|
+
normalizeCliOutputImpl = normalizeCliOutput,
|
|
166
|
+
loadConfigImpl = loadConfig,
|
|
167
|
+
env = process.env,
|
|
168
|
+
cwd = process.cwd(),
|
|
169
|
+
} = deps;
|
|
170
|
+
|
|
171
|
+
const projectRoot = taskInput.cwd || taskInput.projectRoot || cwd;
|
|
172
|
+
const config = loadConfigImpl(projectRoot);
|
|
173
|
+
const provider = normalizeProvider(
|
|
174
|
+
taskInput.provider || env.UFOO_UFOO_ENGINE_PROVIDER || config.agentProvider,
|
|
175
|
+
"codex-cli"
|
|
176
|
+
);
|
|
177
|
+
const model =
|
|
178
|
+
String(taskInput.model || "").trim()
|
|
179
|
+
|| String(env.UFOO_UFOO_ENGINE_MODEL || "").trim()
|
|
180
|
+
|| String(config.agentModel || "").trim()
|
|
181
|
+
|| (provider === "claude-cli" ? "opus" : "");
|
|
182
|
+
|
|
183
|
+
const systemPrompt = buildSystemPrompt();
|
|
184
|
+
const prompt = buildPrompt({
|
|
185
|
+
kind: taskInput.kind,
|
|
186
|
+
context: taskInput.context,
|
|
187
|
+
task: taskInput.task,
|
|
188
|
+
expect: taskInput.expect,
|
|
189
|
+
});
|
|
190
|
+
const timeoutMs = Number.isFinite(taskInput.timeoutMs) ? taskInput.timeoutMs : 60000;
|
|
191
|
+
|
|
192
|
+
const runOnce = async (sessionId) => runCliAgentImpl({
|
|
193
|
+
provider,
|
|
194
|
+
model,
|
|
195
|
+
prompt,
|
|
196
|
+
systemPrompt,
|
|
197
|
+
sessionId: sessionId || undefined,
|
|
198
|
+
disableSession: false,
|
|
199
|
+
cwd: projectRoot,
|
|
200
|
+
timeoutMs,
|
|
201
|
+
sandbox: taskInput.kind === "explore" ? "read-only" : "workspace-write",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
let cliRes = await runOnce(taskInput.sessionId || "");
|
|
205
|
+
if (!cliRes.ok && taskInput.sessionId && isSessionError(cliRes.error)) {
|
|
206
|
+
cliRes = await runOnce("");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!cliRes.ok) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
summary: "",
|
|
213
|
+
artifacts: [],
|
|
214
|
+
logs: [],
|
|
215
|
+
error: cliRes.error || "ufoo-engine cli failed",
|
|
216
|
+
metrics: {},
|
|
217
|
+
session_id: "",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const normalized = normalizeCliOutputImpl(cliRes.output);
|
|
222
|
+
let parsed;
|
|
223
|
+
try {
|
|
224
|
+
parsed = JSON.parse(normalized);
|
|
225
|
+
} catch {
|
|
226
|
+
parsed = normalized;
|
|
227
|
+
}
|
|
228
|
+
const result = normalizeEngineResult(parsed);
|
|
229
|
+
return {
|
|
230
|
+
...result,
|
|
231
|
+
session_id: cliRes.sessionId || "",
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function runUfooEngineCli({ argv = [], stdinText = "", deps = {} } = {}) {
|
|
236
|
+
const options = parseAssistantTaskArgs(argv);
|
|
237
|
+
|
|
238
|
+
let taskInput;
|
|
239
|
+
if (options.assistantTask) {
|
|
240
|
+
if (!options.task) {
|
|
241
|
+
const error = {
|
|
242
|
+
ok: false,
|
|
243
|
+
summary: "",
|
|
244
|
+
artifacts: [],
|
|
245
|
+
logs: [],
|
|
246
|
+
error: "missing task",
|
|
247
|
+
metrics: {},
|
|
248
|
+
};
|
|
249
|
+
return { exitCode: 1, output: `${JSON.stringify(error)}\n` };
|
|
250
|
+
}
|
|
251
|
+
taskInput = {
|
|
252
|
+
task: options.task,
|
|
253
|
+
kind: options.kind,
|
|
254
|
+
context: options.context,
|
|
255
|
+
expect: options.expect,
|
|
256
|
+
provider: options.provider,
|
|
257
|
+
model: options.model,
|
|
258
|
+
sessionId: options.sessionId,
|
|
259
|
+
cwd: options.cwd,
|
|
260
|
+
timeoutMs: 60000,
|
|
261
|
+
};
|
|
262
|
+
} else {
|
|
263
|
+
const payload = parseStdinPayload(stdinText);
|
|
264
|
+
if (!payload || typeof payload !== "object") {
|
|
265
|
+
const error = {
|
|
266
|
+
ok: false,
|
|
267
|
+
summary: "",
|
|
268
|
+
artifacts: [],
|
|
269
|
+
logs: [],
|
|
270
|
+
error: "missing request payload",
|
|
271
|
+
metrics: {},
|
|
272
|
+
};
|
|
273
|
+
return { exitCode: 1, output: `${JSON.stringify(error)}\n` };
|
|
274
|
+
}
|
|
275
|
+
taskInput = {
|
|
276
|
+
task: typeof payload.task === "string" ? payload.task : "",
|
|
277
|
+
kind: typeof payload.kind === "string" ? payload.kind : "mixed",
|
|
278
|
+
context: typeof payload.context === "string" ? payload.context : "",
|
|
279
|
+
expect: typeof payload.expect === "string" ? payload.expect : "",
|
|
280
|
+
provider: typeof payload.provider === "string" ? payload.provider : "",
|
|
281
|
+
model: typeof payload.model === "string" ? payload.model : "",
|
|
282
|
+
sessionId: typeof payload.session_id === "string" ? payload.session_id : "",
|
|
283
|
+
cwd: typeof payload.project_root === "string" ? payload.project_root : "",
|
|
284
|
+
timeoutMs: Number.isFinite(payload.timeout_ms) ? payload.timeout_ms : 60000,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const result = await runEngineTask(taskInput, deps);
|
|
289
|
+
const output = `${JSON.stringify(result)}\n`;
|
|
290
|
+
return {
|
|
291
|
+
exitCode: result.ok === false ? 1 : 0,
|
|
292
|
+
output,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
normalizeProvider,
|
|
298
|
+
parseAssistantTaskArgs,
|
|
299
|
+
buildPrompt,
|
|
300
|
+
buildSystemPrompt,
|
|
301
|
+
normalizeEngineResult,
|
|
302
|
+
parseStdinPayload,
|
|
303
|
+
runEngineTask,
|
|
304
|
+
runUfooEngineCli,
|
|
305
|
+
isSessionError,
|
|
306
|
+
};
|
package/src/bus/activate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
3
3
|
const { spawn, spawnSync } = require("child_process");
|
|
4
|
+
const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 激活指定 agent 的终端
|
|
@@ -135,21 +136,36 @@ end tell`;
|
|
|
135
136
|
async activate(agentId) {
|
|
136
137
|
const info = this.getAgentInfo(agentId);
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
const activateTerminal = async () => {
|
|
140
|
+
if (info.tty) {
|
|
141
|
+
this.activateTerminalByTty(info.tty);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw new Error("Cannot activate: missing tty or tmux_pane for agent");
|
|
145
|
+
};
|
|
141
146
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
const activateTmux = async () => {
|
|
148
|
+
if (info.tmux_pane) {
|
|
149
|
+
await this.activateTmuxPane(info.tmux_pane);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
throw new Error("Cannot activate: missing tty or tmux_pane for agent");
|
|
153
|
+
};
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
const adapterRouter = createTerminalAdapterRouter({
|
|
156
|
+
activateTerminal,
|
|
157
|
+
activateTmux,
|
|
158
|
+
});
|
|
159
|
+
const adapter = adapterRouter.getAdapter({ launchMode: info.launch_mode, agentId });
|
|
160
|
+
|
|
161
|
+
if (!adapter.capabilities.supportsActivate) {
|
|
162
|
+
if (adapter.capabilities.supportsInternalQueueLoop) {
|
|
163
|
+
throw new Error("Internal mode agents cannot be activated (no terminal window)");
|
|
164
|
+
}
|
|
165
|
+
throw new Error("Cannot activate: missing tty or tmux_pane for agent");
|
|
150
166
|
}
|
|
151
167
|
|
|
152
|
-
|
|
168
|
+
await adapter.activate();
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
171
|
|
package/src/bus/daemon.js
CHANGED
|
@@ -3,6 +3,8 @@ const path = require("path");
|
|
|
3
3
|
const { readJSON, writeJSON, isPidAlive, isAgentPidAlive, ensureDir, safeNameToSubscriber, subscriberToSafeName } = require("./utils");
|
|
4
4
|
const Injector = require("./inject");
|
|
5
5
|
const QueueManager = require("./queue");
|
|
6
|
+
const MessageManager = require("./message");
|
|
7
|
+
const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Bus Daemon - 监控消息并自动注入命令
|
|
@@ -22,6 +24,7 @@ class BusDaemon {
|
|
|
22
24
|
|
|
23
25
|
this.queueManager = new QueueManager(busDir);
|
|
24
26
|
this.injector = new Injector(busDir, agentsFile);
|
|
27
|
+
this.adapterRouter = createTerminalAdapterRouter();
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -198,6 +201,8 @@ class BusDaemon {
|
|
|
198
201
|
return;
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
const busData = readJSON(this.agentsFile) || { agents: {} };
|
|
205
|
+
const messageManager = new MessageManager(this.busDir, busData, this.queueManager);
|
|
201
206
|
const subscribers = fs.readdirSync(queuesDir);
|
|
202
207
|
|
|
203
208
|
for (const safeName of subscribers) {
|
|
@@ -206,6 +211,19 @@ class BusDaemon {
|
|
|
206
211
|
continue;
|
|
207
212
|
}
|
|
208
213
|
|
|
214
|
+
const subscriber = safeNameToSubscriber(safeName);
|
|
215
|
+
const meta = busData.agents?.[subscriber];
|
|
216
|
+
const launchMode = meta?.launch_mode || "";
|
|
217
|
+
// Delivery ownership:
|
|
218
|
+
// - notifier/injector: terminal/tmux
|
|
219
|
+
// - internal queue loop: internal/internal-pty
|
|
220
|
+
// Bus daemon only handles legacy/unknown launch modes.
|
|
221
|
+
const adapter = this.adapterRouter.getAdapter({ launchMode, agentId: subscriber });
|
|
222
|
+
const { supportsNotifierInjector, supportsInternalQueueLoop } = adapter.capabilities;
|
|
223
|
+
if (launchMode && (supportsNotifierInjector || supportsInternalQueueLoop)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
209
227
|
// 获取当前消息数
|
|
210
228
|
let count = 0;
|
|
211
229
|
if (fs.statSync(pendingFile).size > 0) {
|
|
@@ -217,14 +235,85 @@ class BusDaemon {
|
|
|
217
235
|
const lastCount = this.getLastCount(safeName);
|
|
218
236
|
|
|
219
237
|
// 如果有新消息,注入命令
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
const wakePath = path.join(queuesDir, safeName, "wake");
|
|
239
|
+
const wakeActive = fs.existsSync(wakePath);
|
|
240
|
+
|
|
241
|
+
if (count > 0 || wakeActive) {
|
|
222
242
|
const now = new Date().toISOString().split("T")[1].slice(0, 8);
|
|
223
|
-
|
|
243
|
+
const note = wakeActive && count <= lastCount ? " (wake)" : "";
|
|
244
|
+
console.log(`[daemon] ${now} New message for ${subscriber} (${lastCount} -> ${count})${note}`);
|
|
224
245
|
|
|
225
246
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
247
|
+
const agentType = String((meta && meta.agent_type) || "").trim().toLowerCase();
|
|
248
|
+
const isUfooCode = subscriber.startsWith("ufoo-code:")
|
|
249
|
+
|| agentType === "ufoo-code"
|
|
250
|
+
|| agentType === "ucode"
|
|
251
|
+
|| agentType === "ufoo";
|
|
252
|
+
if (isUfooCode) {
|
|
253
|
+
// ufoo-code queue is consumed internally by ucode itself.
|
|
254
|
+
// Bus daemon should not inject any command/text into terminal.
|
|
255
|
+
if (wakeActive) fs.rmSync(wakePath, { force: true });
|
|
256
|
+
this.setLastCount(safeName, count);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const events = this.drainPending(pendingFile);
|
|
261
|
+
const failed = [];
|
|
262
|
+
for (const evt of events) {
|
|
263
|
+
if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
// eslint-disable-next-line no-await-in-loop
|
|
268
|
+
await this.injector.inject(subscriber, String(evt.data.message));
|
|
269
|
+
} catch (err) {
|
|
270
|
+
failed.push(evt);
|
|
271
|
+
try {
|
|
272
|
+
const pub = typeof evt.publisher === "object" && evt.publisher
|
|
273
|
+
? (evt.publisher.subscriber || evt.publisher.nickname || "")
|
|
274
|
+
: (evt.publisher || "");
|
|
275
|
+
if (pub) {
|
|
276
|
+
// eslint-disable-next-line no-await-in-loop
|
|
277
|
+
await messageManager.emit(pub, "delivery", {
|
|
278
|
+
target: subscriber,
|
|
279
|
+
seq: evt.seq,
|
|
280
|
+
status: "error",
|
|
281
|
+
message: `delivery failed to ${meta?.nickname || subscriber}: ${err.message || "inject failed"}`,
|
|
282
|
+
}, subscriber, "status/delivery");
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
// ignore delivery emit errors
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
// Emit delivery status back to publisher (best-effort)
|
|
291
|
+
const pub = typeof evt.publisher === "object" && evt.publisher
|
|
292
|
+
? (evt.publisher.subscriber || evt.publisher.nickname || "")
|
|
293
|
+
: (evt.publisher || "");
|
|
294
|
+
if (pub) {
|
|
295
|
+
// eslint-disable-next-line no-await-in-loop
|
|
296
|
+
await messageManager.emit(pub, "delivery", {
|
|
297
|
+
target: subscriber,
|
|
298
|
+
seq: evt.seq,
|
|
299
|
+
status: "ok",
|
|
300
|
+
message: `delivered to ${meta?.nickname || subscriber}`,
|
|
301
|
+
}, subscriber, "status/delivery");
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
// ignore delivery emit errors
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (failed.length > 0) {
|
|
308
|
+
try {
|
|
309
|
+
const content = failed.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
310
|
+
fs.appendFileSync(pendingFile, content, "utf8");
|
|
311
|
+
} catch {
|
|
312
|
+
// ignore requeue failures
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.log(`[daemon] Delivered ${events.length} message(s) to ${subscriber}`);
|
|
316
|
+
if (wakeActive) fs.rmSync(wakePath, { force: true });
|
|
228
317
|
} catch (err) {
|
|
229
318
|
console.error(`[daemon] Failed to inject: ${err.message}`);
|
|
230
319
|
}
|
|
@@ -235,6 +324,45 @@ class BusDaemon {
|
|
|
235
324
|
}
|
|
236
325
|
}
|
|
237
326
|
|
|
327
|
+
drainPending(pendingFile) {
|
|
328
|
+
if (!fs.existsSync(pendingFile)) return [];
|
|
329
|
+
const processingFile = `${pendingFile}.processing.${process.pid}.${Date.now()}`;
|
|
330
|
+
let content = "";
|
|
331
|
+
let readOk = false;
|
|
332
|
+
try {
|
|
333
|
+
fs.renameSync(pendingFile, processingFile);
|
|
334
|
+
content = fs.readFileSync(processingFile, "utf8");
|
|
335
|
+
readOk = true;
|
|
336
|
+
} catch {
|
|
337
|
+
try {
|
|
338
|
+
if (fs.existsSync(processingFile)) {
|
|
339
|
+
fs.renameSync(processingFile, pendingFile);
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
// ignore rollback errors
|
|
343
|
+
}
|
|
344
|
+
return [];
|
|
345
|
+
} finally {
|
|
346
|
+
if (readOk) {
|
|
347
|
+
try {
|
|
348
|
+
if (fs.existsSync(processingFile)) {
|
|
349
|
+
fs.rmSync(processingFile, { force: true });
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
// ignore cleanup errors
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!content.trim()) return [];
|
|
357
|
+
return content.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
358
|
+
try {
|
|
359
|
+
return JSON.parse(line);
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}).filter(Boolean);
|
|
364
|
+
}
|
|
365
|
+
|
|
238
366
|
/**
|
|
239
367
|
* 获取上次的消息计数
|
|
240
368
|
*/
|