u-foo 1.2.12 → 1.2.14
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/bin/ufoo.js +149 -0
- package/modules/online/SKILLS/ufoo-online/SKILL.md +2 -2
- package/package.json +1 -1
- package/scripts/postinstall.js +32 -14
- package/src/agent/notifier.js +5 -1
- package/src/agent/ufooAgent.js +1 -1
- package/src/bus/index.js +10 -3
- package/src/bus/inject.js +6 -3
- package/src/bus/subscriber.js +15 -2
- package/src/chat/commandExecutor.js +138 -10
- package/src/chat/commands.js +2 -1
- package/src/chat/daemonMessageRouter.js +40 -0
- package/src/chat/index.js +10 -29
- package/src/cli/onlineCoreCommands.js +11 -11
- package/src/cli.js +43 -4
- package/src/code/tui.js +53 -29
- package/src/daemon/cronOps.js +362 -29
- package/src/daemon/index.js +64 -0
- package/src/daemon/status.js +3 -0
- package/src/online/bridge.js +1 -1
- package/src/online/client.js +1 -1
- package/src/shared/eventContract.js +1 -0
package/src/chat/index.js
CHANGED
|
@@ -34,7 +34,6 @@ const { createInputListenerController } = require("./inputListenerController");
|
|
|
34
34
|
const { createDaemonMessageRouter } = require("./daemonMessageRouter");
|
|
35
35
|
const { createChatLogController } = require("./chatLogController");
|
|
36
36
|
const { createPasteController } = require("./pasteController");
|
|
37
|
-
const { createCronScheduler } = require("./cronScheduler");
|
|
38
37
|
const { createAgentViewController } = require("./agentViewController");
|
|
39
38
|
const { createSettingsController } = require("./settingsController");
|
|
40
39
|
const { createChatLayout } = require("./layout");
|
|
@@ -87,12 +86,7 @@ async function runChat(projectRoot) {
|
|
|
87
86
|
let agentProvider = config.agentProvider;
|
|
88
87
|
let assistantEngine = normalizeAssistantEngine(config.assistantEngine);
|
|
89
88
|
let autoResume = config.autoResume !== false;
|
|
90
|
-
let
|
|
91
|
-
addTask: () => null,
|
|
92
|
-
listTasks: () => [],
|
|
93
|
-
stopTask: () => false,
|
|
94
|
-
stopAll: () => 0,
|
|
95
|
-
};
|
|
89
|
+
let cronTasks = [];
|
|
96
90
|
|
|
97
91
|
// Dynamic input height settings
|
|
98
92
|
// Layout: topLine(1) + content + bottomLine(1) + dashboard(1)
|
|
@@ -319,7 +313,6 @@ async function runChat(projectRoot) {
|
|
|
319
313
|
if (daemonCoordinator) {
|
|
320
314
|
daemonCoordinator.markExit();
|
|
321
315
|
}
|
|
322
|
-
cronScheduler.stopAll();
|
|
323
316
|
exitAgentView();
|
|
324
317
|
if (screen && screen.program && typeof screen.program.decrst === "function") {
|
|
325
318
|
screen.program.decrst(2004);
|
|
@@ -724,21 +717,6 @@ async function runChat(projectRoot) {
|
|
|
724
717
|
daemonCoordinator.send(req);
|
|
725
718
|
}
|
|
726
719
|
|
|
727
|
-
cronScheduler = createCronScheduler({
|
|
728
|
-
dispatch: ({ taskId, target, message }) => {
|
|
729
|
-
send({
|
|
730
|
-
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
731
|
-
target,
|
|
732
|
-
message,
|
|
733
|
-
});
|
|
734
|
-
queueStatusLine(`cron:${taskId} -> ${target}`);
|
|
735
|
-
},
|
|
736
|
-
onChange: () => {
|
|
737
|
-
renderDashboard();
|
|
738
|
-
screen.render();
|
|
739
|
-
},
|
|
740
|
-
});
|
|
741
|
-
|
|
742
720
|
function updatePromptBox() {
|
|
743
721
|
if (targetAgent) {
|
|
744
722
|
const label = getAgentLabel(targetAgent);
|
|
@@ -907,7 +885,7 @@ async function runChat(projectRoot) {
|
|
|
907
885
|
selectedProviderIndex,
|
|
908
886
|
selectedAssistantIndex,
|
|
909
887
|
selectedResumeIndex,
|
|
910
|
-
cronTasks
|
|
888
|
+
cronTasks,
|
|
911
889
|
providerOptions,
|
|
912
890
|
assistantOptions,
|
|
913
891
|
resumeOptions,
|
|
@@ -923,6 +901,7 @@ async function runChat(projectRoot) {
|
|
|
923
901
|
reportPendingTotal = Number.isFinite(status?.reports?.pending_total)
|
|
924
902
|
? status.reports.pending_total
|
|
925
903
|
: 0;
|
|
904
|
+
cronTasks = Array.isArray(status?.cron?.tasks) ? status.cron.tasks : [];
|
|
926
905
|
const metaList = Array.isArray(status.active_meta) ? status.active_meta : [];
|
|
927
906
|
let fallbackMap = null;
|
|
928
907
|
if (metaList.length === 0 && activeAgents.length > 0) {
|
|
@@ -1018,7 +997,7 @@ async function runChat(projectRoot) {
|
|
|
1018
997
|
agentProvider: { get: () => agentProvider },
|
|
1019
998
|
assistantEngine: { get: () => assistantEngine },
|
|
1020
999
|
autoResume: { get: () => autoResume },
|
|
1021
|
-
cronTasks: { get: () =>
|
|
1000
|
+
cronTasks: { get: () => cronTasks },
|
|
1022
1001
|
providerOptions: { get: () => providerOptions },
|
|
1023
1002
|
assistantOptions: { get: () => assistantOptions },
|
|
1024
1003
|
resumeOptions: { get: () => resumeOptions },
|
|
@@ -1225,10 +1204,12 @@ async function runChat(projectRoot) {
|
|
|
1225
1204
|
restartDaemon,
|
|
1226
1205
|
send,
|
|
1227
1206
|
requestStatus,
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1207
|
+
requestCron: (payload = {}) => {
|
|
1208
|
+
send({
|
|
1209
|
+
type: IPC_REQUEST_TYPES.CRON,
|
|
1210
|
+
...payload,
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1232
1213
|
activateAgent: async (target) => {
|
|
1233
1214
|
const activator = new AgentActivator(projectRoot);
|
|
1234
1215
|
await activator.activate(target);
|
|
@@ -65,7 +65,7 @@ async function runOnlineToken(subscriber, opts = {}) {
|
|
|
65
65
|
const { generateToken, setToken, defaultTokensPath } = require("../online/tokens");
|
|
66
66
|
const filePath = opts.file || defaultTokensPath();
|
|
67
67
|
const token = generateToken();
|
|
68
|
-
const entry = setToken(filePath, subscriber, token, opts.server || "", {
|
|
68
|
+
const entry = setToken(filePath, subscriber, token, opts.server || "https://online.ufoo.dev", {
|
|
69
69
|
nickname: opts.nickname || "",
|
|
70
70
|
});
|
|
71
71
|
console.log(JSON.stringify({
|
|
@@ -79,7 +79,7 @@ async function runOnlineToken(subscriber, opts = {}) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
async function runOnlineRoom(action, opts = {}, onlineAuthHeaders) {
|
|
82
|
-
const base = opts.server || "
|
|
82
|
+
const base = opts.server || "https://online.ufoo.dev";
|
|
83
83
|
const endpoint = `${base.replace(/\/$/, "")}/ufoo/online/rooms`;
|
|
84
84
|
const authHeaders = buildAuthHeaders(onlineAuthHeaders, {
|
|
85
85
|
authToken: opts.authToken,
|
|
@@ -116,7 +116,7 @@ async function runOnlineRoom(action, opts = {}, onlineAuthHeaders) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
async function runOnlineChannel(action, opts = {}, onlineAuthHeaders) {
|
|
119
|
-
const base = opts.server || "
|
|
119
|
+
const base = opts.server || "https://online.ufoo.dev";
|
|
120
120
|
const endpoint = `${base.replace(/\/$/, "")}/ufoo/online/channels`;
|
|
121
121
|
const authHeaders = buildAuthHeaders(onlineAuthHeaders, {
|
|
122
122
|
authToken: opts.authToken,
|
|
@@ -160,7 +160,7 @@ async function runOnlineConnect(opts = {}) {
|
|
|
160
160
|
projectRoot: opts.projectRoot || process.cwd(),
|
|
161
161
|
nickname: opts.nickname,
|
|
162
162
|
subscriberId: opts.subscriber || "",
|
|
163
|
-
url: opts.url || "
|
|
163
|
+
url: opts.url || "wss://online.ufoo.dev/ufoo/online",
|
|
164
164
|
token: opts.token || "",
|
|
165
165
|
tokenHash: opts.tokenHash || "",
|
|
166
166
|
tokenFile: opts.tokenFile || "",
|
|
@@ -220,12 +220,12 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
|
|
|
220
220
|
case "token":
|
|
221
221
|
return runOnlineToken(payload.subscriber, {
|
|
222
222
|
nickname: opts.nickname || "",
|
|
223
|
-
server: opts.server || "",
|
|
223
|
+
server: opts.server || "https://online.ufoo.dev",
|
|
224
224
|
file: opts.file || "",
|
|
225
225
|
});
|
|
226
226
|
case "room":
|
|
227
227
|
return runOnlineRoom(payload.action, {
|
|
228
|
-
server: opts.server || "
|
|
228
|
+
server: opts.server || "https://online.ufoo.dev",
|
|
229
229
|
authToken: opts.authToken || "",
|
|
230
230
|
tokenFile: opts.tokenFile || "",
|
|
231
231
|
subscriber: opts.subscriber || "",
|
|
@@ -236,7 +236,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
|
|
|
236
236
|
}, onlineAuthHeaders);
|
|
237
237
|
case "channel":
|
|
238
238
|
return runOnlineChannel(payload.action, {
|
|
239
|
-
server: opts.server || "
|
|
239
|
+
server: opts.server || "https://online.ufoo.dev",
|
|
240
240
|
authToken: opts.authToken || "",
|
|
241
241
|
tokenFile: opts.tokenFile || "",
|
|
242
242
|
subscriber: opts.subscriber || "",
|
|
@@ -297,14 +297,14 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
|
|
|
297
297
|
const subscriber = argv[1];
|
|
298
298
|
return runOnlineToken(subscriber, {
|
|
299
299
|
nickname: getFallbackOpt(argv, "--nickname"),
|
|
300
|
-
server: getFallbackOpt(argv, "--server"),
|
|
300
|
+
server: getFallbackOpt(argv, "--server") || "https://online.ufoo.dev",
|
|
301
301
|
file: getFallbackOpt(argv, "--file"),
|
|
302
302
|
});
|
|
303
303
|
}
|
|
304
304
|
case "room": {
|
|
305
305
|
const action = argv[1] || "";
|
|
306
306
|
return runOnlineRoom(action, {
|
|
307
|
-
server: getFallbackOpt(argv, "--server") || "
|
|
307
|
+
server: getFallbackOpt(argv, "--server") || "https://online.ufoo.dev",
|
|
308
308
|
authToken: getFallbackOpt(argv, "--auth-token"),
|
|
309
309
|
tokenFile: getFallbackOpt(argv, "--token-file"),
|
|
310
310
|
subscriber: getFallbackOpt(argv, "--subscriber"),
|
|
@@ -318,7 +318,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
|
|
|
318
318
|
const action = argv[1] || "";
|
|
319
319
|
const defaultChannelType = options.defaultChannelType || "public";
|
|
320
320
|
return runOnlineChannel(action, {
|
|
321
|
-
server: getFallbackOpt(argv, "--server") || "
|
|
321
|
+
server: getFallbackOpt(argv, "--server") || "https://online.ufoo.dev",
|
|
322
322
|
authToken: getFallbackOpt(argv, "--auth-token"),
|
|
323
323
|
tokenFile: getFallbackOpt(argv, "--token-file"),
|
|
324
324
|
subscriber: getFallbackOpt(argv, "--subscriber"),
|
|
@@ -339,7 +339,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
|
|
|
339
339
|
projectRoot: options.projectRoot || process.cwd(),
|
|
340
340
|
nickname: getFallbackOpt(argv, "--nickname"),
|
|
341
341
|
subscriber: getFallbackOpt(argv, "--subscriber"),
|
|
342
|
-
url: getFallbackOpt(argv, "--url") || "
|
|
342
|
+
url: getFallbackOpt(argv, "--url") || "wss://online.ufoo.dev/ufoo/online",
|
|
343
343
|
token: getFallbackOpt(argv, "--token"),
|
|
344
344
|
tokenHash: getFallbackOpt(argv, "--token-hash"),
|
|
345
345
|
tokenFile: getFallbackOpt(argv, "--token-file"),
|
package/src/cli.js
CHANGED
|
@@ -247,6 +247,45 @@ async function runCli(argv) {
|
|
|
247
247
|
const repoRoot = getPackageRoot();
|
|
248
248
|
run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "chat"]);
|
|
249
249
|
});
|
|
250
|
+
program
|
|
251
|
+
.command("launch")
|
|
252
|
+
.description("Launch an agent (ucode, uclaude, ucodex)")
|
|
253
|
+
.argument("<agent>", "Agent type: ucode|uclaude|ucodex|claude|codex")
|
|
254
|
+
.argument("[nickname]", "Optional nickname for the agent")
|
|
255
|
+
.action(async (agent, nickname) => {
|
|
256
|
+
try {
|
|
257
|
+
const projectRoot = process.cwd();
|
|
258
|
+
await ensureDaemonRunning(projectRoot);
|
|
259
|
+
|
|
260
|
+
// Normalize agent type
|
|
261
|
+
const agentLower = agent.toLowerCase();
|
|
262
|
+
let normalizedAgent = "";
|
|
263
|
+
if (agentLower === "ucode" || agentLower === "ufoo-code" || agentLower === "ufoo") {
|
|
264
|
+
normalizedAgent = "ucode";
|
|
265
|
+
} else if (agentLower === "uclaude" || agentLower === "claude-code" || agentLower === "claude") {
|
|
266
|
+
normalizedAgent = "claude";
|
|
267
|
+
} else if (agentLower === "ucodex" || agentLower === "codex" || agentLower === "openai") {
|
|
268
|
+
normalizedAgent = "codex";
|
|
269
|
+
} else {
|
|
270
|
+
console.error(`Unknown agent type: ${agent}`);
|
|
271
|
+
console.error("Valid types: ucode, uclaude, ucodex, claude, codex");
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const resp = await sendDaemonRequest(projectRoot, {
|
|
277
|
+
type: "launch_agent",
|
|
278
|
+
agent: normalizedAgent,
|
|
279
|
+
nickname: nickname || "",
|
|
280
|
+
count: 1,
|
|
281
|
+
});
|
|
282
|
+
const reply = resp?.data?.reply || `Launching ${normalizedAgent} agent...`;
|
|
283
|
+
console.log(reply);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(err.message || String(err));
|
|
286
|
+
process.exitCode = 1;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
250
289
|
program
|
|
251
290
|
.command("resume")
|
|
252
291
|
.description("Resume agent sessions (optional nickname)")
|
|
@@ -550,7 +589,7 @@ async function runCli(argv) {
|
|
|
550
589
|
.description("Generate and store a ufoo-online token")
|
|
551
590
|
.argument("<subscriber>", "Subscriber ID (e.g., claude-code:abc123)")
|
|
552
591
|
.option("--nickname <name>", "Nickname for this agent")
|
|
553
|
-
.option("--server <url>", "Online server URL")
|
|
592
|
+
.option("--server <url>", "Online server URL", "https://online.ufoo.dev")
|
|
554
593
|
.option("--file <path>", "Tokens file path")
|
|
555
594
|
.action(async (subscriber, opts) => {
|
|
556
595
|
try {
|
|
@@ -569,7 +608,7 @@ async function runCli(argv) {
|
|
|
569
608
|
.command("room")
|
|
570
609
|
.description("Manage online rooms (HTTP)")
|
|
571
610
|
.argument("<action>", "create|list")
|
|
572
|
-
.option("--server <url>", "Online server base URL (
|
|
611
|
+
.option("--server <url>", "Online server base URL (default: https://online.ufoo.dev)")
|
|
573
612
|
.option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
|
|
574
613
|
.option("--token-file <path>", "Token file path for auth lookup")
|
|
575
614
|
.option("--subscriber <id>", "Subscriber ID to resolve token")
|
|
@@ -594,7 +633,7 @@ async function runCli(argv) {
|
|
|
594
633
|
.command("channel")
|
|
595
634
|
.description("Manage online channels (HTTP)")
|
|
596
635
|
.argument("<action>", "create|list")
|
|
597
|
-
.option("--server <url>", "Online server base URL (
|
|
636
|
+
.option("--server <url>", "Online server base URL (default: https://online.ufoo.dev)")
|
|
598
637
|
.option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
|
|
599
638
|
.option("--token-file <path>", "Token file path for auth lookup")
|
|
600
639
|
.option("--subscriber <id>", "Subscriber ID to resolve token")
|
|
@@ -618,7 +657,7 @@ async function runCli(argv) {
|
|
|
618
657
|
.command("connect")
|
|
619
658
|
.description("Connect to ufoo-online relay (long-running)")
|
|
620
659
|
.requiredOption("--nickname <name>", "Agent nickname")
|
|
621
|
-
.option("--url <url>", "WebSocket URL", "
|
|
660
|
+
.option("--url <url>", "WebSocket URL", "wss://online.ufoo.dev/ufoo/online")
|
|
622
661
|
.option("--subscriber <id>", "Subscriber ID (auto-generated if omitted)")
|
|
623
662
|
.option("--token <tok>", "Auth token")
|
|
624
663
|
.option("--token-hash <hash>", "Auth token hash")
|
package/src/code/tui.js
CHANGED
|
@@ -960,39 +960,63 @@ function runUcodeTui({
|
|
|
960
960
|
autoBusError = "";
|
|
961
961
|
return;
|
|
962
962
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
963
|
+
|
|
964
|
+
// Set pending state for autoBus tasks
|
|
965
|
+
const abortController = new AbortController();
|
|
966
|
+
pendingTask = {
|
|
967
|
+
abortController,
|
|
968
|
+
startedAt: Date.now(),
|
|
969
|
+
};
|
|
970
|
+
updateStatus("Processing bus messages...", "thinking", {
|
|
971
|
+
showTimer: true,
|
|
972
|
+
startedAt: pendingTask.startedAt,
|
|
972
973
|
});
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
974
|
+
|
|
975
|
+
try {
|
|
976
|
+
const ubusResult = await runUbusCommand(state, {
|
|
977
|
+
workspaceRoot,
|
|
978
|
+
subscriberId: autoBusSubscriberId,
|
|
979
|
+
onMessageReceived: (msg) => {
|
|
980
|
+
// Display the incoming message immediately
|
|
981
|
+
const { extractAgentNickname } = require("./agent");
|
|
982
|
+
const nickname = extractAgentNickname(msg.from) || msg.from;
|
|
983
|
+
logText(`${nickname}: ${msg.task}`);
|
|
984
|
+
// Update status to show we're working on this specific task
|
|
985
|
+
updateStatus("Working on task...", "thinking", {
|
|
986
|
+
showTimer: true,
|
|
987
|
+
startedAt: pendingTask.startedAt,
|
|
988
|
+
});
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
if (!ubusResult.ok) {
|
|
993
|
+
const nextError = String(ubusResult.error || "ubus failed");
|
|
994
|
+
if (nextError !== autoBusError) {
|
|
995
|
+
autoBusError = nextError;
|
|
996
|
+
logText(`Error: ${nextError}`);
|
|
990
997
|
}
|
|
998
|
+
return;
|
|
991
999
|
}
|
|
992
|
-
|
|
993
|
-
if (
|
|
994
|
-
|
|
1000
|
+
autoBusError = "";
|
|
1001
|
+
if (ubusResult.handled > 0) {
|
|
1002
|
+
// Display only the replies (tasks were already shown via onMessageReceived)
|
|
1003
|
+
if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
|
|
1004
|
+
const { extractAgentNickname } = require("./agent");
|
|
1005
|
+
for (const exchange of ubusResult.messageExchanges) {
|
|
1006
|
+
const nickname = extractAgentNickname(exchange.from) || exchange.from;
|
|
1007
|
+
// Only show the reply since task was already displayed
|
|
1008
|
+
logText(`@${nickname} ${exchange.reply}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const persisted = persistSessionState(state);
|
|
1012
|
+
if (!persisted || persisted.ok === false) {
|
|
1013
|
+
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1014
|
+
}
|
|
995
1015
|
}
|
|
1016
|
+
} finally {
|
|
1017
|
+
// Clear pending state
|
|
1018
|
+
pendingTask = null;
|
|
1019
|
+
updateStatus("", "none");
|
|
996
1020
|
}
|
|
997
1021
|
};
|
|
998
1022
|
|