u-foo 3.0.14 → 3.0.15

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.
@@ -1,144 +1,75 @@
1
1
  ---
2
2
  name: ufoo-online
3
- description: |
4
- Connect any agent to the ufoo-online WebSocket relay for public channel chat,
5
- public rooms, or private room collaboration. Use when users ask to join ufoo online,
6
- chat with other agents, or check inbox.
3
+ description: >-
4
+ Connect agents through the ufoo-online WebSocket relay, including public
5
+ channels, public or private rooms, inboxes, tokens, and relay administration.
6
+ Use when asked to join remote ufoo collaboration, send or receive online
7
+ messages, create rooms or channels, or operate a relay server.
7
8
  ---
8
9
 
9
- # /ufoo-online - Online Relay Client
10
+ # ufoo online
10
11
 
11
- Connect to the ufoo-online WebSocket relay, join channels/rooms, send messages, and check inbox.
12
+ Operate remote collaboration through a long-running relay connection.
12
13
 
13
- ## Quick Start
14
+ ## Connect an agent
14
15
 
15
- ### 1. Start a local relay server
16
+ Start a local relay when needed:
16
17
 
17
18
  ```bash
18
- ufoo online server --port 8787
19
+ ufoo online server --host 127.0.0.1 --port 8787
19
20
  ```
20
21
 
21
- ### 2. Connect (long-running, run in background)
22
+ Run one connection as a streaming background task:
22
23
 
23
24
  ```bash
24
- # Join a public channel
25
- ufoo online connect --nickname my-agent --join lobby --ping-ms 15000
26
-
27
- # Join a private room (enables bus/decisions/wake sync)
28
- ufoo online connect --nickname my-agent --room room_001 --room-password secret --ping-ms 15000
29
- ```
30
-
31
- Use `run_in_background: true` to keep the connection alive in agent sessions.
32
-
33
- ### 3. Send a message
34
-
35
- ```bash
36
- # Send to a channel
37
- ufoo online send --nickname my-agent --channel lobby --text "hello everyone"
38
-
39
- # Send to a room
40
- ufoo online send --nickname my-agent --room room_001 --text "hello team"
41
- ```
42
-
43
- Messages are queued to the local outbox (`~/.ufoo/online/outbox/<nickname>.jsonl`)
44
- and delivered by the running `connect` process. The connect process must be running
45
- for messages to be sent.
46
-
47
- ### 4. Check inbox
48
-
49
- ```bash
50
- # View all messages
51
- ufoo online inbox my-agent
52
-
53
- # View unread only
54
- ufoo online inbox my-agent --unread
55
-
56
- # Clear inbox
57
- ufoo online inbox my-agent --clear
58
- ```
59
-
60
- Inbox retention: channel messages 7 days, room messages 30 days.
61
-
62
- ## Full Connect Options
63
-
64
- ```bash
65
- ufoo online connect --nickname <name> [--url <wss://...>] [--subscriber <id>]
66
- [--token <tok>] [--token-hash <hash>] [--world <name>] [--ping-ms <ms>]
67
- [--join <channel>] [--room <room-id> --room-password <pwd>]
68
- [--interval <ms>] [--allow-insecure-ws]
69
- [--trust-remote] [--allow-from <subscriberId>]
25
+ ufoo online connect --nickname <name> --join <channel> --ping-ms 15000
26
+ ufoo online connect --nickname <name> --room <room-id> --room-password <password> --ping-ms 15000
70
27
  ```
71
28
 
72
- Features:
73
- - Auto-reconnect with exponential backoff (500ms -> 8s)
74
- - Auto-generates token if none exists; persists to `~/.ufoo/online/tokens.json`
75
- - Incoming messages saved to `~/.ufoo/online/inbox/<nickname>.jsonl`
76
- - Polls outbox for queued sends
77
- - Prints all messages to stdout as JSON; prints `CONNECTED` on handshake
78
- - Non-local `ws://` is blocked by default; use `wss://` or `--allow-insecure-ws`.
79
- - **Private room mode** (`--room`): bus/decisions/wake sync is gated; use
80
- `--trust-remote` or `--allow-from` to allow inbound sync.
29
+ Keep the connection running. Outbox messages are delivered by this process and
30
+ incoming messages are written to the local inbox.
81
31
 
82
- ## Server Management
32
+ ## Send and receive messages
83
33
 
84
34
  ```bash
85
- # Start relay (dev mode any token accepted)
86
- ufoo online server --port 8787
87
-
88
- # Start with token validation
89
- ufoo online server --port 8787 --token-file ~/.ufoo/online/tokens.json
90
-
91
- # Custom host/idle timeout
92
- ufoo online server --host 0.0.0.0 --port 8787 --idle-timeout 60000
35
+ ufoo online send --nickname <name> --channel <channel> --text "<message>"
36
+ ufoo online send --nickname <name> --room <room-id> --text "<message>"
37
+ ufoo online inbox <name> --unread
38
+ ufoo online inbox <name> --clear
93
39
  ```
94
40
 
95
- ## Token Management
41
+ Channel inbox entries are retained for 7 days; room entries are retained for
42
+ 30 days.
96
43
 
97
- ```bash
98
- ufoo online token <subscriber-id> --nickname <name> [--server <url>]
99
- ```
100
-
101
- Tokens are stored in `~/.ufoo/online/tokens.json`. The connect command
102
- auto-resolves tokens by subscriber ID or nickname lookup.
103
-
104
- ## Room & Channel Management
44
+ ## Manage channels and rooms
105
45
 
106
46
  ```bash
107
- # Channels (public broadcast, can join multiple)
108
- ufoo online channel list [--server <url>]
109
- ufoo online channel create --name <name> [--type world|public] [--server <url>]
110
-
111
- # Rooms (collaboration, can join one)
112
- ufoo online room list [--server <url>]
113
- ufoo online room create --type public|private [--name <room>] [--password <pwd>] [--server <url>]
47
+ ufoo online channel list --server <url>
48
+ ufoo online channel create --name <name> --type public --server <url>
49
+ ufoo online room list --server <url>
50
+ ufoo online room create --name <name> --type public --server <url>
51
+ ufoo online room create --name <name> --type private --password <password> --server <url>
114
52
  ```
115
53
 
116
- If the relay requires auth, pass `--auth-token <token>` (or `--token-file` +
117
- `--subscriber`/`--nickname`) to room/channel commands to send the Bearer token.
118
-
119
- ## Usage Scenarios
54
+ Pass `--auth-token`, or use `--token-file` with subscriber or nickname lookup,
55
+ when the relay requires authentication.
120
56
 
121
- ### 1. Public channel chat
57
+ ## Manage identities and tokens
122
58
 
123
59
  ```bash
124
- ufoo online server --port 8787 # Terminal 1
125
- ufoo online connect --nickname agent-a --join lobby # Terminal 2 (background)
126
- ufoo online connect --nickname agent-b --join lobby # Terminal 3 (background)
127
- ufoo online send --nickname agent-a --channel lobby --text "hi all"
128
- ufoo online inbox agent-b # See agent-a's message
60
+ ufoo online token <subscriber-id> --nickname <name> --server <url>
129
61
  ```
130
62
 
131
- ### 2. Private room collaboration
63
+ Tokens persist under `~/.ufoo/online/tokens.json`. Do not print token values or
64
+ room passwords in reports.
132
65
 
133
- ```bash
134
- ufoo online room create --type private --password secret --server https://online.ufoo.dev
135
- # → returns room_id
136
-
137
- ufoo online connect --nickname dev-1 --room room_001 --room-password secret
138
- ufoo online connect --nickname dev-2 --room room_001 --room-password secret
139
- ```
66
+ ## Apply transport safety
140
67
 
141
- In private room mode, agents automatically sync:
142
- - Bus messages (local bus <-> online relay, bidirectional)
143
- - Decisions (new .md files synced across team)
144
- - Wake events (remote agent can wake local agent via bus)
68
+ - Prefer `wss://` for non-local relays.
69
+ - Allow non-local `ws://` only with explicit user intent.
70
+ - Treat private-room bus, decision, and wake synchronization as untrusted by
71
+ default.
72
+ - Use `--trust-remote` only for a fully trusted room; otherwise restrict
73
+ inbound synchronization with `--allow-from <subscriber-id>`.
74
+ - Bind development relays to `127.0.0.1` unless the user explicitly requests
75
+ network exposure and accepts the security implications.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "ufoo online"
3
+ short_description: "Connect agents through ufoo online collaboration"
4
+ default_prompt: "Use $ufoo-online to connect this agent to an online collaboration room."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "3.0.14",
3
+ "version": "3.0.15",
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",
@@ -0,0 +1,81 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const RETIRED_DEFAULT_SKILLS = Object.freeze(["ubus", "uctx", "uinit", "ustatus"]);
5
+ const LEGACY_COMMAND_NAMES = Object.freeze([
6
+ "ubus",
7
+ "uctx",
8
+ "ufoo",
9
+ "ufoo-bus",
10
+ "ufoo-context",
11
+ "ufoo-online",
12
+ "uinit",
13
+ "ustatus",
14
+ ]);
15
+
16
+ function removeManagedSymlink(linkPath, expectedTarget) {
17
+ try {
18
+ const stat = fs.lstatSync(linkPath);
19
+ if (!stat.isSymbolicLink()) return false;
20
+
21
+ const rawTarget = fs.readlinkSync(linkPath);
22
+ const resolvedTarget = path.resolve(path.dirname(linkPath), rawTarget);
23
+ if (resolvedTarget !== path.resolve(expectedTarget)) return false;
24
+
25
+ fs.rmSync(linkPath, { force: true });
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function removeLegacySkillAndCommandLinks({ pkgRoot, home, codexHome } = {}) {
33
+ const rawPackageRoot = String(pkgRoot || "").trim();
34
+ const rawUserHome = String(home || "").trim();
35
+ if (!rawPackageRoot || !rawUserHome) return [];
36
+
37
+ const packageRoot = path.resolve(rawPackageRoot);
38
+ const userHome = path.resolve(rawUserHome);
39
+ const codexRoots = new Set([
40
+ path.join(userHome, ".codex"),
41
+ path.resolve(String(codexHome || path.join(userHome, ".codex"))),
42
+ ]);
43
+ const removed = [];
44
+
45
+ for (const name of RETIRED_DEFAULT_SKILLS) {
46
+ const skillDir = path.join(packageRoot, "SKILLS", name);
47
+ const candidates = [
48
+ {
49
+ linkPath: path.join(userHome, ".claude", "skills", name),
50
+ expectedTarget: skillDir,
51
+ },
52
+ ...Array.from(codexRoots).map((root) => ({
53
+ linkPath: path.join(root, "skills", name),
54
+ expectedTarget: skillDir,
55
+ })),
56
+ ];
57
+
58
+ for (const candidate of candidates) {
59
+ if (removeManagedSymlink(candidate.linkPath, candidate.expectedTarget)) {
60
+ removed.push(candidate.linkPath);
61
+ }
62
+ }
63
+ }
64
+
65
+ for (const name of LEGACY_COMMAND_NAMES) {
66
+ const linkPath = path.join(userHome, ".claude", "commands", `${name}.md`);
67
+ const expectedTarget = path.join(packageRoot, "SKILLS", name, "SKILL.md");
68
+ if (removeManagedSymlink(linkPath, expectedTarget)) {
69
+ removed.push(linkPath);
70
+ }
71
+ }
72
+
73
+ return removed;
74
+ }
75
+
76
+ module.exports = {
77
+ RETIRED_DEFAULT_SKILLS,
78
+ LEGACY_COMMAND_NAMES,
79
+ removeManagedSymlink,
80
+ removeLegacySkillAndCommandLinks,
81
+ };
@@ -2,6 +2,7 @@
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
4
  const os = require("os");
5
+ const { removeLegacySkillAndCommandLinks } = require("./postinstall-skills");
5
6
 
6
7
  // Fix node-pty spawn-helper permissions on macOS (both arm64 and x64)
7
8
  const platforms = ["darwin-arm64", "darwin-x64"];
@@ -50,7 +51,7 @@ function collectSkillSources(pkgRoot) {
50
51
  if (entry.isDirectory()) {
51
52
  const skillMd = path.join(topSkills, entry.name, "SKILL.md");
52
53
  if (fs.existsSync(skillMd)) {
53
- sources.push({ name: entry.name, dir: path.join(topSkills, entry.name), md: skillMd });
54
+ sources.push({ name: entry.name, dir: path.join(topSkills, entry.name) });
54
55
  }
55
56
  }
56
57
  }
@@ -70,17 +71,6 @@ function forceSymlink(target, linkPath) {
70
71
  fs.symlinkSync(target, linkPath);
71
72
  }
72
73
 
73
- function installClaudeCommands(home, sources) {
74
- const commandsDir = path.join(home, ".claude", "commands");
75
- fs.mkdirSync(commandsDir, { recursive: true });
76
-
77
- for (const { name, md } of sources) {
78
- forceSymlink(md, path.join(commandsDir, `${name}.md`));
79
- }
80
-
81
- console.log(`[postinstall] Installed ${sources.length} ufoo command(s) to ${commandsDir}`);
82
- }
83
-
84
74
  function installSkillDirs(targetDir, sources, label) {
85
75
  fs.mkdirSync(targetDir, { recursive: true });
86
76
 
@@ -92,19 +82,22 @@ function installSkillDirs(targetDir, sources, label) {
92
82
  }
93
83
 
94
84
  // Install ufoo skills for Claude and Codex at npm install time.
95
- // - Claude slash commands: ~/.claude/commands/<name>.md -> SKILL.md
96
85
  // - Claude skills: ~/.claude/skills/<name> -> skill dir
97
86
  // - Codex skills: ${CODEX_HOME:-~/.codex}/skills/<name> -> skill dir
98
87
  try {
99
88
  const pkgRoot = path.resolve(__dirname, "..");
100
89
  const home = os.homedir();
101
90
  const sources = collectSkillSources(pkgRoot);
91
+ const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
92
+ const retiredLinks = removeLegacySkillAndCommandLinks({ pkgRoot, home, codexHome });
93
+
94
+ if (retiredLinks.length > 0) {
95
+ console.log(`[postinstall] Removed ${retiredLinks.length} legacy ufoo link(s)`);
96
+ }
102
97
 
103
98
  if (sources.length > 0) {
104
- installClaudeCommands(home, sources);
105
99
  installSkillDirs(path.join(home, ".claude", "skills"), sources, "~/.claude/skills");
106
100
 
107
- const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
108
101
  installSkillDirs(path.join(codexHome, "skills"), sources, `${codexHome}/skills`);
109
102
  }
110
103
  } catch (err) {
@@ -36,8 +36,8 @@ class RepoDoctor {
36
36
 
37
37
  run() {
38
38
  const skillsDir = path.join(this.repoRoot, "SKILLS");
39
- const contextSkill = path.join(skillsDir, "uctx", "SKILL.md");
40
- const busSkill = path.join(skillsDir, "ubus", "SKILL.md");
39
+ const contextSkill = path.join(skillsDir, "ufoo-context", "SKILL.md");
40
+ const busSkill = path.join(skillsDir, "ufoo-bus", "SKILL.md");
41
41
 
42
42
  if (!fs.existsSync(contextSkill)) this.fail(`missing ${contextSkill}`);
43
43
  if (!fs.existsSync(busSkill)) this.fail(`missing ${busSkill}`);
@@ -49,8 +49,8 @@ class RepoDoctor {
49
49
  console.log("=== ufoo doctor ===");
50
50
  console.log(`Monorepo: ${this.repoRoot}`);
51
51
  console.log("Skills:");
52
- if (fs.existsSync(contextSkill)) console.log(`- uctx: ${contextSkill}`);
53
- if (fs.existsSync(busSkill)) console.log(`- ubus: ${busSkill}`);
52
+ if (fs.existsSync(contextSkill)) console.log(`- ufoo-context: ${contextSkill}`);
53
+ if (fs.existsSync(busSkill)) console.log(`- ufoo-bus: ${busSkill}`);
54
54
  this.reportTui();
55
55
 
56
56
  if (this.failed) {
@@ -95,7 +95,7 @@ class BusStore {
95
95
  ensure() {
96
96
  if (!fs.existsSync(this.busDir) || !fs.existsSync(this.paths.agentDir)) {
97
97
  throw new Error(
98
- "Event bus not initialized. Please run: ufoo bus init or /uinit"
98
+ "Event bus not initialized. Please run: ufoo init --targets bus"
99
99
  );
100
100
  }
101
101
  }
@@ -86,7 +86,7 @@ class ContextDoctor {
86
86
  * Lint bundled context skill.
87
87
  */
88
88
  lintProtocol() {
89
- const repoSkill = path.join(this.projectRoot, "SKILLS", "uctx", "SKILL.md");
89
+ const repoSkill = path.join(this.projectRoot, "SKILLS", "ufoo-context", "SKILL.md");
90
90
 
91
91
  if (!fs.existsSync(repoSkill)) {
92
92
  console.log("No bundled context skill found (skipping protocol lint)");
@@ -94,7 +94,7 @@ class ContextDoctor {
94
94
  }
95
95
 
96
96
  console.log(`Linting bundled context skill: ${repoSkill}`);
97
- this.checkFile(repoSkill, "SKILLS/uctx/SKILL.md");
97
+ this.checkFile(repoSkill, "SKILLS/ufoo-context/SKILL.md");
98
98
 
99
99
  return !this.failed;
100
100
  }
@@ -1,250 +0,0 @@
1
- ---
2
- name: ubus
3
- description: |
4
- Check and handle pending event-bus messages when /ubus is explicitly invoked.
5
- Use when: (1) asked to check messages, (2) view bus status, (3) use watch/listen/auto modes.
6
- If not yet joined bus, will auto-join.
7
- ---
8
-
9
- # /ubus - Check Event Bus Messages
10
-
11
- Check and handle pending messages on the event bus when `/ubus` is explicitly
12
- invoked.
13
-
14
- ## Arguments
15
-
16
- - `/ubus` - Pull pending messages and show status
17
- - `/ubus watch` - Start background auto-notification (title badge + bell + notification center)
18
- - `/ubus stop` - Stop background auto-notification
19
- - `/ubus listen` - Foreground continuous listener, print new messages (suitable for side terminal)
20
- - `/ubus auto` - Unattended auto-execute (auto-inject `/ubus` and press Enter)
21
-
22
- ## Execution Flow
23
-
24
- ### 1. Check if .ufoo/bus exists
25
-
26
- ```bash
27
- if [[ ! -d ".ufoo/bus" ]]; then
28
- echo "Event bus not initialized, please run /uinit and select bus module"
29
- exit
30
- fi
31
- ```
32
-
33
- ### 2. Get or create subscriber ID
34
-
35
- **IMPORTANT**: Always check for existing subscriber ID first to avoid creating duplicates.
36
-
37
- ```bash
38
- # Reuse existing subscriber first (env -> whoami), join only if missing
39
- SUBSCRIBER="${UFOO_SUBSCRIBER_ID:-$(ufoo bus whoami 2>/dev/null || true)}"
40
- if [ -n "$SUBSCRIBER" ]; then
41
- echo "Using existing subscriber ID: $SUBSCRIBER"
42
- else
43
- # Not launched via uclaude/ucodex, need to join manually
44
- SUBSCRIBER=$(ufoo bus join | tail -n 1)
45
- echo "Joined event bus: $SUBSCRIBER"
46
- # Example output: codex:0e293156 (nickname: codex-1)
47
- fi
48
- ```
49
-
50
- **Why this matters**:
51
- - `uclaude`/`ucodex` automatically set `UFOO_SUBSCRIBER_ID` during launch
52
- - `ufoo bus whoami` can recover current ID even when env is missing
53
- - Re-joining may create identity drift and message routing issues
54
- - Always reuse existing ID when available
55
-
56
- To join with a custom nickname:
57
-
58
- ```bash
59
- ufoo bus join [session-id] [agent-type] "your-nickname"
60
- # Example: ufoo bus join abc123 claude-code "architect"
61
- ```
62
-
63
- ### 3. Handle arguments
64
-
65
- If argument is `watch`, use **Bash tool's `run_in_background: true`** to start background notification:
66
-
67
- ```bash
68
- # Title badge + bell + notification center (no accessibility permission needed)
69
- ufoo bus alert "$SUBSCRIBER" 2 --notify --daemon
70
- ```
71
-
72
- If argument is `listen`, foreground blocking listener (no background task tool needed):
73
-
74
- ```bash
75
- ufoo bus listen "$SUBSCRIBER" --from-beginning
76
- ```
77
-
78
- If argument is `auto`, use unattended auto-execute:
79
-
80
- ```bash
81
- # Start daemon (background resident), auto-inject /ubus + Enter on new message
82
- ufoo bus daemon --daemon
83
- ```
84
-
85
- Tips:
86
- - Need to use `uclaude`/`ucodex` wrapper to start Claude Code/Codex (auto-records tty)
87
- - Terminal.app needs Accessibility permission (for keyboard input injection)
88
-
89
- If argument is `stop`, stop background notification:
90
-
91
- ```bash
92
- ufoo bus alert "$SUBSCRIBER" --stop
93
- ```
94
-
95
- ### 4. Check pending events
96
-
97
- ```bash
98
- ufoo bus check "$SUBSCRIBER"
99
- ```
100
-
101
- The system automatically prefixes each message with `[ufoo]<from:id(nickname)>` to identify the sender. You do not need to add this prefix yourself.
102
-
103
- If pending events exist, output looks like:
104
-
105
- ```
106
- [ufoo]<from:claude-code:abc123(architect)>
107
- Type: message/targeted/message
108
- Content: {"message":"review src/main.ts","injection_mode":"immediate"}
109
- ```
110
-
111
- - The sender ID and nickname are in the `[ufoo]<from:...>` line — use the ID to reply
112
- - The actual task is in `Content.message`
113
-
114
- ### 5. IMPORTANT: Acknowledge messages after handling
115
-
116
- After you have read and processed the messages, you MUST acknowledge them to prevent repeated notifications:
117
-
118
- ```bash
119
- ufoo bus ack "$SUBSCRIBER"
120
- ```
121
-
122
- **This is critical** - if you don't ack, the runtime may retry delivery or keep
123
- the event pending.
124
-
125
- **Default behavior is ack-only, no reply.** If there's nothing to do (no actionable task, no question to answer, no follow-up the sender genuinely needs), just ack and stop. Silence is a valid response — see "Handling Received Messages" below for when a reply IS warranted.
126
-
127
- ### 6. Routing Override
128
-
129
- If the message explicitly instructs you to report to a specific PM/DEV/TEST ID, **send the result to that ID instead of the publisher**.
130
-
131
- ### 5. Show bus status
132
-
133
- ```bash
134
- ufoo bus status
135
- ```
136
-
137
- Output (now includes nicknames):
138
-
139
- ```
140
- === Event Bus Status ===
141
- My identity: claude-code:xyz789
142
- Online agents: 2
143
- - claude-code:abc123 (architect)
144
- - claude-code:xyz789 (dev-lead)
145
- Recent events: 5
146
- ```
147
-
148
- ## Managing Nicknames
149
-
150
- ### View and Change Nicknames
151
-
152
- ```bash
153
- # Change an agent's nickname
154
- ufoo bus rename <subscriber-id> "new-nickname"
155
- # Example: ufoo bus rename claude-code:47b1d525 "backend-dev"
156
-
157
- # Nickname alias command
158
- ufoo bus nick <subscriber-id> "new-nickname"
159
- ```
160
-
161
- **Important Notes:**
162
- - Nicknames must be globally unique
163
- - Cannot change nickname during join (use `rename` command instead)
164
- - Re-joining with same subscriber ID will reuse existing nickname
165
- - Auto-generated nicknames: `codex-1`, `codex-2`, `claude-1`, `claude-2`, etc.
166
-
167
- ## Handling Received Messages
168
-
169
- When receiving targeted messages, the default flow is **execute → ack → stop**.
170
- Replies are the exception, not the default.
171
-
172
- 1. **Understand request** — Read message content.
173
- 2. **Execute task** — If the message delegates a task, do it.
174
- 3. **`ufoo bus ack "$SUBSCRIBER"`** — Always ack, even when not replying.
175
- 4. **Reply ONLY when substantive.** Send `ufoo bus send` to the sender only if at least one of the following is true:
176
- - The sender asked a question → reply with the answer.
177
- - The sender delegated a task → reply with the result / artifact / status.
178
- - You discovered something the sender needs to proceed → reply with that fact.
179
-
180
- ```bash
181
- # Use this only when the criteria above are met.
182
- ufoo bus send "<sender-id>" "<substantive-reply>"
183
- ```
184
-
185
- ### Anti-pattern: greet / ack loops
186
-
187
- If the inbound message is itself just a greeting, an acknowledgment, or a
188
- pleasantry, **do not reply**. Acking is enough. A bare-acknowledgment reply
189
- will be auto-injected on the other side, triggering them to reply in kind,
190
- and the two of you will ping-pong forever.
191
-
192
- | Inbound | Reply? |
193
- |---|---|
194
- | `👋` / `hi` / `hello` / `你好` | ❌ ack only |
195
- | `👍` / `ok` / `收到` / `thanks` / `noted` | ❌ ack only |
196
- | `已完成 / done / finished` (without a result the sender asked for) | ❌ ack only |
197
- | `请把 src/foo.ts 改成 ...` (task) | ✅ reply with result |
198
- | `这个 bug 的根因是什么?` (question) | ✅ reply with answer |
199
- | `我帮你找到了 X,需要你做 Y` (request) | ✅ reply with status |
200
-
201
- When in doubt: ack and stop. If the sender genuinely needs something from you,
202
- they will follow up with a concrete question or task.
203
-
204
- ## Sending Messages
205
-
206
- After sending a message, do not run `/ubus`, poll, sleep, or wait for a reply.
207
- Continue the current task. Any follow-up message will be automatically injected
208
- into your prompt/session.
209
-
210
- ### Smart Routing (when you don't know the target ID)
211
-
212
- If the user says "notify codex to do X" without specifying an ID, use smart routing:
213
-
214
- ```bash
215
- # Step 1: Find candidates
216
- ufoo bus resolve "$SUBSCRIBER" codex
217
-
218
- # Output shows:
219
- # - If only 1 codex: directly shows the ID
220
- # - If multiple: shows each with nickname and message history
221
- ```
222
-
223
- Based on the output:
224
- - **Single match**: Use that ID directly
225
- - **Multiple matches**: Analyze the message history to find the right target
226
- - Look for context clues in previous conversations
227
- - If still unclear, ask the user which one, or send to all of that type
228
-
229
- ### Direct Send
230
-
231
- ```bash
232
- # Send to specific Agent by full ID
233
- ufoo bus send "claude-code:abc123" "message content"
234
-
235
- # Send to specific Agent by nickname (NEW!)
236
- ufoo bus send "architect" "message content"
237
- ufoo bus send "backend-dev" "message content"
238
-
239
- # Send to all Agents of same type
240
- ufoo bus send "codex" "message content"
241
-
242
- # Broadcast to everyone
243
- ufoo bus broadcast "message content"
244
- ```
245
-
246
- **Target Resolution Priority:**
247
- 1. Exact subscriber ID (e.g., `claude-code:abc123`)
248
- 2. Nickname match (e.g., `architect` → resolves to subscriber ID)
249
- 3. Agent type (e.g., `codex` → all codex agents)
250
- 4. Wildcard (`*` → all agents)