u-foo 1.7.3 → 1.7.5
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/package.json +1 -1
- package/src/agent/notifier.js +7 -5
- package/src/agent/ufooAgent.js +7 -4
- package/src/assistant/engine.js +1 -6
- package/src/bus/daemon.js +64 -1
- package/src/bus/index.js +5 -1
- package/src/bus/message.js +5 -2
- package/src/bus/messageMeta.js +52 -0
- package/src/chat/commandExecutor.js +46 -21
- package/src/chat/commands.js +1 -1
- package/src/chat/cronScheduler.js +37 -6
- package/src/chat/daemonMessageRouter.js +2 -2
- package/src/chat/dashboardKeyController.js +2 -65
- package/src/chat/dashboardView.js +2 -36
- package/src/chat/index.js +4 -41
- package/src/chat/inputSubmitHandler.js +28 -3
- package/src/chat/settingsController.js +0 -28
- package/src/cli/busCoreCommands.js +44 -1
- package/src/daemon/cronOps.js +48 -11
- package/src/daemon/index.js +13 -6
- package/src/daemon/promptRequest.js +20 -3
- package/src/daemon/status.js +2 -0
package/package.json
CHANGED
package/src/agent/notifier.js
CHANGED
|
@@ -7,6 +7,7 @@ const { shakeTerminalByTty } = require("../bus/shake");
|
|
|
7
7
|
const { isITerm2 } = require("../terminal/detect");
|
|
8
8
|
const iterm2 = require("../terminal/iterm2");
|
|
9
9
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
10
|
+
const { INJECTION_MODES, getInjectionModeFromEvent } = require("../bus/messageMeta");
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Agent 消息通知监听器
|
|
@@ -232,11 +233,6 @@ class AgentNotifier {
|
|
|
232
233
|
return 0;
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
const activityState = this.getCurrentActivityState();
|
|
236
|
-
if (this.isBusyState(activityState)) {
|
|
237
|
-
return 0;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
236
|
// Back off on consecutive inject failures to avoid tight retry loop
|
|
241
237
|
if (this.injectFailCount >= this.maxInjectRetries) {
|
|
242
238
|
return 0;
|
|
@@ -251,6 +247,12 @@ class AgentNotifier {
|
|
|
251
247
|
if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
|
|
252
248
|
continue;
|
|
253
249
|
}
|
|
250
|
+
const injectionMode = getInjectionModeFromEvent(evt, INJECTION_MODES.IMMEDIATE);
|
|
251
|
+
const activityState = this.getCurrentActivityState();
|
|
252
|
+
if (injectionMode === INJECTION_MODES.QUEUED && this.isBusyState(activityState)) {
|
|
253
|
+
requeue.push(evt);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
254
256
|
if (consumedOne) {
|
|
255
257
|
requeue.push(evt);
|
|
256
258
|
continue;
|
package/src/agent/ufooAgent.js
CHANGED
|
@@ -274,8 +274,8 @@ function buildSystemPrompt(context) {
|
|
|
274
274
|
"{",
|
|
275
275
|
' "reply": "string",',
|
|
276
276
|
` "assistant_call": {"kind":"explore|bash|mixed","task":"string","context":"optional","expect":"optional","provider":"codex|claude|ufoo (optional)","model":"optional","timeout_ms":${DEFAULT_ASSISTANT_TIMEOUT_MS}},`,
|
|
277
|
-
' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string"}],',
|
|
278
|
-
' "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"}],',
|
|
277
|
+
' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string","injection_mode":"immediate|queued (optional)","source":"optional"}],',
|
|
278
|
+
' "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,"at":"YYYY-MM-DD HH:mm","once_at_ms":1700000000000,"target":"agent-id|nickname|csv","targets":["agent-id"],"title":"optional short title","prompt":"message","id":"task-id|all"}],',
|
|
279
279
|
' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}',
|
|
280
280
|
"}",
|
|
281
281
|
"Rules:",
|
|
@@ -283,7 +283,7 @@ function buildSystemPrompt(context) {
|
|
|
283
283
|
"- If multiple possible agents, use disambiguate with candidates and no dispatch.",
|
|
284
284
|
"- If user specifies a nickname for a new agent, include ops.launch with nickname so daemon can rename.",
|
|
285
285
|
"- If user requests rename, use ops.rename with agent_id and nickname (do NOT launch).",
|
|
286
|
-
"- For scheduled follow-up (cron), use ops.cron with operation=start and include
|
|
286
|
+
"- For scheduled follow-up (cron), use ops.cron with operation=start and include target(s)+prompt, plus optional title; use every/interval_ms for recurring or at/once_at_ms for one-time.",
|
|
287
287
|
"- To check scheduled tasks, use ops.cron with operation=list.",
|
|
288
288
|
"- To stop scheduled tasks, use ops.cron with operation=stop and id (or id=all).",
|
|
289
289
|
"- Use top-level assistant_call for project exploration, temporary shell tasks, and quick execution support.",
|
|
@@ -291,7 +291,10 @@ function buildSystemPrompt(context) {
|
|
|
291
291
|
"- Prefer assistant_call over launching coding agents when the task is short-lived.",
|
|
292
292
|
"- Primary routing signal is semantic continuity from agent_prompt_history; prefer the agent that already handled similar prompts.",
|
|
293
293
|
"- Launch a new coding agent when the request is a new topic without clear ownership in existing histories.",
|
|
294
|
-
"-
|
|
294
|
+
"- dispatch.injection_mode defaults to immediate when omitted.",
|
|
295
|
+
"- Use queued only when routing a chat-dialog request that is clearly a new unrelated task for an agent whose recent prompt history shows a different ongoing thread.",
|
|
296
|
+
"- If the new request strongly continues the target agent's recent prompt history, keep injection_mode immediate even when that agent is busy.",
|
|
297
|
+
"- Manual @agent sends in ufoo chat are handled outside this router and remain immediate; do not model them here.",
|
|
295
298
|
"- Legacy compatibility: if model emits ops.assistant_call, daemon will still process it.",
|
|
296
299
|
"- If no action needed, return reply with empty dispatch/ops.",
|
|
297
300
|
agentGuidance,
|
package/src/assistant/engine.js
CHANGED
|
@@ -18,16 +18,11 @@ function resolveAssistantEngine({
|
|
|
18
18
|
} = {}) {
|
|
19
19
|
const config = loadConfig(projectRoot);
|
|
20
20
|
|
|
21
|
-
const hasRequestedProvider = String(requestedProvider || "").trim().length > 0;
|
|
22
21
|
const requested = normalizeAssistantEngine(requestedProvider);
|
|
23
|
-
const configEngine = normalizeAssistantEngine(config.assistantEngine);
|
|
24
22
|
const fallback = normalizeAssistantEngine(fallbackProvider) || "codex";
|
|
25
23
|
|
|
26
24
|
let selected = requested;
|
|
27
|
-
|
|
28
|
-
// Explicit assistant_call provider=auto should inherit current main agent provider.
|
|
29
|
-
selected = hasRequestedProvider ? fallback : configEngine;
|
|
30
|
-
}
|
|
25
|
+
// Omitted/auto assistant providers inherit the active ufoo-agent provider.
|
|
31
26
|
if (selected === "auto") selected = fallback;
|
|
32
27
|
if (selected === "auto") selected = "codex";
|
|
33
28
|
|
package/src/bus/daemon.js
CHANGED
|
@@ -5,6 +5,15 @@ const Injector = require("./inject");
|
|
|
5
5
|
const QueueManager = require("./queue");
|
|
6
6
|
const MessageManager = require("./message");
|
|
7
7
|
const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
|
|
8
|
+
const { INJECTION_MODES, getInjectionModeFromEvent } = require("./messageMeta");
|
|
9
|
+
|
|
10
|
+
function isBusyActivityState(value = "") {
|
|
11
|
+
const state = String(value || "").trim().toLowerCase();
|
|
12
|
+
return state === "working"
|
|
13
|
+
|| state === "running"
|
|
14
|
+
|| state === "waiting_input"
|
|
15
|
+
|| state === "blocked";
|
|
16
|
+
}
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
19
|
* Bus Daemon - 监控消息并自动注入命令
|
|
@@ -25,6 +34,49 @@ class BusDaemon {
|
|
|
25
34
|
this.queueManager = new QueueManager(busDir);
|
|
26
35
|
this.injector = new Injector(busDir, agentsFile);
|
|
27
36
|
this.adapterRouter = createTerminalAdapterRouter();
|
|
37
|
+
this.workingHoldMs = Number.parseInt(process.env.UFOO_ACTIVITY_WORKING_HOLD_MS || "", 10) || 5000;
|
|
38
|
+
this.lastWorkingAt = new Map();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setLegacyActivityState(subscriber, state) {
|
|
42
|
+
const busData = readJSON(this.agentsFile) || { agents: {} };
|
|
43
|
+
if (!busData.agents || !busData.agents[subscriber]) return;
|
|
44
|
+
busData.agents[subscriber].activity_state = state;
|
|
45
|
+
busData.agents[subscriber].activity_since = new Date().toISOString();
|
|
46
|
+
writeJSON(this.agentsFile, busData);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
refreshLegacyActivityState(subscriber, meta, hasPending) {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const current = String(meta?.activity_state || "").trim().toLowerCase();
|
|
52
|
+
const lastWorkingAt = this.lastWorkingAt.get(subscriber) || 0;
|
|
53
|
+
const holdActive = lastWorkingAt > 0 && (now - lastWorkingAt) < this.workingHoldMs;
|
|
54
|
+
const daemonManagedWorking = lastWorkingAt > 0;
|
|
55
|
+
|
|
56
|
+
if (holdActive) {
|
|
57
|
+
if (current !== "working") {
|
|
58
|
+
this.setLegacyActivityState(subscriber, "working");
|
|
59
|
+
}
|
|
60
|
+
return "working";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (lastWorkingAt > 0) {
|
|
64
|
+
this.lastWorkingAt.delete(subscriber);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (current === "starting") {
|
|
68
|
+
const next = hasPending ? "ready" : "idle";
|
|
69
|
+
this.setLegacyActivityState(subscriber, next);
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (current === "working" && daemonManagedWorking) {
|
|
74
|
+
const next = hasPending ? "ready" : "idle";
|
|
75
|
+
this.setLegacyActivityState(subscriber, next);
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return current;
|
|
28
80
|
}
|
|
29
81
|
|
|
30
82
|
/**
|
|
@@ -257,15 +309,26 @@ class BusDaemon {
|
|
|
257
309
|
continue;
|
|
258
310
|
}
|
|
259
311
|
|
|
312
|
+
let currentActivityState = this.refreshLegacyActivityState(subscriber, meta, count > 0);
|
|
260
313
|
const events = this.drainPending(pendingFile);
|
|
261
314
|
const failed = [];
|
|
315
|
+
let deliveredCount = 0;
|
|
262
316
|
for (const evt of events) {
|
|
263
317
|
if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
|
|
264
318
|
continue;
|
|
265
319
|
}
|
|
320
|
+
const injectionMode = getInjectionModeFromEvent(evt, INJECTION_MODES.IMMEDIATE);
|
|
321
|
+
if (injectionMode === INJECTION_MODES.QUEUED && isBusyActivityState(currentActivityState)) {
|
|
322
|
+
failed.push(evt);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
266
325
|
try {
|
|
267
326
|
// eslint-disable-next-line no-await-in-loop
|
|
268
327
|
await this.injector.inject(subscriber, String(evt.data.message));
|
|
328
|
+
deliveredCount += 1;
|
|
329
|
+
currentActivityState = "working";
|
|
330
|
+
this.lastWorkingAt.set(subscriber, Date.now());
|
|
331
|
+
this.setLegacyActivityState(subscriber, "working");
|
|
269
332
|
} catch (err) {
|
|
270
333
|
failed.push(evt);
|
|
271
334
|
try {
|
|
@@ -312,7 +375,7 @@ class BusDaemon {
|
|
|
312
375
|
// ignore requeue failures
|
|
313
376
|
}
|
|
314
377
|
}
|
|
315
|
-
console.log(`[daemon] Delivered ${
|
|
378
|
+
console.log(`[daemon] Delivered ${deliveredCount} message(s) to ${subscriber}`);
|
|
316
379
|
if (wakeActive) fs.rmSync(wakePath, { force: true });
|
|
317
380
|
} catch (err) {
|
|
318
381
|
console.error(`[daemon] Failed to inject: ${err.message}`);
|
package/src/bus/index.js
CHANGED
|
@@ -306,7 +306,11 @@ class EventBus {
|
|
|
306
306
|
const eventName = options.event || "message";
|
|
307
307
|
const data = options.data || { message };
|
|
308
308
|
const result = eventName === "message"
|
|
309
|
-
? await this.messageManager.send(target, message, publisher
|
|
309
|
+
? await this.messageManager.send(target, message, publisher, {
|
|
310
|
+
data,
|
|
311
|
+
injectionMode: options.injectionMode,
|
|
312
|
+
source: options.source,
|
|
313
|
+
})
|
|
310
314
|
: await this.messageManager.emit(target, eventName, data, publisher);
|
|
311
315
|
const silent = options.silent === true;
|
|
312
316
|
if (!silent && eventName === "message") {
|
package/src/bus/message.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
normalizeAgentTypeAlias,
|
|
11
11
|
} = require("./utils");
|
|
12
12
|
const NicknameManager = require("./nickname");
|
|
13
|
+
const { buildMessageData } = require("./messageMeta");
|
|
13
14
|
|
|
14
15
|
const SEQ_LOCK_TIMEOUT_MS = 5000;
|
|
15
16
|
const SEQ_LOCK_POLL_MS = 25;
|
|
@@ -241,7 +242,7 @@ class MessageManager {
|
|
|
241
242
|
/**
|
|
242
243
|
* 发送消息
|
|
243
244
|
*/
|
|
244
|
-
async send(target, message, publisher = "unknown") {
|
|
245
|
+
async send(target, message, publisher = "unknown", options = {}) {
|
|
245
246
|
const seq = await this.getNextSeq();
|
|
246
247
|
const timestamp = getTimestamp();
|
|
247
248
|
const date = getDate();
|
|
@@ -252,6 +253,8 @@ class MessageManager {
|
|
|
252
253
|
throw new Error(`Target "${target}" not found`);
|
|
253
254
|
}
|
|
254
255
|
|
|
256
|
+
const data = buildMessageData(message, options);
|
|
257
|
+
|
|
255
258
|
// 构建事件
|
|
256
259
|
const event = {
|
|
257
260
|
seq,
|
|
@@ -260,7 +263,7 @@ class MessageManager {
|
|
|
260
263
|
event: "message",
|
|
261
264
|
publisher,
|
|
262
265
|
target,
|
|
263
|
-
data
|
|
266
|
+
data,
|
|
264
267
|
};
|
|
265
268
|
|
|
266
269
|
// 写入事件日志
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const INJECTION_MODES = {
|
|
4
|
+
IMMEDIATE: "immediate",
|
|
5
|
+
QUEUED: "queued",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function normalizeInjectionMode(value, fallback = INJECTION_MODES.IMMEDIATE) {
|
|
9
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
10
|
+
if (raw === INJECTION_MODES.QUEUED) return INJECTION_MODES.QUEUED;
|
|
11
|
+
if (raw === INJECTION_MODES.IMMEDIATE) return INJECTION_MODES.IMMEDIATE;
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeMessageSource(value) {
|
|
16
|
+
const raw = String(value || "").trim();
|
|
17
|
+
return raw || "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildMessageData(message, options = {}) {
|
|
21
|
+
const base = options && typeof options.data === "object" && options.data
|
|
22
|
+
? { ...options.data }
|
|
23
|
+
: {};
|
|
24
|
+
const data = { ...base, message };
|
|
25
|
+
data.injection_mode = normalizeInjectionMode(
|
|
26
|
+
options.injectionMode || data.injection_mode,
|
|
27
|
+
INJECTION_MODES.IMMEDIATE,
|
|
28
|
+
);
|
|
29
|
+
const source = normalizeMessageSource(options.source || data.source);
|
|
30
|
+
if (source) {
|
|
31
|
+
data.source = source;
|
|
32
|
+
} else {
|
|
33
|
+
delete data.source;
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getInjectionModeFromEvent(evt, fallback = INJECTION_MODES.IMMEDIATE) {
|
|
39
|
+
const data = evt && typeof evt.data === "object" && evt.data ? evt.data : {};
|
|
40
|
+
return normalizeInjectionMode(
|
|
41
|
+
data.injection_mode || evt?.injection_mode,
|
|
42
|
+
fallback,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
INJECTION_MODES,
|
|
48
|
+
normalizeInjectionMode,
|
|
49
|
+
normalizeMessageSource,
|
|
50
|
+
buildMessageData,
|
|
51
|
+
getInjectionModeFromEvent,
|
|
52
|
+
};
|
|
@@ -241,13 +241,36 @@ function createCommandExecutor(options = {}) {
|
|
|
241
241
|
|
|
242
242
|
try {
|
|
243
243
|
if (subcommand === "send") {
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
let injectionMode = "immediate";
|
|
245
|
+
let index = 1;
|
|
246
|
+
while (index < args.length) {
|
|
247
|
+
const arg = args[index];
|
|
248
|
+
if (arg === "--queued") {
|
|
249
|
+
injectionMode = "queued";
|
|
250
|
+
index += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (arg === "--immediate") {
|
|
254
|
+
injectionMode = "immediate";
|
|
255
|
+
index += 1;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
const positionals = args.slice(index);
|
|
261
|
+
if (positionals.length < 2) {
|
|
262
|
+
logMessage("error", "{white-fg}✗{/white-fg} Usage: /bus send [--queued|--immediate] <target> <message>");
|
|
246
263
|
return;
|
|
247
264
|
}
|
|
248
|
-
const target =
|
|
249
|
-
const message =
|
|
250
|
-
send({
|
|
265
|
+
const target = positionals[0];
|
|
266
|
+
const message = positionals.slice(1).join(" ");
|
|
267
|
+
send({
|
|
268
|
+
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
269
|
+
target,
|
|
270
|
+
message,
|
|
271
|
+
injection_mode: injectionMode,
|
|
272
|
+
source: "chat-command",
|
|
273
|
+
});
|
|
251
274
|
logMessage("system", `{white-fg}✓{/white-fg} Message sent to ${target}`);
|
|
252
275
|
return;
|
|
253
276
|
}
|
|
@@ -665,6 +688,9 @@ function createCommandExecutor(options = {}) {
|
|
|
665
688
|
const targetsRaw = String(
|
|
666
689
|
kv.target || kv.targets || kv.agent || kv.agents || ""
|
|
667
690
|
).trim();
|
|
691
|
+
const title = String(
|
|
692
|
+
kv.title || kv.name || kv.label || ""
|
|
693
|
+
).trim();
|
|
668
694
|
const prompt = String(
|
|
669
695
|
kv.prompt || kv.message || kv.msg || nonKvParts.join(" ") || ""
|
|
670
696
|
).trim();
|
|
@@ -672,7 +698,7 @@ function createCommandExecutor(options = {}) {
|
|
|
672
698
|
if ((!intervalRaw && !atRaw) || !targetsRaw || !prompt) {
|
|
673
699
|
logMessage(
|
|
674
700
|
"error",
|
|
675
|
-
"{white-fg}✗{/white-fg} Usage: /cron start every=<10s|5m> or at=\"YYYY-MM-DD HH:mm\" target=<agent1,agent2> prompt=\"...\""
|
|
701
|
+
"{white-fg}✗{/white-fg} Usage: /cron start every=<10s|5m> or at=\"YYYY-MM-DD HH:mm\" target=<agent1,agent2> [title=\"...\"] prompt=\"...\""
|
|
676
702
|
);
|
|
677
703
|
return;
|
|
678
704
|
}
|
|
@@ -705,21 +731,18 @@ function createCommandExecutor(options = {}) {
|
|
|
705
731
|
}
|
|
706
732
|
|
|
707
733
|
if (typeof requestCron === "function") {
|
|
734
|
+
const request = {
|
|
735
|
+
operation: "start",
|
|
736
|
+
targets,
|
|
737
|
+
prompt,
|
|
738
|
+
};
|
|
739
|
+
if (title) request.title = title;
|
|
708
740
|
if (atMs > 0) {
|
|
709
|
-
|
|
710
|
-
operation: "start",
|
|
711
|
-
once_at_ms: atMs,
|
|
712
|
-
targets,
|
|
713
|
-
prompt,
|
|
714
|
-
});
|
|
741
|
+
request.once_at_ms = atMs;
|
|
715
742
|
} else {
|
|
716
|
-
|
|
717
|
-
operation: "start",
|
|
718
|
-
interval_ms: intervalMs,
|
|
719
|
-
targets,
|
|
720
|
-
prompt,
|
|
721
|
-
});
|
|
743
|
+
request.interval_ms = intervalMs;
|
|
722
744
|
}
|
|
745
|
+
requestCron(request);
|
|
723
746
|
schedule(requestStatus, 200);
|
|
724
747
|
return;
|
|
725
748
|
}
|
|
@@ -729,11 +752,13 @@ function createCommandExecutor(options = {}) {
|
|
|
729
752
|
return;
|
|
730
753
|
}
|
|
731
754
|
|
|
732
|
-
const
|
|
755
|
+
const taskPayload = {
|
|
733
756
|
intervalMs,
|
|
734
757
|
targets,
|
|
735
758
|
prompt,
|
|
736
|
-
}
|
|
759
|
+
};
|
|
760
|
+
if (title) taskPayload.title = title;
|
|
761
|
+
const task = createCronTask(taskPayload);
|
|
737
762
|
if (!task) {
|
|
738
763
|
logMessage("error", "{white-fg}✗{/white-fg} Failed to create cron task");
|
|
739
764
|
return;
|
|
@@ -741,7 +766,7 @@ function createCommandExecutor(options = {}) {
|
|
|
741
766
|
|
|
742
767
|
logMessage(
|
|
743
768
|
"system",
|
|
744
|
-
`{white-fg}✓{/white-fg} Cron started ${task.id}: ${atMs > 0 ? `at ${formatCronAt(atMs)}` : `every ${formatIntervalMs(intervalMs)}`} -> ${targets.join(", ")}`
|
|
769
|
+
`{white-fg}✓{/white-fg} Cron started ${task.id}: ${task.label || `${atMs > 0 ? `at ${formatCronAt(atMs)}` : `every ${formatIntervalMs(intervalMs)}`} -> ${targets.join(", ")}`}`
|
|
745
770
|
);
|
|
746
771
|
}
|
|
747
772
|
|
package/src/chat/commands.js
CHANGED
|
@@ -30,7 +30,7 @@ const COMMAND_TREE = {
|
|
|
30
30
|
"/cron": {
|
|
31
31
|
desc: "Cron scheduler operations",
|
|
32
32
|
children: {
|
|
33
|
-
start: { desc: "Create cron task" },
|
|
33
|
+
start: { desc: "Create cron task (optional title)" },
|
|
34
34
|
list: { desc: "List cron tasks" },
|
|
35
35
|
stop: { desc: "Stop cron task by id or all" },
|
|
36
36
|
},
|
|
@@ -29,13 +29,39 @@ function sanitizeSummaryText(value = "") {
|
|
|
29
29
|
.trim();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function truncateCronText(value = "", maxLength = 24) {
|
|
33
|
+
const text = String(value || "").trim();
|
|
34
|
+
if (!text) return "";
|
|
35
|
+
if (text.length <= maxLength) return text;
|
|
36
|
+
return `${text.slice(0, Math.max(1, maxLength - 3))}...`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveTaskTitle(task = {}) {
|
|
40
|
+
const explicitTitle = truncateCronText(
|
|
41
|
+
sanitizeSummaryText(task.title || "").replace(/:/g, "-"),
|
|
42
|
+
24
|
|
43
|
+
);
|
|
44
|
+
if (explicitTitle) return explicitTitle;
|
|
45
|
+
const fallbackTitle = truncateCronText(
|
|
46
|
+
sanitizeSummaryText(task.prompt || "").replace(/:/g, "-"),
|
|
47
|
+
24
|
|
48
|
+
);
|
|
49
|
+
return fallbackTitle || "(empty)";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildTaskLabel(task = {}) {
|
|
53
|
+
const targets = Array.isArray(task.targets) && task.targets.length > 0
|
|
54
|
+
? task.targets.join("+")
|
|
55
|
+
: "unknown";
|
|
56
|
+
const title = resolveTaskTitle(task);
|
|
57
|
+
const interval = formatIntervalMs(task.intervalMs || 0);
|
|
58
|
+
return `${targets}:${title}:${interval}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
32
61
|
function summarizeTask(task = {}) {
|
|
33
62
|
const id = String(task.id || "");
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const promptRaw = sanitizeSummaryText(task.prompt || "");
|
|
37
|
-
const prompt = promptRaw.length > 24 ? `${promptRaw.slice(0, 24)}...` : promptRaw;
|
|
38
|
-
return `${id}@${interval}->${targets}: ${prompt || "(empty)"}`;
|
|
63
|
+
const label = buildTaskLabel(task);
|
|
64
|
+
return id ? `${id} ${label}` : label;
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
function createCronScheduler(options = {}) {
|
|
@@ -58,7 +84,7 @@ function createCronScheduler(options = {}) {
|
|
|
58
84
|
}
|
|
59
85
|
}
|
|
60
86
|
|
|
61
|
-
function addTask({ intervalMs = 0, targets = [], prompt = "" } = {}) {
|
|
87
|
+
function addTask({ intervalMs = 0, targets = [], prompt = "", title = "" } = {}) {
|
|
62
88
|
const safeInterval = Number.parseInt(intervalMs, 10);
|
|
63
89
|
const safeTargets = Array.isArray(targets)
|
|
64
90
|
? targets.map((item) => String(item || "").trim()).filter(Boolean)
|
|
@@ -74,6 +100,7 @@ function createCronScheduler(options = {}) {
|
|
|
74
100
|
intervalMs: safeInterval,
|
|
75
101
|
targets: Array.from(new Set(safeTargets)),
|
|
76
102
|
prompt: safePrompt,
|
|
103
|
+
title: resolveTaskTitle({ title, prompt: safePrompt }),
|
|
77
104
|
createdAt: nowFn(),
|
|
78
105
|
lastRunAt: 0,
|
|
79
106
|
tickCount: 0,
|
|
@@ -100,6 +127,7 @@ function createCronScheduler(options = {}) {
|
|
|
100
127
|
notifyChange();
|
|
101
128
|
return {
|
|
102
129
|
...task,
|
|
130
|
+
label: buildTaskLabel(task),
|
|
103
131
|
summary: summarizeTask(task),
|
|
104
132
|
};
|
|
105
133
|
}
|
|
@@ -110,9 +138,11 @@ function createCronScheduler(options = {}) {
|
|
|
110
138
|
intervalMs: task.intervalMs,
|
|
111
139
|
targets: task.targets.slice(),
|
|
112
140
|
prompt: task.prompt,
|
|
141
|
+
title: task.title,
|
|
113
142
|
createdAt: task.createdAt,
|
|
114
143
|
lastRunAt: task.lastRunAt,
|
|
115
144
|
tickCount: task.tickCount,
|
|
145
|
+
label: buildTaskLabel(task),
|
|
116
146
|
summary: summarizeTask(task),
|
|
117
147
|
}));
|
|
118
148
|
}
|
|
@@ -155,6 +185,7 @@ function createCronScheduler(options = {}) {
|
|
|
155
185
|
module.exports = {
|
|
156
186
|
parseIntervalMs,
|
|
157
187
|
formatIntervalMs,
|
|
188
|
+
buildTaskLabel,
|
|
158
189
|
summarizeTask,
|
|
159
190
|
createCronScheduler,
|
|
160
191
|
};
|
|
@@ -254,12 +254,12 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
254
254
|
if (task.mode === "once") {
|
|
255
255
|
logMessage(
|
|
256
256
|
"system",
|
|
257
|
-
`{white-fg}✓{/white-fg} Cron scheduled ${escapeBlessed(task.id)}
|
|
257
|
+
`{white-fg}✓{/white-fg} Cron scheduled ${escapeBlessed(task.id)}: ${escapeBlessed(task.label || task.onceAt || String(task.onceAtMs || ""))}`
|
|
258
258
|
);
|
|
259
259
|
} else {
|
|
260
260
|
logMessage(
|
|
261
261
|
"system",
|
|
262
|
-
`{white-fg}✓{/white-fg} Cron started ${escapeBlessed(task.id)}:
|
|
262
|
+
`{white-fg}✓{/white-fg} Cron started ${escapeBlessed(task.id)}: ${escapeBlessed(task.label || task.interval || String(task.intervalMs || ""))}`
|
|
263
263
|
);
|
|
264
264
|
}
|
|
265
265
|
} else if (operation === "stop") {
|
|
@@ -18,7 +18,6 @@ function createDashboardKeyController(options = {}) {
|
|
|
18
18
|
exitDashboardMode = () => {},
|
|
19
19
|
setLaunchMode = () => {},
|
|
20
20
|
setAgentProvider = () => {},
|
|
21
|
-
setAssistantEngine = () => {},
|
|
22
21
|
setAutoResume = () => {},
|
|
23
22
|
clampAgentWindow = () => {},
|
|
24
23
|
clampAgentWindowWithSelection = () => {},
|
|
@@ -232,10 +231,7 @@ function createDashboardKeyController(options = {}) {
|
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
if (key.name === "down") {
|
|
235
|
-
state.dashboardView = "
|
|
236
|
-
const list = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
|
|
237
|
-
const nextIndex = list.findIndex((opt) => opt.value === state.assistantEngine);
|
|
238
|
-
state.selectedAssistantIndex = nextIndex >= 0 ? nextIndex : 0;
|
|
234
|
+
state.dashboardView = "cron";
|
|
239
235
|
renderDashboardAndScreen();
|
|
240
236
|
return true;
|
|
241
237
|
}
|
|
@@ -261,67 +257,9 @@ function createDashboardKeyController(options = {}) {
|
|
|
261
257
|
return true;
|
|
262
258
|
}
|
|
263
259
|
|
|
264
|
-
function handleAssistantKey(key) {
|
|
265
|
-
const options = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
|
|
266
|
-
if (options.length === 0) {
|
|
267
|
-
if (key.name === "up") {
|
|
268
|
-
state.dashboardView = "provider";
|
|
269
|
-
renderDashboardAndScreen();
|
|
270
|
-
return true;
|
|
271
|
-
}
|
|
272
|
-
if (key.name === "escape" || key.name === "enter" || key.name === "return") {
|
|
273
|
-
exitDashboardMode(false);
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (key.name === "left") {
|
|
280
|
-
state.selectedAssistantIndex = state.selectedAssistantIndex <= 0
|
|
281
|
-
? options.length - 1
|
|
282
|
-
: state.selectedAssistantIndex - 1;
|
|
283
|
-
renderDashboardAndScreen();
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (key.name === "right") {
|
|
288
|
-
state.selectedAssistantIndex = state.selectedAssistantIndex >= options.length - 1
|
|
289
|
-
? 0
|
|
290
|
-
: state.selectedAssistantIndex + 1;
|
|
291
|
-
renderDashboardAndScreen();
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (key.name === "up") {
|
|
296
|
-
state.dashboardView = "provider";
|
|
297
|
-
renderDashboardAndScreen();
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (key.name === "down") {
|
|
302
|
-
state.dashboardView = "cron";
|
|
303
|
-
renderDashboardAndScreen();
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (key.name === "enter" || key.name === "return") {
|
|
308
|
-
const selected = options[state.selectedAssistantIndex];
|
|
309
|
-
if (selected) setAssistantEngine(selected.value);
|
|
310
|
-
exitDashboardMode(false);
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (key.name === "escape") {
|
|
315
|
-
exitDashboardMode(false);
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
260
|
function handleCronKey(key) {
|
|
323
261
|
if (key.name === "up") {
|
|
324
|
-
state.dashboardView = "
|
|
262
|
+
state.dashboardView = "provider";
|
|
325
263
|
renderDashboardAndScreen();
|
|
326
264
|
return true;
|
|
327
265
|
}
|
|
@@ -552,7 +490,6 @@ function createDashboardKeyController(options = {}) {
|
|
|
552
490
|
|
|
553
491
|
if (state.dashboardView === "mode") return handleModeKey(key);
|
|
554
492
|
if (state.dashboardView === "provider") return handleProviderKey(key);
|
|
555
|
-
if (state.dashboardView === "assistant") return handleAssistantKey(key);
|
|
556
493
|
if (state.dashboardView === "resume") return handleResumeKey(key);
|
|
557
494
|
if (state.dashboardView === "cron") return handleCronKey(key);
|
|
558
495
|
|
|
@@ -8,14 +8,6 @@ function providerLabel(value) {
|
|
|
8
8
|
return "codex";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function assistantLabel(value) {
|
|
12
|
-
if (value === "codex") return "codex";
|
|
13
|
-
if (value === "claude") return "claude";
|
|
14
|
-
if (value === "ufoo") return "ucode";
|
|
15
|
-
if (value === "ucode") return "ucode";
|
|
16
|
-
return "auto";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
function ensureAtPrefix(value) {
|
|
20
12
|
const text = String(value || "").trim();
|
|
21
13
|
if (!text) return text;
|
|
@@ -43,7 +35,6 @@ function buildSummaryLine(options = {}) {
|
|
|
43
35
|
getAgentState = () => "",
|
|
44
36
|
launchMode = "terminal",
|
|
45
37
|
agentProvider = "codex-cli",
|
|
46
|
-
assistantEngine = "auto",
|
|
47
38
|
cronTasks = [],
|
|
48
39
|
} = options;
|
|
49
40
|
const agents = activeAgents.length > 0
|
|
@@ -55,7 +46,6 @@ function buildSummaryLine(options = {}) {
|
|
|
55
46
|
return `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`
|
|
56
47
|
+ ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`
|
|
57
48
|
+ ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`
|
|
58
|
-
+ ` {gray-fg}Assistant:{/gray-fg} {cyan-fg}${assistantLabel(assistantEngine)}{/cyan-fg}`
|
|
59
49
|
+ ` {gray-fg}Cron:{/gray-fg} {cyan-fg}${Array.isArray(cronTasks) ? cronTasks.length : 0}{/cyan-fg}`;
|
|
60
50
|
}
|
|
61
51
|
|
|
@@ -134,11 +124,9 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
134
124
|
getAgentState = () => "",
|
|
135
125
|
selectedModeIndex = 0,
|
|
136
126
|
selectedProviderIndex = 0,
|
|
137
|
-
selectedAssistantIndex = 0,
|
|
138
127
|
selectedResumeIndex = 0,
|
|
139
128
|
cronTasks = [],
|
|
140
129
|
providerOptions = [],
|
|
141
|
-
assistantOptions = [],
|
|
142
130
|
resumeOptions = [],
|
|
143
131
|
dashHints = {},
|
|
144
132
|
modeOptions = DEFAULT_MODE_OPTIONS,
|
|
@@ -171,18 +159,6 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
171
159
|
return { content, windowStart };
|
|
172
160
|
}
|
|
173
161
|
|
|
174
|
-
if (dashboardView === "assistant") {
|
|
175
|
-
const assistantParts = assistantOptions.map((opt, i) => {
|
|
176
|
-
if (i === selectedAssistantIndex) {
|
|
177
|
-
return `{inverse}${opt.label}{/inverse}`;
|
|
178
|
-
}
|
|
179
|
-
return `{cyan-fg}${opt.label}{/cyan-fg}`;
|
|
180
|
-
});
|
|
181
|
-
content += `{gray-fg}Assistant:{/gray-fg} ${assistantParts.join(" ")}`;
|
|
182
|
-
content += ` {gray-fg}│ ${dashHints.assistant || ""}{/gray-fg}`;
|
|
183
|
-
return { content, windowStart };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
162
|
if (dashboardView === "resume") {
|
|
187
163
|
const resumeParts = resumeOptions.map((opt, i) => {
|
|
188
164
|
if (i === selectedResumeIndex) {
|
|
@@ -198,9 +174,9 @@ function buildDashboardDetailLine(options = {}) {
|
|
|
198
174
|
if (dashboardView === "cron") {
|
|
199
175
|
const items = Array.isArray(cronTasks) ? cronTasks : [];
|
|
200
176
|
const summary = items.length > 0
|
|
201
|
-
? items.map((item) => item.summary || item.id || "").filter(Boolean).join(", ")
|
|
177
|
+
? items.map((item) => item.label || item.summary || item.id || "").filter(Boolean).join(", ")
|
|
202
178
|
: "none";
|
|
203
|
-
content += `{gray-fg}Cron:{/gray-fg} {
|
|
179
|
+
content += `{gray-fg}Cron:{/gray-fg} {inverse}${summary}{/inverse}`;
|
|
204
180
|
content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
|
|
205
181
|
return { content, windowStart };
|
|
206
182
|
}
|
|
@@ -259,14 +235,11 @@ function computeDashboardContent(options = {}) {
|
|
|
259
235
|
getAgentState = () => "",
|
|
260
236
|
launchMode = "terminal",
|
|
261
237
|
agentProvider = "codex-cli",
|
|
262
|
-
assistantEngine = "auto",
|
|
263
238
|
selectedModeIndex = 0,
|
|
264
239
|
selectedProviderIndex = 0,
|
|
265
|
-
selectedAssistantIndex = 0,
|
|
266
240
|
selectedResumeIndex = 0,
|
|
267
241
|
cronTasks = [],
|
|
268
242
|
providerOptions = [],
|
|
269
|
-
assistantOptions = [],
|
|
270
243
|
resumeOptions = [],
|
|
271
244
|
dashHints = {},
|
|
272
245
|
modeOptions = DEFAULT_MODE_OPTIONS,
|
|
@@ -297,7 +270,6 @@ function computeDashboardContent(options = {}) {
|
|
|
297
270
|
getAgentState,
|
|
298
271
|
launchMode,
|
|
299
272
|
agentProvider,
|
|
300
|
-
assistantEngine,
|
|
301
273
|
cronTasks,
|
|
302
274
|
});
|
|
303
275
|
return {
|
|
@@ -317,11 +289,9 @@ function computeDashboardContent(options = {}) {
|
|
|
317
289
|
getAgentState,
|
|
318
290
|
selectedModeIndex,
|
|
319
291
|
selectedProviderIndex,
|
|
320
|
-
selectedAssistantIndex,
|
|
321
292
|
selectedResumeIndex,
|
|
322
293
|
cronTasks,
|
|
323
294
|
providerOptions,
|
|
324
|
-
assistantOptions,
|
|
325
295
|
resumeOptions,
|
|
326
296
|
dashHints,
|
|
327
297
|
modeOptions,
|
|
@@ -344,11 +314,9 @@ function computeDashboardContent(options = {}) {
|
|
|
344
314
|
getAgentState,
|
|
345
315
|
selectedModeIndex,
|
|
346
316
|
selectedProviderIndex,
|
|
347
|
-
selectedAssistantIndex,
|
|
348
317
|
selectedResumeIndex,
|
|
349
318
|
cronTasks,
|
|
350
319
|
providerOptions,
|
|
351
|
-
assistantOptions,
|
|
352
320
|
resumeOptions,
|
|
353
321
|
dashHints,
|
|
354
322
|
modeOptions,
|
|
@@ -362,7 +330,6 @@ function computeDashboardContent(options = {}) {
|
|
|
362
330
|
getAgentState,
|
|
363
331
|
launchMode,
|
|
364
332
|
agentProvider,
|
|
365
|
-
assistantEngine,
|
|
366
333
|
cronTasks,
|
|
367
334
|
});
|
|
368
335
|
|
|
@@ -372,5 +339,4 @@ function computeDashboardContent(options = {}) {
|
|
|
372
339
|
module.exports = {
|
|
373
340
|
computeDashboardContent,
|
|
374
341
|
providerLabel,
|
|
375
|
-
assistantLabel,
|
|
376
342
|
};
|
package/src/chat/index.js
CHANGED
|
@@ -9,7 +9,6 @@ const {
|
|
|
9
9
|
saveConfig,
|
|
10
10
|
normalizeLaunchMode,
|
|
11
11
|
normalizeAgentProvider,
|
|
12
|
-
normalizeAssistantEngine,
|
|
13
12
|
} = require("../config");
|
|
14
13
|
const { socketPath, isRunning } = require("../daemon");
|
|
15
14
|
const UfooInit = require("../init");
|
|
@@ -104,7 +103,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
104
103
|
const config = loadConfig(projectRoot);
|
|
105
104
|
let launchMode = config.launchMode;
|
|
106
105
|
let agentProvider = config.agentProvider;
|
|
107
|
-
let assistantEngine = normalizeAssistantEngine(config.assistantEngine);
|
|
108
106
|
let autoResume = config.autoResume !== false;
|
|
109
107
|
let cronTasks = [];
|
|
110
108
|
|
|
@@ -316,6 +314,8 @@ async function runChat(projectRoot, options = {}) {
|
|
|
316
314
|
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
317
315
|
target,
|
|
318
316
|
message: JSON.stringify({ raw: true, data }),
|
|
317
|
+
injection_mode: "immediate",
|
|
318
|
+
source: "chat-agent-view",
|
|
319
319
|
});
|
|
320
320
|
},
|
|
321
321
|
});
|
|
@@ -673,7 +673,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
673
673
|
let selectedAgentIndex = -1; // -1 = not in dashboard selection mode
|
|
674
674
|
let targetAgent = null; // Selected agent for direct messaging
|
|
675
675
|
let focusMode = "input"; // "input" or "dashboard"
|
|
676
|
-
let dashboardView = "agents"; // "projects" | "agents" | "mode" | "provider" | "
|
|
676
|
+
let dashboardView = "agents"; // "projects" | "agents" | "mode" | "provider" | "cron"
|
|
677
677
|
let reportPendingTotal = 0;
|
|
678
678
|
let selectedModeIndex = Math.max(0, MODE_OPTIONS.indexOf(launchMode));
|
|
679
679
|
const providerOptions = [
|
|
@@ -682,16 +682,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
682
682
|
{ label: "ucode", value: "ucode" },
|
|
683
683
|
];
|
|
684
684
|
let selectedProviderIndex = Math.max(0, providerOptions.findIndex((opt) => opt.value === agentProvider));
|
|
685
|
-
const assistantOptions = [
|
|
686
|
-
{ label: "auto", value: "auto" },
|
|
687
|
-
{ label: "codex", value: "codex" },
|
|
688
|
-
{ label: "claude", value: "claude" },
|
|
689
|
-
{ label: "ucode", value: "ufoo" },
|
|
690
|
-
];
|
|
691
|
-
let selectedAssistantIndex = Math.max(
|
|
692
|
-
0,
|
|
693
|
-
assistantOptions.findIndex((opt) => opt.value === assistantEngine)
|
|
694
|
-
);
|
|
695
685
|
const resumeOptions = [
|
|
696
686
|
{ label: "Resume previous session", value: true },
|
|
697
687
|
{ label: "Start new session", value: false },
|
|
@@ -702,8 +692,7 @@ async function runChat(projectRoot, options = {}) {
|
|
|
702
692
|
agentsGlobal: "←/→ select · Enter · ↓ mode · ↑ projects",
|
|
703
693
|
agentsEmpty: "↓ mode · ↑ back",
|
|
704
694
|
mode: "←/→ select · Enter · ↓ provider · ↑ back",
|
|
705
|
-
provider: "←/→ select · Enter · ↓
|
|
706
|
-
assistant: "←/→ select · Enter · ↓ cron · ↑ back",
|
|
695
|
+
provider: "←/→ select · Enter · ↓ cron · ↑ back",
|
|
707
696
|
cron: "Ctrl+X close · ↑ back",
|
|
708
697
|
resume: "",
|
|
709
698
|
projects: "Use /project switch <index|path>",
|
|
@@ -1079,12 +1068,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1079
1068
|
}
|
|
1080
1069
|
}
|
|
1081
1070
|
|
|
1082
|
-
function setAssistantEngine(value) {
|
|
1083
|
-
if (settingsController) {
|
|
1084
|
-
settingsController.setAssistantEngine(value);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
1071
|
function setAutoResume(value) {
|
|
1089
1072
|
if (settingsController) {
|
|
1090
1073
|
settingsController.setAutoResume(value);
|
|
@@ -1101,7 +1084,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1101
1084
|
saveConfig,
|
|
1102
1085
|
normalizeLaunchMode,
|
|
1103
1086
|
normalizeAgentProvider,
|
|
1104
|
-
normalizeAssistantEngine,
|
|
1105
1087
|
fsModule: fs,
|
|
1106
1088
|
getUfooPaths,
|
|
1107
1089
|
logMessage,
|
|
@@ -1122,14 +1104,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1122
1104
|
setSelectedProviderIndex: (value) => {
|
|
1123
1105
|
selectedProviderIndex = value;
|
|
1124
1106
|
},
|
|
1125
|
-
getAssistantEngine: () => assistantEngine,
|
|
1126
|
-
setAssistantEngineState: (value) => {
|
|
1127
|
-
assistantEngine = value;
|
|
1128
|
-
},
|
|
1129
|
-
setSelectedAssistantIndex: (value) => {
|
|
1130
|
-
selectedAssistantIndex = value;
|
|
1131
|
-
},
|
|
1132
|
-
assistantOptions,
|
|
1133
1107
|
providerOptions,
|
|
1134
1108
|
modeOptions: MODE_OPTIONS,
|
|
1135
1109
|
getAutoResume: () => autoResume,
|
|
@@ -1178,15 +1152,12 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1178
1152
|
},
|
|
1179
1153
|
launchMode,
|
|
1180
1154
|
agentProvider,
|
|
1181
|
-
assistantEngine,
|
|
1182
1155
|
autoResume,
|
|
1183
1156
|
selectedModeIndex,
|
|
1184
1157
|
selectedProviderIndex,
|
|
1185
|
-
selectedAssistantIndex,
|
|
1186
1158
|
selectedResumeIndex,
|
|
1187
1159
|
cronTasks,
|
|
1188
1160
|
providerOptions,
|
|
1189
|
-
assistantOptions,
|
|
1190
1161
|
resumeOptions,
|
|
1191
1162
|
pendingReports: reportPendingTotal,
|
|
1192
1163
|
dashHints: DASH_HINTS,
|
|
@@ -1335,10 +1306,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1335
1306
|
}
|
|
1336
1307
|
selectedModeIndex = Math.max(0, MODE_OPTIONS.indexOf(launchMode));
|
|
1337
1308
|
selectedProviderIndex = Math.max(0, providerOptions.findIndex((opt) => opt.value === agentProvider));
|
|
1338
|
-
selectedAssistantIndex = Math.max(
|
|
1339
|
-
0,
|
|
1340
|
-
assistantOptions.findIndex((opt) => opt.value === assistantEngine)
|
|
1341
|
-
);
|
|
1342
1309
|
selectedResumeIndex = autoResume ? 0 : 1;
|
|
1343
1310
|
// Immediately set @target when first agent is selected.
|
|
1344
1311
|
if (!globalMode && selectedAgentIndex >= 0 && selectedAgentIndex < activeAgents.length) {
|
|
@@ -1366,15 +1333,12 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1366
1333
|
activeAgentMetaMap: { get: () => activeAgentMetaMap },
|
|
1367
1334
|
selectedModeIndex: { get: () => selectedModeIndex, set: (value) => { selectedModeIndex = value; } },
|
|
1368
1335
|
selectedProviderIndex: { get: () => selectedProviderIndex, set: (value) => { selectedProviderIndex = value; } },
|
|
1369
|
-
selectedAssistantIndex: { get: () => selectedAssistantIndex, set: (value) => { selectedAssistantIndex = value; } },
|
|
1370
1336
|
selectedResumeIndex: { get: () => selectedResumeIndex, set: (value) => { selectedResumeIndex = value; } },
|
|
1371
1337
|
launchMode: { get: () => launchMode },
|
|
1372
1338
|
agentProvider: { get: () => agentProvider },
|
|
1373
|
-
assistantEngine: { get: () => assistantEngine },
|
|
1374
1339
|
autoResume: { get: () => autoResume },
|
|
1375
1340
|
cronTasks: { get: () => cronTasks },
|
|
1376
1341
|
providerOptions: { get: () => providerOptions },
|
|
1377
|
-
assistantOptions: { get: () => assistantOptions },
|
|
1378
1342
|
resumeOptions: { get: () => resumeOptions },
|
|
1379
1343
|
agentOutputSuppressed: {
|
|
1380
1344
|
get: () => getAgentOutputSuppressed(),
|
|
@@ -1413,7 +1377,6 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1413
1377
|
exitDashboardMode,
|
|
1414
1378
|
setLaunchMode,
|
|
1415
1379
|
setAgentProvider,
|
|
1416
|
-
setAssistantEngine,
|
|
1417
1380
|
setAutoResume,
|
|
1418
1381
|
clampAgentWindow,
|
|
1419
1382
|
clampAgentWindowWithSelection,
|
|
@@ -90,7 +90,13 @@ function createInputSubmitHandler(options = {}) {
|
|
|
90
90
|
);
|
|
91
91
|
renderScreen(); // Immediately render the user message
|
|
92
92
|
markPendingDelivery(state.targetAgent);
|
|
93
|
-
send({
|
|
93
|
+
send({
|
|
94
|
+
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
95
|
+
target: state.targetAgent,
|
|
96
|
+
message: text,
|
|
97
|
+
injection_mode: "immediate",
|
|
98
|
+
source: "chat-direct",
|
|
99
|
+
});
|
|
94
100
|
clearTargetAgent();
|
|
95
101
|
focusInput();
|
|
96
102
|
return;
|
|
@@ -120,7 +126,13 @@ function createInputSubmitHandler(options = {}) {
|
|
|
120
126
|
);
|
|
121
127
|
renderScreen(); // Immediately render the user message
|
|
122
128
|
markPendingDelivery(resolvedTarget);
|
|
123
|
-
send({
|
|
129
|
+
send({
|
|
130
|
+
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
131
|
+
target: resolvedTarget,
|
|
132
|
+
message: atTarget.message,
|
|
133
|
+
injection_mode: "immediate",
|
|
134
|
+
source: "chat-direct",
|
|
135
|
+
});
|
|
124
136
|
focusInput();
|
|
125
137
|
return;
|
|
126
138
|
}
|
|
@@ -145,6 +157,11 @@ function createInputSubmitHandler(options = {}) {
|
|
|
145
157
|
send({
|
|
146
158
|
type: IPC_REQUEST_TYPES.PROMPT,
|
|
147
159
|
text: `Use agent ${choice.agent_id} to handle: ${state.pending.original || "the request"}`,
|
|
160
|
+
request_meta: {
|
|
161
|
+
source: "chat-dialog",
|
|
162
|
+
dispatch_default_injection_mode: "immediate",
|
|
163
|
+
allow_relevance_queue: true,
|
|
164
|
+
},
|
|
148
165
|
});
|
|
149
166
|
state.pending = null;
|
|
150
167
|
} else {
|
|
@@ -153,7 +170,15 @@ function createInputSubmitHandler(options = {}) {
|
|
|
153
170
|
} else {
|
|
154
171
|
state.pending = { original: text };
|
|
155
172
|
queueStatusLine("ufoo-agent processing");
|
|
156
|
-
send({
|
|
173
|
+
send({
|
|
174
|
+
type: IPC_REQUEST_TYPES.PROMPT,
|
|
175
|
+
text,
|
|
176
|
+
request_meta: {
|
|
177
|
+
source: "chat-dialog",
|
|
178
|
+
dispatch_default_injection_mode: "immediate",
|
|
179
|
+
allow_relevance_queue: true,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
157
182
|
logMessage("user", `{white-fg}→{/white-fg} ${escapeBlessed(text)}`);
|
|
158
183
|
renderScreen(); // Render plain text message immediately
|
|
159
184
|
}
|
|
@@ -6,7 +6,6 @@ function createSettingsController(options = {}) {
|
|
|
6
6
|
saveConfig = () => {},
|
|
7
7
|
normalizeLaunchMode = (value) => value,
|
|
8
8
|
normalizeAgentProvider = (value) => value,
|
|
9
|
-
normalizeAssistantEngine = (value) => value,
|
|
10
9
|
fsModule,
|
|
11
10
|
getUfooPaths = () => ({ agentDir: "" }),
|
|
12
11
|
logMessage = () => {},
|
|
@@ -19,10 +18,6 @@ function createSettingsController(options = {}) {
|
|
|
19
18
|
getAgentProvider = () => "codex-cli",
|
|
20
19
|
setAgentProviderState = () => {},
|
|
21
20
|
setSelectedProviderIndex = () => {},
|
|
22
|
-
getAssistantEngine = () => "auto",
|
|
23
|
-
setAssistantEngineState = () => {},
|
|
24
|
-
setSelectedAssistantIndex = () => {},
|
|
25
|
-
assistantOptions = [],
|
|
26
21
|
providerOptions = [],
|
|
27
22
|
modeOptions = [],
|
|
28
23
|
getAutoResume = () => true,
|
|
@@ -41,14 +36,6 @@ function createSettingsController(options = {}) {
|
|
|
41
36
|
return value === "claude-cli" ? "claude" : "codex";
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
function assistantLabel(value) {
|
|
45
|
-
const normalized = normalizeAssistantEngine(value);
|
|
46
|
-
if (normalized === "codex") return "codex";
|
|
47
|
-
if (normalized === "claude") return "claude";
|
|
48
|
-
if (normalized === "ufoo") return "ufoo";
|
|
49
|
-
return "auto";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
39
|
function clearUfooAgentIdentity() {
|
|
53
40
|
const agentDir = getUfooPaths(projectRoot).agentDir;
|
|
54
41
|
const stateFile = path.join(agentDir, "ufoo-agent.json");
|
|
@@ -106,26 +93,11 @@ function createSettingsController(options = {}) {
|
|
|
106
93
|
return true;
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
function setAssistantEngine(engine) {
|
|
110
|
-
const next = normalizeAssistantEngine(engine);
|
|
111
|
-
if (next === getAssistantEngine()) return false;
|
|
112
|
-
setAssistantEngineState(next);
|
|
113
|
-
const idx = assistantOptions.findIndex((opt) => opt && opt.value === next);
|
|
114
|
-
setSelectedAssistantIndex(idx >= 0 ? idx : 0);
|
|
115
|
-
saveConfig(projectRoot, { assistantEngine: next });
|
|
116
|
-
logMessage("status", `{white-fg}⚙{/white-fg} assistant-engine: ${assistantLabel(next)}`);
|
|
117
|
-
renderDashboard();
|
|
118
|
-
renderScreen();
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
96
|
return {
|
|
123
97
|
providerLabel,
|
|
124
|
-
assistantLabel,
|
|
125
98
|
clearUfooAgentIdentity,
|
|
126
99
|
setLaunchMode,
|
|
127
100
|
setAgentProvider,
|
|
128
|
-
setAssistantEngine,
|
|
129
101
|
setAutoResume,
|
|
130
102
|
};
|
|
131
103
|
}
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
function parseSendArgs(cmdArgs = []) {
|
|
4
|
+
let injectionMode = "immediate";
|
|
5
|
+
let source = "";
|
|
6
|
+
let index = 0;
|
|
7
|
+
|
|
8
|
+
while (index < cmdArgs.length) {
|
|
9
|
+
const arg = cmdArgs[index];
|
|
10
|
+
if (arg === "--queued") {
|
|
11
|
+
injectionMode = "queued";
|
|
12
|
+
index += 1;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (arg === "--immediate") {
|
|
16
|
+
injectionMode = "immediate";
|
|
17
|
+
index += 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (arg === "--source") {
|
|
21
|
+
source = String(cmdArgs[index + 1] || "").trim();
|
|
22
|
+
index += 2;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const positionals = cmdArgs.slice(index);
|
|
29
|
+
|
|
30
|
+
if (positionals.length < 2) {
|
|
31
|
+
throw new Error("send requires <target> <message>");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
target: positionals[0],
|
|
36
|
+
message: positionals.slice(1).join(" "),
|
|
37
|
+
injectionMode,
|
|
38
|
+
source,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
3
42
|
async function runBusCoreCommand(eventBus, cmd, cmdArgs = []) {
|
|
4
43
|
switch (cmd) {
|
|
5
44
|
case "init":
|
|
@@ -15,7 +54,11 @@ async function runBusCoreCommand(eventBus, cmd, cmdArgs = []) {
|
|
|
15
54
|
case "send":
|
|
16
55
|
{
|
|
17
56
|
const publisher = await eventBus.ensureJoined();
|
|
18
|
-
|
|
57
|
+
const parsed = parseSendArgs(cmdArgs);
|
|
58
|
+
await eventBus.send(parsed.target, parsed.message, publisher, {
|
|
59
|
+
injectionMode: parsed.injectionMode,
|
|
60
|
+
source: parsed.source,
|
|
61
|
+
});
|
|
19
62
|
}
|
|
20
63
|
return {};
|
|
21
64
|
case "broadcast":
|
package/src/daemon/cronOps.js
CHANGED
|
@@ -128,18 +128,41 @@ function sanitizeSummaryText(value = "") {
|
|
|
128
128
|
.trim();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
function truncateCronText(value = "", maxLength = 24) {
|
|
132
|
+
const text = String(value || "").trim();
|
|
133
|
+
if (!text) return "";
|
|
134
|
+
if (text.length <= maxLength) return text;
|
|
135
|
+
return `${text.slice(0, Math.max(1, maxLength - 3))}...`;
|
|
136
|
+
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
function normalizeCronTitle(value = "", prompt = "") {
|
|
139
|
+
const explicitTitle = truncateCronText(
|
|
140
|
+
sanitizeSummaryText(value || "").replace(/:/g, "-"),
|
|
141
|
+
24
|
|
142
|
+
);
|
|
143
|
+
if (explicitTitle) return explicitTitle;
|
|
144
|
+
const fallbackTitle = truncateCronText(
|
|
145
|
+
sanitizeSummaryText(prompt || "").replace(/:/g, "-"),
|
|
146
|
+
24
|
|
147
|
+
);
|
|
148
|
+
return fallbackTitle || "(empty)";
|
|
149
|
+
}
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
function buildCronLabel(task = {}) {
|
|
152
|
+
const targets = Array.isArray(task.targets) && task.targets.length > 0
|
|
153
|
+
? task.targets.join("+")
|
|
154
|
+
: "unknown";
|
|
155
|
+
const title = normalizeCronTitle(task.title || "", task.prompt || "");
|
|
156
|
+
const schedule = Number(task.onceAtMs) > 0
|
|
157
|
+
? formatCronAtMs(task.onceAtMs)
|
|
158
|
+
: formatIntervalMs(task.intervalMs || 0);
|
|
159
|
+
return `${targets}:${title}:${schedule || "0s"}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function summarizeCronTask(task = {}) {
|
|
163
|
+
const id = String(task.id || "");
|
|
164
|
+
const label = buildCronLabel(task);
|
|
165
|
+
return id ? `${id} ${label}` : label;
|
|
143
166
|
}
|
|
144
167
|
|
|
145
168
|
function formatCronTask(task = {}) {
|
|
@@ -153,6 +176,8 @@ function formatCronTask(task = {}) {
|
|
|
153
176
|
onceAt: onceAtMs > 0 ? formatCronAtMs(onceAtMs) : "",
|
|
154
177
|
targets: Array.isArray(task.targets) ? task.targets.slice() : [],
|
|
155
178
|
prompt: String(task.prompt || ""),
|
|
179
|
+
title: normalizeCronTitle(task.title || "", task.prompt || ""),
|
|
180
|
+
label: buildCronLabel(task),
|
|
156
181
|
createdAt: Number(task.createdAt) || 0,
|
|
157
182
|
lastRunAt: Number(task.lastRunAt) || 0,
|
|
158
183
|
tickCount: Number(task.tickCount) || 0,
|
|
@@ -160,6 +185,10 @@ function formatCronTask(task = {}) {
|
|
|
160
185
|
};
|
|
161
186
|
}
|
|
162
187
|
|
|
188
|
+
function resolveCronTitle(op = {}) {
|
|
189
|
+
return String(op.title || op.name || op.label || "").trim();
|
|
190
|
+
}
|
|
191
|
+
|
|
163
192
|
function createDaemonCronController(options = {}) {
|
|
164
193
|
const {
|
|
165
194
|
projectRoot = "",
|
|
@@ -198,6 +227,7 @@ function createDaemonCronController(options = {}) {
|
|
|
198
227
|
onceAtMs: task.onceAtMs,
|
|
199
228
|
targets: task.targets.slice(),
|
|
200
229
|
prompt: task.prompt,
|
|
230
|
+
title: task.title,
|
|
201
231
|
createdAt: task.createdAt,
|
|
202
232
|
lastRunAt: task.lastRunAt,
|
|
203
233
|
tickCount: task.tickCount,
|
|
@@ -275,13 +305,14 @@ function createDaemonCronController(options = {}) {
|
|
|
275
305
|
}, task.intervalMs);
|
|
276
306
|
}
|
|
277
307
|
|
|
278
|
-
function addTask({ intervalMs = 0, onceAtMs = 0, targets = [], prompt = "" } = {}) {
|
|
308
|
+
function addTask({ intervalMs = 0, onceAtMs = 0, targets = [], prompt = "", title = "" } = {}) {
|
|
279
309
|
const safeInterval = Number.parseInt(intervalMs, 10);
|
|
280
310
|
const safeOnceAt = Number.parseInt(onceAtMs, 10);
|
|
281
311
|
const safeTargets = Array.isArray(targets)
|
|
282
312
|
? targets.map((item) => String(item || "").trim()).filter(Boolean)
|
|
283
313
|
: [];
|
|
284
314
|
const safePrompt = String(prompt || "").trim();
|
|
315
|
+
const safeTitle = normalizeCronTitle(title, safePrompt);
|
|
285
316
|
|
|
286
317
|
if (!safePrompt || safeTargets.length === 0) return null;
|
|
287
318
|
|
|
@@ -296,6 +327,7 @@ function createDaemonCronController(options = {}) {
|
|
|
296
327
|
onceAtMs: useOnce ? safeOnceAt : 0,
|
|
297
328
|
targets: Array.from(new Set(safeTargets)),
|
|
298
329
|
prompt: safePrompt,
|
|
330
|
+
title: safeTitle,
|
|
299
331
|
createdAt: nowFn(),
|
|
300
332
|
lastRunAt: 0,
|
|
301
333
|
tickCount: 0,
|
|
@@ -367,6 +399,7 @@ function createDaemonCronController(options = {}) {
|
|
|
367
399
|
? item.targets.map((v) => String(v || "").trim()).filter(Boolean)
|
|
368
400
|
: [];
|
|
369
401
|
const prompt = String(item && item.prompt ? item.prompt : "").trim();
|
|
402
|
+
const title = normalizeCronTitle(item && item.title ? item.title : "", prompt);
|
|
370
403
|
|
|
371
404
|
if (!prompt || targets.length === 0) {
|
|
372
405
|
changed = true;
|
|
@@ -389,6 +422,7 @@ function createDaemonCronController(options = {}) {
|
|
|
389
422
|
onceAtMs: Number.isFinite(onceAtMs) ? Math.floor(onceAtMs) : 0,
|
|
390
423
|
targets: Array.from(new Set(targets)),
|
|
391
424
|
prompt,
|
|
425
|
+
title,
|
|
392
426
|
createdAt: Number(item && item.createdAt) || now,
|
|
393
427
|
lastRunAt: Number(item && item.lastRunAt) || 0,
|
|
394
428
|
tickCount: Number(item && item.tickCount) || 0,
|
|
@@ -535,6 +569,7 @@ function createDaemonCronController(options = {}) {
|
|
|
535
569
|
onceAtMs,
|
|
536
570
|
targets,
|
|
537
571
|
prompt,
|
|
572
|
+
title: resolveCronTitle(op),
|
|
538
573
|
});
|
|
539
574
|
|
|
540
575
|
if (!task) {
|
|
@@ -567,8 +602,10 @@ module.exports = {
|
|
|
567
602
|
resolveCronOperation,
|
|
568
603
|
resolveCronIntervalMs,
|
|
569
604
|
resolveCronOnceAtMs,
|
|
605
|
+
resolveCronTitle,
|
|
570
606
|
resolveCronPrompt,
|
|
571
607
|
resolveCronTaskId,
|
|
572
608
|
parseCronAtMs,
|
|
609
|
+
buildCronLabel,
|
|
573
610
|
formatCronTask,
|
|
574
611
|
};
|
package/src/daemon/index.js
CHANGED
|
@@ -478,11 +478,15 @@ async function dispatchMessages(projectRoot, dispatch = []) {
|
|
|
478
478
|
for (const item of dispatch) {
|
|
479
479
|
if (!item || !item.target || !item.message) continue;
|
|
480
480
|
const pub = item.publisher || defaultPublisher;
|
|
481
|
+
const sendOptions = {
|
|
482
|
+
injectionMode: item.injection_mode,
|
|
483
|
+
source: item.source,
|
|
484
|
+
};
|
|
481
485
|
try {
|
|
482
486
|
if (item.target === "broadcast") {
|
|
483
|
-
await eventBus.broadcast(item.message, pub);
|
|
487
|
+
await eventBus.broadcast(item.message, pub, sendOptions);
|
|
484
488
|
} else {
|
|
485
|
-
await eventBus.send(item.target, item.message, pub);
|
|
489
|
+
await eventBus.send(item.target, item.message, pub, sendOptions);
|
|
486
490
|
}
|
|
487
491
|
} catch {
|
|
488
492
|
// ignore dispatch failures
|
|
@@ -844,7 +848,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
844
848
|
}
|
|
845
849
|
if (req.type === IPC_REQUEST_TYPES.BUS_SEND) {
|
|
846
850
|
// Direct bus send request from chat UI
|
|
847
|
-
const { target, message } = req;
|
|
851
|
+
const { target, message, injection_mode, source } = req;
|
|
848
852
|
if (!target || !message) {
|
|
849
853
|
socket.write(
|
|
850
854
|
`${JSON.stringify({
|
|
@@ -858,7 +862,10 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
858
862
|
try {
|
|
859
863
|
const publisher = busBridge.getSubscriber() || "ufoo-agent";
|
|
860
864
|
const eventBus = new EventBus(projectRoot);
|
|
861
|
-
await eventBus.send(target, message, publisher
|
|
865
|
+
await eventBus.send(target, message, publisher, {
|
|
866
|
+
injectionMode: injection_mode,
|
|
867
|
+
source,
|
|
868
|
+
});
|
|
862
869
|
busBridge.markPending(target);
|
|
863
870
|
log(`bus_send target=${target} publisher=${publisher}`);
|
|
864
871
|
socket.write(
|
|
@@ -908,9 +915,9 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
908
915
|
}
|
|
909
916
|
} else if (result.operation === "start" && result.task) {
|
|
910
917
|
if (result.task.mode === "once") {
|
|
911
|
-
reply = `Cron scheduled ${result.task.id}
|
|
918
|
+
reply = `Cron scheduled ${result.task.id}: ${result.task.label || result.task.onceAt || result.task.onceAtMs}`;
|
|
912
919
|
} else {
|
|
913
|
-
reply = `Cron started ${result.task.id}:
|
|
920
|
+
reply = `Cron started ${result.task.id}: ${result.task.label || result.task.interval || result.task.intervalMs}`;
|
|
914
921
|
}
|
|
915
922
|
} else {
|
|
916
923
|
reply = "Cron updated";
|
|
@@ -6,13 +6,30 @@ const {
|
|
|
6
6
|
consumeControllerInboxEntries,
|
|
7
7
|
} = require("../report/store");
|
|
8
8
|
|
|
9
|
-
function buildPromptWithPrivateReports(prompt = "", reports = []) {
|
|
9
|
+
function buildPromptWithPrivateReports(prompt = "", reports = [], requestMeta = {}) {
|
|
10
|
+
const meta = requestMeta && typeof requestMeta === "object" ? requestMeta : {};
|
|
11
|
+
const hasMeta = Object.keys(meta).length > 0;
|
|
10
12
|
if (!Array.isArray(reports) || reports.length === 0) {
|
|
11
|
-
return prompt;
|
|
13
|
+
if (!hasMeta) return prompt;
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push(prompt || "");
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push("Routing request metadata (JSON):");
|
|
18
|
+
lines.push(JSON.stringify(meta, null, 2));
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push("Honor this metadata when choosing dispatch targets and injection_mode.");
|
|
21
|
+
return lines.join("\n");
|
|
12
22
|
}
|
|
13
23
|
const lines = [];
|
|
14
24
|
lines.push(prompt || "");
|
|
15
25
|
lines.push("");
|
|
26
|
+
if (hasMeta) {
|
|
27
|
+
lines.push("Routing request metadata (JSON):");
|
|
28
|
+
lines.push(JSON.stringify(meta, null, 2));
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("Honor this metadata when choosing dispatch targets and injection_mode.");
|
|
31
|
+
lines.push("");
|
|
32
|
+
}
|
|
16
33
|
lines.push("Private runtime reports for ufoo-agent (JSON):");
|
|
17
34
|
lines.push(JSON.stringify(reports, null, 2));
|
|
18
35
|
lines.push("");
|
|
@@ -40,7 +57,7 @@ async function handlePromptRequest(options = {}) {
|
|
|
40
57
|
|
|
41
58
|
log(`prompt ${String(req.text || "").slice(0, 200)}`);
|
|
42
59
|
const privateReports = listControllerInboxEntries(projectRoot, "ufoo-agent", { num: 100 });
|
|
43
|
-
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports);
|
|
60
|
+
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports, req.request_meta);
|
|
44
61
|
|
|
45
62
|
try {
|
|
46
63
|
const handled = await runPromptWithAssistant({
|
package/src/daemon/status.js
CHANGED
|
@@ -72,6 +72,8 @@ function normalizeCronTasks(raw = []) {
|
|
|
72
72
|
onceAt: String(task && task.onceAt ? task.onceAt : ""),
|
|
73
73
|
targets: Array.isArray(task && task.targets) ? task.targets.slice() : [],
|
|
74
74
|
prompt: String(task && task.prompt ? task.prompt : ""),
|
|
75
|
+
title: String(task && task.title ? task.title : ""),
|
|
76
|
+
label: String(task && task.label ? task.label : ""),
|
|
75
77
|
summary: String(task && task.summary ? task.summary : ""),
|
|
76
78
|
createdAt: Number(task && task.createdAt ? task.createdAt : 0) || 0,
|
|
77
79
|
lastRunAt: Number(task && task.lastRunAt ? task.lastRunAt : 0) || 0,
|