u-foo 1.8.4 → 1.8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "1.8.4",
3
+ "version": "1.8.5",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
package/src/bus/index.js CHANGED
@@ -285,13 +285,19 @@ class EventBus {
285
285
 
286
286
  // 如果 publisher 不在 agents 列表中,自动注册它(懒加载模式)
287
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();
288
+ let subscriber;
289
+ if (publisher === "ufoo-agent") {
290
+ // Keep the reserved controller on its fixed identity even after metadata loss.
291
+ subscriber = (await this.subscriberManager.join("ufoo-agent", "ufoo-agent", "ufoo-agent")).subscriber;
292
+ } else {
293
+ // 解析 agent 信息
294
+ const parts = publisher.split(":");
295
+ const agentType = parts[0] || "unknown-agent";
296
+ const sessionId = parts[1] || require("./utils").generateInstanceId();
292
297
 
293
- // 自动加入总线(静默模式,不输出日志)
294
- const subscriber = await this.subscriberManager.join(sessionId, agentType, null);
298
+ // 自动加入总线(静默模式,不输出日志)
299
+ subscriber = (await this.subscriberManager.join(sessionId, agentType, null)).subscriber;
300
+ }
295
301
  this.saveBusData();
296
302
  publisher = subscriber; // 使用规范化的 subscriber ID
297
303
  }
@@ -178,6 +178,7 @@ class MessageManager {
178
178
  resolveTarget(target) {
179
179
  const nicknameManager = new NicknameManager(this.busData);
180
180
  const normalizedTarget = normalizeAgentTypeAlias(target);
181
+ const controllerTarget = target === "ufoo-agent";
181
182
 
182
183
  // 0. Exact subscriber ID match (allows ids without ":" e.g. "ufoo-agent")
183
184
  const subscribers = this.busData.agents || {};
@@ -185,6 +186,21 @@ class MessageManager {
185
186
  return [target];
186
187
  }
187
188
 
189
+ // Reserved controller ID should never fan out to legacy typed aliases.
190
+ if (controllerTarget) {
191
+ try {
192
+ const queueDir = this.queueManager && this.queueManager.getQueueDir
193
+ ? this.queueManager.getQueueDir(target)
194
+ : path.join(this.busDir, "queues", target);
195
+ if (queueDir && fs.existsSync(queueDir)) {
196
+ return [target];
197
+ }
198
+ } catch {
199
+ // ignore queue lookup errors
200
+ }
201
+ return [];
202
+ }
203
+
188
204
  // 1. 尝试作为订阅者 ID
189
205
  if (target.includes(":")) {
190
206
  return [target];
package/src/bus/store.js CHANGED
@@ -47,6 +47,75 @@ function allocateRecoveredNickname(agentType, used) {
47
47
  return nick;
48
48
  }
49
49
 
50
+ function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) {
51
+ if (!subscriber || data.agents[subscriber]) return false;
52
+
53
+ if (subscriber === "ufoo-agent") {
54
+ const tty = readQueueTty(queueDir);
55
+ const ttyInfo = tty ? getTtyProcessInfo(tty) : null;
56
+ data.agents[subscriber] = {
57
+ agent_type: "ufoo-agent",
58
+ nickname: "ufoo-agent",
59
+ status: "active",
60
+ joined_at: now,
61
+ last_seen: now,
62
+ pid: 0,
63
+ tty,
64
+ tty_shell_pid: ttyInfo && ttyInfo.shellPid ? ttyInfo.shellPid : 0,
65
+ tmux_pane: "",
66
+ launch_mode: "",
67
+ };
68
+ return true;
69
+ }
70
+
71
+ const parts = subscriber.split(":");
72
+ if (parts.length !== 2) return false;
73
+ const [agentType, sessionId] = parts;
74
+ if (!agentType || !sessionId) return false;
75
+ if (!isRecoverableSessionId(sessionId)) return false;
76
+
77
+ const tty = readQueueTty(queueDir);
78
+ const ttyInfo = tty ? getTtyProcessInfo(tty) : null;
79
+ const activeByTty = Boolean(ttyInfo && ttyInfo.alive && ttyInfo.hasAgent);
80
+ const nickname = activeByTty ? allocateRecoveredNickname(agentType, usedNicknames) : "";
81
+
82
+ data.agents[subscriber] = {
83
+ agent_type: agentType,
84
+ nickname,
85
+ status: activeByTty ? "active" : "inactive",
86
+ joined_at: now,
87
+ last_seen: now,
88
+ pid: 0,
89
+ tty,
90
+ tty_shell_pid: ttyInfo && ttyInfo.shellPid ? ttyInfo.shellPid : 0,
91
+ tmux_pane: "",
92
+ launch_mode: "",
93
+ };
94
+ return true;
95
+ }
96
+
97
+ function reconcileReservedControllerAliases(data, now) {
98
+ if (!data.agents || !data.agents["ufoo-agent"]) return false;
99
+
100
+ let changed = false;
101
+ for (const [id, meta] of Object.entries(data.agents)) {
102
+ if (!id.startsWith("ufoo-agent:")) continue;
103
+ if (!meta || meta.status !== "active") continue;
104
+ if (String(meta.agent_type || "").trim() !== "ufoo-agent") continue;
105
+ const hasRuntimeBinding = Boolean(
106
+ meta.tty
107
+ || meta.tmux_pane
108
+ || meta.host_inject_sock
109
+ || meta.host_daemon_sock
110
+ );
111
+ if (hasRuntimeBinding) continue;
112
+ meta.status = "inactive";
113
+ meta.last_seen = now;
114
+ changed = true;
115
+ }
116
+ return changed;
117
+ }
118
+
50
119
  class BusStore {
51
120
  constructor(projectRoot) {
52
121
  this.projectRoot = projectRoot;
@@ -89,33 +158,11 @@ class BusStore {
89
158
  if (!stat.isDirectory()) continue;
90
159
 
91
160
  const subscriber = safeNameToSubscriber(entry);
92
- const parts = subscriber.split(":");
93
- if (parts.length !== 2) continue;
94
- const [agentType, sessionId] = parts;
95
- if (!agentType || !sessionId) continue;
96
- if (!isRecoverableSessionId(sessionId)) continue;
97
- if (data.agents[subscriber]) continue;
98
-
99
- const tty = readQueueTty(queueDir);
100
- const ttyInfo = tty ? getTtyProcessInfo(tty) : null;
101
- const activeByTty = Boolean(ttyInfo && ttyInfo.alive && ttyInfo.hasAgent);
102
- const nickname = activeByTty ? allocateRecoveredNickname(agentType, usedNicknames) : "";
103
-
104
- data.agents[subscriber] = {
105
- agent_type: agentType,
106
- nickname,
107
- status: activeByTty ? "active" : "inactive",
108
- joined_at: now,
109
- last_seen: now,
110
- pid: 0,
111
- tty,
112
- tty_shell_pid: ttyInfo && ttyInfo.shellPid ? ttyInfo.shellPid : 0,
113
- tmux_pane: "",
114
- launch_mode: "",
115
- };
116
- recovered = true;
161
+ recovered = recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) || recovered;
117
162
  }
118
163
 
164
+ recovered = reconcileReservedControllerAliases(data, now) || recovered;
165
+
119
166
  if (recovered) {
120
167
  saveAgentsData(this.agentsFile, data);
121
168
  }