u-foo 1.0.3 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -11
- package/README.zh-CN.md +9 -7
- package/SKILLS/ufoo/SKILL.md +132 -0
- package/SKILLS/uinit/SKILL.md +78 -0
- package/SKILLS/ustatus/SKILL.md +36 -0
- package/bin/uclaude.js +13 -0
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ucodex.js +13 -0
- package/bin/ufoo +9 -31
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +17 -0
- package/modules/AGENTS.template.md +29 -11
- package/modules/bus/README.md +33 -25
- package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
- package/modules/context/README.md +18 -40
- package/modules/context/SKILLS/uctx/SKILL.md +63 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +25 -4
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +30 -0
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +554 -33
- package/src/agent/internalRunner.js +150 -56
- package/src/agent/launcher.js +754 -0
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +340 -0
- package/src/agent/ptyRunner.js +847 -0
- package/src/agent/ptyWrapper.js +379 -0
- package/src/agent/readyDetector.js +175 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +46 -42
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +172 -0
- package/src/bus/daemon.js +436 -0
- package/src/bus/index.js +842 -0
- package/src/bus/inject.js +315 -0
- package/src/bus/message.js +430 -0
- package/src/bus/nickname.js +88 -0
- package/src/bus/queue.js +136 -0
- package/src/bus/shake.js +26 -0
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +312 -0
- package/src/bus/utils.js +363 -0
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +1011 -1392
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +1162 -96
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +56 -3
- package/src/context/decisions.js +324 -0
- package/src/context/doctor.js +183 -0
- package/src/context/index.js +55 -0
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +998 -170
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +630 -48
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +306 -0
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +31 -1
- package/src/daemon/status.js +48 -8
- package/src/doctor/index.js +50 -0
- package/src/init/index.js +318 -0
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/skills/index.js +159 -0
- package/src/status/index.js +285 -0
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/terminal/detect.js +64 -0
- package/src/terminal/index.js +8 -0
- package/src/terminal/iterm2.js +126 -0
- package/src/ufoo/agentsStore.js +107 -0
- package/src/ufoo/paths.js +46 -0
- package/src/utils/banner.js +76 -0
- package/bin/uclaude +0 -65
- package/bin/ucodex +0 -65
- package/modules/bus/scripts/bus-alert.sh +0 -185
- package/modules/bus/scripts/bus-listen.sh +0 -117
- package/modules/context/ASSUMPTIONS.md +0 -7
- package/modules/context/CONSTRAINTS.md +0 -7
- package/modules/context/CONTEXT-STRUCTURE.md +0 -49
- package/modules/context/DECISION-PROTOCOL.md +0 -62
- package/modules/context/HANDOFF.md +0 -33
- package/modules/context/RULES.md +0 -15
- package/modules/context/SKILLS/README.md +0 -14
- package/modules/context/SYSTEM.md +0 -18
- package/modules/context/TEMPLATES/assumptions.md +0 -4
- package/modules/context/TEMPLATES/constraints.md +0 -4
- package/modules/context/TEMPLATES/decision.md +0 -16
- package/modules/context/TEMPLATES/project-context-readme.md +0 -6
- package/modules/context/TEMPLATES/system.md +0 -3
- package/modules/context/TEMPLATES/terminology.md +0 -4
- package/modules/context/TERMINOLOGY.md +0 -10
- package/scripts/banner.sh +0 -89
- package/scripts/bus-alert.sh +0 -6
- package/scripts/bus-autotrigger.sh +0 -6
- package/scripts/bus-daemon.sh +0 -231
- package/scripts/bus-inject.sh +0 -144
- package/scripts/bus-listen.sh +0 -6
- package/scripts/bus.sh +0 -984
- package/scripts/context-decisions.sh +0 -167
- package/scripts/context-doctor.sh +0 -72
- package/scripts/context-lint.sh +0 -110
- package/scripts/doctor.sh +0 -22
- package/scripts/init.sh +0 -247
- package/scripts/skills.sh +0 -113
- package/scripts/status.sh +0 -125
package/src/bus/index.js
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const {
|
|
5
|
+
logInfo,
|
|
6
|
+
logOk,
|
|
7
|
+
ensureDir,
|
|
8
|
+
logWarn,
|
|
9
|
+
logError,
|
|
10
|
+
colors,
|
|
11
|
+
generateInstanceId,
|
|
12
|
+
subscriberToSafeName,
|
|
13
|
+
isPidAlive,
|
|
14
|
+
truncateFile,
|
|
15
|
+
getCurrentTty,
|
|
16
|
+
sleep,
|
|
17
|
+
} = require("./utils");
|
|
18
|
+
const { shakeTerminalByTty } = require("./shake");
|
|
19
|
+
const QueueManager = require("./queue");
|
|
20
|
+
const SubscriberManager = require("./subscriber");
|
|
21
|
+
const MessageManager = require("./message");
|
|
22
|
+
const NicknameManager = require("./nickname");
|
|
23
|
+
const BusDaemon = require("./daemon");
|
|
24
|
+
const Injector = require("./inject");
|
|
25
|
+
const { BusStore } = require("./store");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Event Bus - 项目级 Agent 事件总线
|
|
29
|
+
*/
|
|
30
|
+
class EventBus {
|
|
31
|
+
constructor(projectRoot) {
|
|
32
|
+
this.projectRoot = projectRoot;
|
|
33
|
+
this.store = new BusStore(projectRoot);
|
|
34
|
+
this.paths = this.store.paths;
|
|
35
|
+
this.busDir = this.store.busDir;
|
|
36
|
+
this.agentsFile = this.store.agentsFile;
|
|
37
|
+
this.eventsDir = this.store.eventsDir;
|
|
38
|
+
this.logsDir = this.store.logsDir;
|
|
39
|
+
|
|
40
|
+
this.busData = null;
|
|
41
|
+
this.queueManager = null;
|
|
42
|
+
this.subscriberManager = null;
|
|
43
|
+
this.messageManager = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 确保 bus 已初始化
|
|
48
|
+
*/
|
|
49
|
+
ensureBus() {
|
|
50
|
+
this.store.ensure();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 加载 bus 数据
|
|
55
|
+
*/
|
|
56
|
+
loadBusData() {
|
|
57
|
+
this.busData = this.store.load();
|
|
58
|
+
|
|
59
|
+
this.queueManager = new QueueManager(this.busDir);
|
|
60
|
+
this.subscriberManager = new SubscriberManager(
|
|
61
|
+
this.busData,
|
|
62
|
+
this.queueManager
|
|
63
|
+
);
|
|
64
|
+
this.messageManager = new MessageManager(
|
|
65
|
+
this.busDir,
|
|
66
|
+
this.busData,
|
|
67
|
+
this.queueManager
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// 自动清理不活跃的 agents
|
|
71
|
+
this.subscriberManager.cleanupInactive();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 保存 bus 数据
|
|
76
|
+
*/
|
|
77
|
+
saveBusData() {
|
|
78
|
+
this.store.save(this.busData);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 获取当前订阅者 ID
|
|
83
|
+
*/
|
|
84
|
+
getCurrentSubscriber() {
|
|
85
|
+
return this.store.getCurrentSubscriber(this.busData);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 解析订阅者 ID
|
|
90
|
+
*/
|
|
91
|
+
parseSubscriber(subscriber) {
|
|
92
|
+
if (!subscriber || typeof subscriber !== "string") return null;
|
|
93
|
+
if (subscriber === "ufoo-agent") {
|
|
94
|
+
return { agentType: "codex", sessionId: "ufoo-agent" };
|
|
95
|
+
}
|
|
96
|
+
const parts = subscriber.split(":");
|
|
97
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
98
|
+
return {
|
|
99
|
+
agentType: parts[0],
|
|
100
|
+
sessionId: parts[1],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 推断 join 所需的 agentType
|
|
106
|
+
*/
|
|
107
|
+
resolveJoinAgentType(explicitAgentType, currentSubscriber = "") {
|
|
108
|
+
if (explicitAgentType) return explicitAgentType;
|
|
109
|
+
|
|
110
|
+
const parsedCurrent = this.parseSubscriber(currentSubscriber);
|
|
111
|
+
if (parsedCurrent && parsedCurrent.agentType) return parsedCurrent.agentType;
|
|
112
|
+
|
|
113
|
+
const envAgentType = (process.env.UFOO_AGENT_TYPE || "").trim();
|
|
114
|
+
if (envAgentType) return envAgentType;
|
|
115
|
+
|
|
116
|
+
const parsedEnv = this.parseSubscriber(process.env.UFOO_SUBSCRIBER_ID || "");
|
|
117
|
+
if (parsedEnv && parsedEnv.agentType) return parsedEnv.agentType;
|
|
118
|
+
|
|
119
|
+
// 最后回退(手动场景)
|
|
120
|
+
return "claude-code";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 初始化事件总线
|
|
125
|
+
*/
|
|
126
|
+
async init() {
|
|
127
|
+
this.store.init();
|
|
128
|
+
logOk("Event bus initialized");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 加入总线
|
|
133
|
+
*/
|
|
134
|
+
async join(sessionId, agentType, nickname = null, options = {}) {
|
|
135
|
+
this.ensureBus();
|
|
136
|
+
this.loadBusData();
|
|
137
|
+
|
|
138
|
+
const currentSubscriber = this.getCurrentSubscriber();
|
|
139
|
+
const currentMeta = currentSubscriber && this.busData.agents
|
|
140
|
+
? this.busData.agents[currentSubscriber]
|
|
141
|
+
: null;
|
|
142
|
+
const currentActive = currentMeta
|
|
143
|
+
&& currentMeta.status === "active"
|
|
144
|
+
&& (!currentMeta.pid || isPidAlive(currentMeta.pid));
|
|
145
|
+
|
|
146
|
+
// 已在总线中且无显式参数时,直接复用当前身份(避免二次 join 产生新 ID)
|
|
147
|
+
if (!sessionId && !agentType && currentSubscriber && currentActive) {
|
|
148
|
+
this.subscriberManager.updateLastSeen(currentSubscriber);
|
|
149
|
+
this.saveBusData();
|
|
150
|
+
const currentNickname = currentMeta.nickname ? ` (${currentMeta.nickname})` : "";
|
|
151
|
+
logInfo(`Already joined event bus: ${currentSubscriber}${currentNickname}`);
|
|
152
|
+
return currentSubscriber;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 自动检测 session ID 和 agent type
|
|
156
|
+
const parsedCurrent = this.parseSubscriber(currentSubscriber);
|
|
157
|
+
if (!sessionId && parsedCurrent && parsedCurrent.sessionId) {
|
|
158
|
+
sessionId = parsedCurrent.sessionId;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!sessionId) {
|
|
162
|
+
sessionId = generateInstanceId();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
agentType = this.resolveJoinAgentType(agentType, currentSubscriber);
|
|
166
|
+
|
|
167
|
+
const result = await this.subscriberManager.join(
|
|
168
|
+
sessionId,
|
|
169
|
+
agentType,
|
|
170
|
+
nickname,
|
|
171
|
+
options
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
this.saveBusData();
|
|
175
|
+
|
|
176
|
+
logOk(
|
|
177
|
+
`Joined event bus: ${result.subscriber}${result.nickname ? ` (${result.nickname})` : ""}`
|
|
178
|
+
);
|
|
179
|
+
return result.subscriber;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 离开总线
|
|
184
|
+
*/
|
|
185
|
+
async leave(subscriber) {
|
|
186
|
+
this.ensureBus();
|
|
187
|
+
this.loadBusData();
|
|
188
|
+
|
|
189
|
+
const success = await this.subscriberManager.leave(subscriber);
|
|
190
|
+
|
|
191
|
+
if (success) {
|
|
192
|
+
this.saveBusData();
|
|
193
|
+
logOk(`Left event bus: ${subscriber}`);
|
|
194
|
+
} else {
|
|
195
|
+
logError(`Subscriber not found: ${subscriber}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return success;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 重命名订阅者
|
|
203
|
+
*/
|
|
204
|
+
async rename(subscriber, newNickname, publisher = null) {
|
|
205
|
+
this.ensureBus();
|
|
206
|
+
this.loadBusData();
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const result = await this.subscriberManager.rename(
|
|
210
|
+
subscriber,
|
|
211
|
+
newNickname
|
|
212
|
+
);
|
|
213
|
+
this.saveBusData();
|
|
214
|
+
const pub = publisher || this.getDefaultPublisher() || "unknown";
|
|
215
|
+
try {
|
|
216
|
+
await this.messageManager.emit(
|
|
217
|
+
"*",
|
|
218
|
+
"agent_renamed",
|
|
219
|
+
{
|
|
220
|
+
agent_id: result.subscriber,
|
|
221
|
+
old_nickname: result.oldNickname,
|
|
222
|
+
new_nickname: result.newNickname,
|
|
223
|
+
},
|
|
224
|
+
pub
|
|
225
|
+
);
|
|
226
|
+
} catch {
|
|
227
|
+
// ignore event emit failures
|
|
228
|
+
}
|
|
229
|
+
logOk(
|
|
230
|
+
`Renamed ${result.subscriber}: "${result.oldNickname}" -> "${result.newNickname}"`
|
|
231
|
+
);
|
|
232
|
+
return result;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logError(err.message);
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 获取当前订阅者 ID
|
|
241
|
+
*/
|
|
242
|
+
async whoami() {
|
|
243
|
+
this.ensureBus();
|
|
244
|
+
this.loadBusData();
|
|
245
|
+
|
|
246
|
+
// 优先使用 UFOO_SUBSCRIBER_ID(daemon 启动的情况)
|
|
247
|
+
if (process.env.UFOO_SUBSCRIBER_ID) {
|
|
248
|
+
const subscriber = process.env.UFOO_SUBSCRIBER_ID;
|
|
249
|
+
const meta = this.subscriberManager.getSubscriber(subscriber);
|
|
250
|
+
|
|
251
|
+
if (meta) {
|
|
252
|
+
console.log(subscriber);
|
|
253
|
+
return subscriber;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
logError("Not joined to bus. Please run: ufoo bus join");
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 发送消息
|
|
263
|
+
*/
|
|
264
|
+
async send(target, message, publisher = null, options = {}) {
|
|
265
|
+
this.ensureBus();
|
|
266
|
+
this.loadBusData();
|
|
267
|
+
|
|
268
|
+
// 自动检测 publisher
|
|
269
|
+
if (!publisher) {
|
|
270
|
+
publisher =
|
|
271
|
+
process.env.AI_BUS_PUBLISHER ||
|
|
272
|
+
this.getDefaultPublisher() ||
|
|
273
|
+
this.getCurrentSubscriber() ||
|
|
274
|
+
"unknown";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 如果 publisher 还是 unknown,尝试从命令行参数或环境推断
|
|
278
|
+
if (publisher === "unknown") {
|
|
279
|
+
// 尝试从 tty 查找可能的 subscriber
|
|
280
|
+
const possibleSubscriber = this.getCurrentSubscriber();
|
|
281
|
+
if (possibleSubscriber) {
|
|
282
|
+
publisher = possibleSubscriber;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 如果 publisher 不在 agents 列表中,自动注册它(懒加载模式)
|
|
287
|
+
if (publisher !== "unknown" && this.busData.agents && !this.busData.agents[publisher]) {
|
|
288
|
+
// 解析 agent 信息
|
|
289
|
+
const parts = publisher.split(":");
|
|
290
|
+
const agentType = parts[0] || "unknown-agent";
|
|
291
|
+
const sessionId = parts[1] || require("./utils").generateInstanceId();
|
|
292
|
+
|
|
293
|
+
// 自动加入总线(静默模式,不输出日志)
|
|
294
|
+
const subscriber = await this.subscriberManager.join(sessionId, agentType, null);
|
|
295
|
+
this.saveBusData();
|
|
296
|
+
publisher = subscriber; // 使用规范化的 subscriber ID
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 更新 publisher 的心跳
|
|
300
|
+
if (publisher !== "unknown" && this.busData.agents && this.busData.agents[publisher]) {
|
|
301
|
+
this.subscriberManager.updateLastSeen(publisher);
|
|
302
|
+
this.saveBusData();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const eventName = options.event || "message";
|
|
307
|
+
const data = options.data || { message };
|
|
308
|
+
const result = eventName === "message"
|
|
309
|
+
? await this.messageManager.send(target, message, publisher)
|
|
310
|
+
: await this.messageManager.emit(target, eventName, data, publisher);
|
|
311
|
+
logOk(
|
|
312
|
+
`Message sent: seq=${result.seq} -> ${result.targets.join(", ")}`
|
|
313
|
+
);
|
|
314
|
+
return result;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
logError(err.message);
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 广播消息
|
|
323
|
+
*/
|
|
324
|
+
async broadcast(message, publisher = null, options = {}) {
|
|
325
|
+
return this.send("*", message, publisher, options);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 检查待处理消息
|
|
330
|
+
*/
|
|
331
|
+
async check(subscriber, autoAck = false) {
|
|
332
|
+
this.ensureBus();
|
|
333
|
+
this.loadBusData();
|
|
334
|
+
|
|
335
|
+
// 更新心跳
|
|
336
|
+
this.subscriberManager.updateLastSeen(subscriber);
|
|
337
|
+
this.saveBusData();
|
|
338
|
+
|
|
339
|
+
const pending = await this.messageManager.check(subscriber);
|
|
340
|
+
|
|
341
|
+
if (pending.length === 0) {
|
|
342
|
+
logOk("No pending messages");
|
|
343
|
+
return pending;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
logWarn(`You have ${pending.length} pending event(s):`);
|
|
347
|
+
console.log();
|
|
348
|
+
|
|
349
|
+
for (const event of pending) {
|
|
350
|
+
console.log(` ${colors.yellow}@you${colors.reset} from ${colors.cyan}${event.publisher}${colors.reset}`);
|
|
351
|
+
console.log(` Type: ${event.type}/${event.event}`);
|
|
352
|
+
console.log(` Content: ${JSON.stringify(event.data)}`);
|
|
353
|
+
console.log();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(`${colors.cyan}After handling, run: ufoo bus ack ${subscriber}${colors.reset}`);
|
|
357
|
+
|
|
358
|
+
if (autoAck) {
|
|
359
|
+
await this.ack(subscriber);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return pending;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* 确认消息
|
|
367
|
+
*/
|
|
368
|
+
async ack(subscriber) {
|
|
369
|
+
this.ensureBus();
|
|
370
|
+
this.loadBusData();
|
|
371
|
+
|
|
372
|
+
const count = await this.messageManager.ack(subscriber);
|
|
373
|
+
|
|
374
|
+
if (count > 0) {
|
|
375
|
+
logOk(`Acknowledged and cleared ${count} message(s)`);
|
|
376
|
+
} else {
|
|
377
|
+
logOk("No pending messages to acknowledge");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return count;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* 消费事件
|
|
385
|
+
*/
|
|
386
|
+
async consume(subscriber, fromBeginning = false) {
|
|
387
|
+
this.ensureBus();
|
|
388
|
+
this.loadBusData();
|
|
389
|
+
|
|
390
|
+
const result = await this.messageManager.consume(subscriber, fromBeginning);
|
|
391
|
+
|
|
392
|
+
for (const event of result.consumed) {
|
|
393
|
+
console.log(JSON.stringify(event));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
logInfo(`Consumed ${result.consumed.length} events, new offset: ${result.newOffset}`);
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 查看总线状态
|
|
403
|
+
*/
|
|
404
|
+
async status() {
|
|
405
|
+
this.ensureBus();
|
|
406
|
+
this.loadBusData();
|
|
407
|
+
|
|
408
|
+
// 清理不活跃的订阅者
|
|
409
|
+
this.subscriberManager.cleanupInactive();
|
|
410
|
+
|
|
411
|
+
// 尝试获取当前 subscriber 并更新 last_seen + 重新激活(保持心跳)
|
|
412
|
+
const currentSubscriber = this.getCurrentSubscriber();
|
|
413
|
+
if (currentSubscriber && this.busData.agents && this.busData.agents[currentSubscriber]) {
|
|
414
|
+
this.subscriberManager.updateLastSeen(currentSubscriber);
|
|
415
|
+
this.busData.agents[currentSubscriber].status = "active";
|
|
416
|
+
this.saveBusData();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.log(`${colors.cyan}=== Event Bus Status ===${colors.reset}`);
|
|
420
|
+
console.log();
|
|
421
|
+
|
|
422
|
+
// 显示 bus ID
|
|
423
|
+
const busId = path.basename(this.projectRoot) || "ai-workspace";
|
|
424
|
+
console.log(`Bus ID: ${busId}`);
|
|
425
|
+
console.log();
|
|
426
|
+
|
|
427
|
+
// 显示在线订阅者
|
|
428
|
+
const active = this.subscriberManager.getActiveSubscribers();
|
|
429
|
+
console.log(`${colors.cyan}Online agents:${colors.reset}`);
|
|
430
|
+
if (active.length === 0) {
|
|
431
|
+
console.log(" (none)");
|
|
432
|
+
} else {
|
|
433
|
+
for (const sub of active) {
|
|
434
|
+
const nickname = sub.nickname ? ` (${sub.nickname})` : "";
|
|
435
|
+
console.log(` ${sub.id}${nickname}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
console.log();
|
|
439
|
+
|
|
440
|
+
// 显示事件统计
|
|
441
|
+
console.log(`${colors.cyan}Event statistics:${colors.reset}`);
|
|
442
|
+
if (fs.existsSync(this.eventsDir)) {
|
|
443
|
+
const files = fs.readdirSync(this.eventsDir)
|
|
444
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
445
|
+
.sort();
|
|
446
|
+
|
|
447
|
+
let totalEvents = 0;
|
|
448
|
+
for (const file of files) {
|
|
449
|
+
const filePath = path.join(this.eventsDir, file);
|
|
450
|
+
const lines = fs.readFileSync(filePath, "utf8").trim().split("\n").filter(Boolean);
|
|
451
|
+
const count = lines.length;
|
|
452
|
+
totalEvents += count;
|
|
453
|
+
console.log(` ${file}: ${count} events`);
|
|
454
|
+
}
|
|
455
|
+
console.log(` Total: ${totalEvents} events`);
|
|
456
|
+
} else {
|
|
457
|
+
console.log(" (no events yet)");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return { active, busId };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 智能路由
|
|
465
|
+
*/
|
|
466
|
+
async resolve(myId, targetType) {
|
|
467
|
+
this.ensureBus();
|
|
468
|
+
this.loadBusData();
|
|
469
|
+
|
|
470
|
+
const result = await this.messageManager.resolve(myId, targetType);
|
|
471
|
+
|
|
472
|
+
if (result.single) {
|
|
473
|
+
console.log(result.single);
|
|
474
|
+
return result.single;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (result.candidates.length === 0) {
|
|
478
|
+
logError(`No ${targetType} agents found`);
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log(`Multiple ${targetType} agents found:`);
|
|
483
|
+
for (const candidate of result.candidates) {
|
|
484
|
+
const nickname = candidate.nickname ? ` (${candidate.nickname})` : "";
|
|
485
|
+
console.log(` ${candidate.id}${nickname}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 获取默认发布者
|
|
493
|
+
*/
|
|
494
|
+
getDefaultPublisher() {
|
|
495
|
+
// 使用 UFOO_SUBSCRIBER_ID(daemon 启动的情况)
|
|
496
|
+
return process.env.UFOO_SUBSCRIBER_ID || null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 确保当前 agent 已经 join 总线(如果没有则自动 join)
|
|
501
|
+
*/
|
|
502
|
+
async ensureJoined() {
|
|
503
|
+
this.ensureBus();
|
|
504
|
+
this.loadBusData();
|
|
505
|
+
|
|
506
|
+
// 检查是否已经 join
|
|
507
|
+
const currentSubscriber = this.getCurrentSubscriber();
|
|
508
|
+
const currentMeta = currentSubscriber && this.busData.agents
|
|
509
|
+
? this.busData.agents[currentSubscriber]
|
|
510
|
+
: null;
|
|
511
|
+
const currentActive = currentMeta
|
|
512
|
+
&& currentMeta.status === "active"
|
|
513
|
+
&& (!currentMeta.pid || isPidAlive(currentMeta.pid));
|
|
514
|
+
if (currentSubscriber && currentActive) {
|
|
515
|
+
// 已经 join,只需更新心跳
|
|
516
|
+
this.subscriberManager.updateLastSeen(currentSubscriber);
|
|
517
|
+
this.saveBusData();
|
|
518
|
+
return currentSubscriber;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 当前身份可解析但元数据缺失/失效时,复用同一身份重新注册
|
|
522
|
+
const parsedCurrent = this.parseSubscriber(currentSubscriber || "");
|
|
523
|
+
if (parsedCurrent) {
|
|
524
|
+
return this.join(parsedCurrent.sessionId, parsedCurrent.agentType, null);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// 还没有 join,自动 join
|
|
528
|
+
const sessionId = null; // 自动生成
|
|
529
|
+
const agentType = null; // 自动检测
|
|
530
|
+
const nickname = null; // 自动生成
|
|
531
|
+
const subscriber = await this.join(sessionId, agentType, nickname);
|
|
532
|
+
|
|
533
|
+
// 静默加入(不输出 "Joined event bus" 信息)
|
|
534
|
+
return subscriber;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 后台消息提醒
|
|
539
|
+
*/
|
|
540
|
+
async alert(subscriber, intervalSeconds = 2, options = {}) {
|
|
541
|
+
this.ensureBus();
|
|
542
|
+
this.loadBusData();
|
|
543
|
+
|
|
544
|
+
if (!subscriber) {
|
|
545
|
+
throw new Error("alert requires <subscriber-id>");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const interval = Math.max(1, parseInt(intervalSeconds, 10) || 2);
|
|
549
|
+
const intervalMs = interval * 1000;
|
|
550
|
+
const useNotify = Boolean(options.notify);
|
|
551
|
+
const useTitle = options.title !== false;
|
|
552
|
+
const useBell = options.bell !== false;
|
|
553
|
+
const daemon = Boolean(options.daemon);
|
|
554
|
+
const stop = Boolean(options.stop);
|
|
555
|
+
|
|
556
|
+
const safeName = subscriberToSafeName(subscriber);
|
|
557
|
+
const pidDir = path.join(this.busDir, "pids");
|
|
558
|
+
const pidFile = path.join(pidDir, `alert-${safeName}.pid`);
|
|
559
|
+
const logDir = path.join(this.busDir, "logs");
|
|
560
|
+
const logFile = path.join(logDir, `alert-${safeName}.log`);
|
|
561
|
+
|
|
562
|
+
ensureDir(pidDir);
|
|
563
|
+
|
|
564
|
+
if (stop) {
|
|
565
|
+
if (fs.existsSync(pidFile)) {
|
|
566
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
567
|
+
if (pid && isPidAlive(pid)) {
|
|
568
|
+
try {
|
|
569
|
+
process.kill(pid);
|
|
570
|
+
console.log(`[alert] Stopped ${subscriber} (pid=${pid})`);
|
|
571
|
+
} catch {
|
|
572
|
+
console.log("[alert] Not running");
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
console.log(`[alert] Not running for ${subscriber}`);
|
|
576
|
+
}
|
|
577
|
+
fs.rmSync(pidFile, { force: true });
|
|
578
|
+
} else {
|
|
579
|
+
console.log(`[alert] Not running for ${subscriber}`);
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (daemon) {
|
|
585
|
+
if (fs.existsSync(pidFile)) {
|
|
586
|
+
const existing = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
587
|
+
if (existing && isPidAlive(existing)) {
|
|
588
|
+
console.log(`[alert] Already running for ${subscriber} (pid=${existing})`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
ensureDir(logDir);
|
|
594
|
+
|
|
595
|
+
const args = [
|
|
596
|
+
path.join(__dirname, "..", "..", "bin", "ufoo.js"),
|
|
597
|
+
"bus",
|
|
598
|
+
"alert",
|
|
599
|
+
subscriber,
|
|
600
|
+
String(interval),
|
|
601
|
+
];
|
|
602
|
+
if (useNotify) args.push("--notify");
|
|
603
|
+
if (!useTitle) args.push("--no-title");
|
|
604
|
+
if (!useBell) args.push("--no-bell");
|
|
605
|
+
|
|
606
|
+
const logStream = fs.openSync(logFile, "a");
|
|
607
|
+
const child = spawn(process.execPath, args, {
|
|
608
|
+
detached: true,
|
|
609
|
+
stdio: ["ignore", logStream, logStream],
|
|
610
|
+
cwd: process.cwd(),
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
child.unref();
|
|
614
|
+
fs.writeFileSync(pidFile, `${child.pid}\n`, "utf8");
|
|
615
|
+
console.log(`[alert] Started for ${subscriber} (pid=${child.pid}, log=${logFile})`);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
fs.writeFileSync(pidFile, `${process.pid}\n`, "utf8");
|
|
620
|
+
const cleanup = () => {
|
|
621
|
+
if (fs.existsSync(pidFile)) fs.rmSync(pidFile, { force: true });
|
|
622
|
+
};
|
|
623
|
+
process.on("exit", cleanup);
|
|
624
|
+
process.on("SIGINT", () => {
|
|
625
|
+
cleanup();
|
|
626
|
+
process.exit(0);
|
|
627
|
+
});
|
|
628
|
+
process.on("SIGTERM", () => {
|
|
629
|
+
cleanup();
|
|
630
|
+
process.exit(0);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
const queuePath = this.queueManager.getPendingPath(subscriber);
|
|
634
|
+
this.queueManager.ensureQueueDir(subscriber);
|
|
635
|
+
|
|
636
|
+
const countLines = () => {
|
|
637
|
+
if (!fs.existsSync(queuePath)) return 0;
|
|
638
|
+
const content = fs.readFileSync(queuePath, "utf8").trim();
|
|
639
|
+
if (!content) return 0;
|
|
640
|
+
return content.split("\n").filter(Boolean).length;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
let lastCount = countLines();
|
|
644
|
+
console.log(`[alert] Watching ${subscriber} (interval=${interval}s)`);
|
|
645
|
+
|
|
646
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
647
|
+
|
|
648
|
+
while (true) {
|
|
649
|
+
const count = countLines();
|
|
650
|
+
if (count > lastCount) {
|
|
651
|
+
const newCount = count - lastCount;
|
|
652
|
+
const now = new Date().toISOString().split("T")[1].slice(0, 8);
|
|
653
|
+
console.log(`[alert] ${now} +${newCount} new message(s)`);
|
|
654
|
+
|
|
655
|
+
if (useBell) {
|
|
656
|
+
const tty = getCurrentTty();
|
|
657
|
+
if (tty) shakeTerminalByTty(tty);
|
|
658
|
+
}
|
|
659
|
+
if (useTitle) {
|
|
660
|
+
process.stdout.write(`\x1b]0;[${count}] ${subscriber}\x07`);
|
|
661
|
+
}
|
|
662
|
+
if (useNotify && process.platform === "darwin") {
|
|
663
|
+
const message = `${newCount} new message(s)`;
|
|
664
|
+
spawn(
|
|
665
|
+
"osascript",
|
|
666
|
+
[
|
|
667
|
+
"-e",
|
|
668
|
+
`display notification "${message}" with title "ufoo bus" subtitle "${subscriber}"`,
|
|
669
|
+
],
|
|
670
|
+
{ detached: true, stdio: "ignore" }
|
|
671
|
+
).unref();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (useTitle && count > 0) {
|
|
676
|
+
process.stdout.write(`\x1b]0;[${count}] ${subscriber}\x07`);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
lastCount = count;
|
|
680
|
+
await sleep(intervalMs);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* 远程唤醒本地 agent(触发 /ubus 注入)
|
|
686
|
+
*/
|
|
687
|
+
async wake(subscriber, options = {}) {
|
|
688
|
+
this.ensureBus();
|
|
689
|
+
this.loadBusData();
|
|
690
|
+
|
|
691
|
+
const publisher =
|
|
692
|
+
options.publisher ||
|
|
693
|
+
process.env.AI_BUS_PUBLISHER ||
|
|
694
|
+
this.getDefaultPublisher() ||
|
|
695
|
+
this.getCurrentSubscriber() ||
|
|
696
|
+
"unknown";
|
|
697
|
+
|
|
698
|
+
const targets = this.messageManager.resolveTarget(subscriber || "");
|
|
699
|
+
if (targets.length === 0) {
|
|
700
|
+
throw new Error(`Target "${subscriber}" not found`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
for (const target of targets) {
|
|
704
|
+
const safeName = subscriberToSafeName(target);
|
|
705
|
+
const queueDir = path.join(this.busDir, "queues", safeName);
|
|
706
|
+
const pendingFile = path.join(queueDir, "pending.jsonl");
|
|
707
|
+
ensureDir(queueDir);
|
|
708
|
+
|
|
709
|
+
const before = fs.existsSync(pendingFile) ? fs.readFileSync(pendingFile, "utf8") : "";
|
|
710
|
+
const countBefore = before.trim() ? before.trim().split(/\r?\n/).length : 0;
|
|
711
|
+
await this.messageManager.emit(target, "wake", { reason: options.reason || "remote" }, publisher, "status/wake");
|
|
712
|
+
const after = fs.existsSync(pendingFile) ? fs.readFileSync(pendingFile, "utf8") : "";
|
|
713
|
+
const countAfter = after.trim() ? after.trim().split(/\r?\n/).length : 0;
|
|
714
|
+
|
|
715
|
+
if (countAfter > countBefore) {
|
|
716
|
+
await sleep(50);
|
|
717
|
+
const daemon = new BusDaemon(this.busDir, this.agentsFile, this.paths.busDaemonDir, 2000);
|
|
718
|
+
await daemon.injector.inject(target, options.command || "");
|
|
719
|
+
if (options.shake !== false) {
|
|
720
|
+
const tty = daemon.injector.readTty(target);
|
|
721
|
+
if (tty) shakeTerminalByTty(tty, { skipFrontmost: true });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
logOk(`Wake sent -> ${targets.join(", ")}`);
|
|
727
|
+
return { ok: true, targets };
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* 前台消息监听
|
|
732
|
+
*/
|
|
733
|
+
async listen(subscriber, options = {}) {
|
|
734
|
+
this.ensureBus();
|
|
735
|
+
this.loadBusData();
|
|
736
|
+
|
|
737
|
+
let target = subscriber;
|
|
738
|
+
if (!target && options.autoJoin) {
|
|
739
|
+
target = await this.join();
|
|
740
|
+
console.log(`[listen] Auto-joined as: ${target}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (!target) {
|
|
744
|
+
throw new Error("listen requires <subscriber-id> (or --auto-join)");
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const queuePath = this.queueManager.getPendingPath(target);
|
|
748
|
+
this.queueManager.ensureQueueDir(target);
|
|
749
|
+
if (!fs.existsSync(queuePath)) {
|
|
750
|
+
fs.writeFileSync(queuePath, "", "utf8");
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (options.reset) {
|
|
754
|
+
console.log("[listen] Resetting queue...");
|
|
755
|
+
truncateFile(queuePath);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const readLines = () => {
|
|
759
|
+
if (!fs.existsSync(queuePath)) return [];
|
|
760
|
+
const content = fs.readFileSync(queuePath, "utf8").trim();
|
|
761
|
+
if (!content) return [];
|
|
762
|
+
return content.split("\n").filter(Boolean);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const formatLine = (line) => {
|
|
766
|
+
let data = null;
|
|
767
|
+
try {
|
|
768
|
+
data = JSON.parse(line);
|
|
769
|
+
} catch {
|
|
770
|
+
data = null;
|
|
771
|
+
}
|
|
772
|
+
const msg = data?.data?.message ?? data?.data ?? line;
|
|
773
|
+
const from = data?.publisher ?? "unknown";
|
|
774
|
+
const ts = data?.ts || data?.timestamp || "";
|
|
775
|
+
const shortTs = ts ? ts.slice(11, 19) : "";
|
|
776
|
+
const prefix = shortTs ? `[${shortTs}] ` : "";
|
|
777
|
+
console.log(`${prefix}<${from}> ${msg}`);
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
if (options.fromBeginning) {
|
|
781
|
+
const lines = readLines();
|
|
782
|
+
if (lines.length > 0) {
|
|
783
|
+
console.log("[listen] Existing messages:");
|
|
784
|
+
console.log("---");
|
|
785
|
+
lines.forEach((line) => formatLine(line));
|
|
786
|
+
console.log("---");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
console.log("[listen] Listening for new messages... (Ctrl+C to stop)");
|
|
791
|
+
|
|
792
|
+
let lastLines = readLines().length;
|
|
793
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
794
|
+
|
|
795
|
+
while (true) {
|
|
796
|
+
const lines = readLines();
|
|
797
|
+
if (lines.length > lastLines) {
|
|
798
|
+
const newLines = lines.slice(lastLines);
|
|
799
|
+
const tty = getCurrentTty();
|
|
800
|
+
if (tty) shakeTerminalByTty(tty);
|
|
801
|
+
newLines.forEach((line) => {
|
|
802
|
+
formatLine(line);
|
|
803
|
+
});
|
|
804
|
+
lastLines = lines.length;
|
|
805
|
+
}
|
|
806
|
+
await sleep(1000);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Daemon 管理
|
|
812
|
+
*/
|
|
813
|
+
async daemon(action, options = {}) {
|
|
814
|
+
const interval = options.interval || 2000;
|
|
815
|
+
const daemon = new BusDaemon(this.busDir, this.agentsFile, this.paths.busDaemonDir, interval);
|
|
816
|
+
|
|
817
|
+
switch (action) {
|
|
818
|
+
case "start":
|
|
819
|
+
await daemon.start(options.background || false);
|
|
820
|
+
break;
|
|
821
|
+
case "stop":
|
|
822
|
+
daemon.stop();
|
|
823
|
+
break;
|
|
824
|
+
case "status":
|
|
825
|
+
daemon.status();
|
|
826
|
+
break;
|
|
827
|
+
default:
|
|
828
|
+
throw new Error(`Unknown daemon action: ${action}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* 注入命令到订阅者终端
|
|
834
|
+
*/
|
|
835
|
+
async inject(subscriber, commandOverride = "") {
|
|
836
|
+
this.ensureBus();
|
|
837
|
+
const injector = new Injector(this.busDir, this.agentsFile);
|
|
838
|
+
await injector.inject(subscriber, commandOverride);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
module.exports = EventBus;
|