u-foo 1.7.3 → 1.7.4
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 +5 -2
- 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 +28 -5
- package/src/chat/index.js +2 -0
- package/src/chat/inputSubmitHandler.js +28 -3
- package/src/cli/busCoreCommands.js +44 -1
- package/src/daemon/index.js +11 -4
- package/src/daemon/promptRequest.js +20 -3
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,7 +274,7 @@ 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"}],',
|
|
277
|
+
' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string","injection_mode":"immediate|queued (optional)","source":"optional"}],',
|
|
278
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"}],',
|
|
279
279
|
' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}',
|
|
280
280
|
"}",
|
|
@@ -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/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
|
}
|
package/src/chat/index.js
CHANGED
|
@@ -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
|
}
|
|
@@ -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/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(
|
|
@@ -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({
|