u-foo 3.0.14 → 3.0.16
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/OPTIONAL_SKILLS/ufoo-bus-poll/SKILL.md +60 -0
- package/OPTIONAL_SKILLS/ufoo-bus-poll/agents/openai.yaml +4 -0
- package/README.md +9 -3
- package/README.zh-CN.md +8 -3
- package/SKILLS/ufoo/SKILL.md +56 -179
- package/SKILLS/ufoo/agents/openai.yaml +4 -0
- package/SKILLS/ufoo-bus/SKILL.md +92 -0
- package/SKILLS/ufoo-bus/agents/openai.yaml +4 -0
- package/SKILLS/ufoo-context/SKILL.md +101 -0
- package/SKILLS/ufoo-context/agents/openai.yaml +4 -0
- package/SKILLS/ufoo-online/SKILL.md +43 -112
- package/SKILLS/ufoo-online/agents/openai.yaml +4 -0
- package/package.json +1 -1
- package/scripts/postinstall-skills.js +81 -0
- package/scripts/postinstall.js +8 -15
- package/src/app/cli/features/doctor.js +4 -4
- package/src/coordination/bus/store.js +1 -1
- package/src/coordination/context/doctor.js +2 -2
- package/OPTIONAL_SKILLS/ubus-poll/SKILL.md +0 -76
- package/SKILLS/ubus/SKILL.md +0 -250
- package/SKILLS/uctx/SKILL.md +0 -156
- package/SKILLS/uinit/SKILL.md +0 -76
- package/SKILLS/ustatus/SKILL.md +0 -36
|
@@ -1,144 +1,75 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ufoo-online
|
|
3
|
-
description:
|
|
4
|
-
Connect
|
|
5
|
-
public
|
|
6
|
-
|
|
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
|
-
#
|
|
10
|
+
# ufoo online
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Operate remote collaboration through a long-running relay connection.
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
+
## Connect an agent
|
|
14
15
|
|
|
15
|
-
|
|
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
|
-
|
|
22
|
+
Run one connection as a streaming background task:
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
|
|
25
|
-
ufoo online connect --nickname
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
##
|
|
32
|
+
## Send and receive messages
|
|
83
33
|
|
|
84
34
|
```bash
|
|
85
|
-
|
|
86
|
-
ufoo online
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
41
|
+
Channel inbox entries are retained for 7 days; room entries are retained for
|
|
42
|
+
30 days.
|
|
96
43
|
|
|
97
|
-
|
|
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
|
-
|
|
108
|
-
ufoo online channel
|
|
109
|
-
ufoo online
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
57
|
+
## Manage identities and tokens
|
|
122
58
|
|
|
123
59
|
```bash
|
|
124
|
-
ufoo online
|
|
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
|
-
|
|
63
|
+
Tokens persist under `~/.ufoo/online/tokens.json`. Do not print token values or
|
|
64
|
+
room passwords in reports.
|
|
132
65
|
|
|
133
|
-
|
|
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
|
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
|
|
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.
|
package/package.json
CHANGED
|
@@ -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
|
+
};
|
package/scripts/postinstall.js
CHANGED
|
@@ -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)
|
|
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, "
|
|
40
|
-
const busSkill = path.join(skillsDir, "
|
|
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(`-
|
|
53
|
-
if (fs.existsSync(busSkill)) console.log(`-
|
|
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
|
|
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", "
|
|
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/
|
|
97
|
+
this.checkFile(repoSkill, "SKILLS/ufoo-context/SKILL.md");
|
|
98
98
|
|
|
99
99
|
return !this.failed;
|
|
100
100
|
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ubus-poll
|
|
3
|
-
description: |
|
|
4
|
-
Explicitly start a resident ufoo bus stream in an agent host that has been
|
|
5
|
-
configured to deliver streaming background-task output. Install by name only.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# /ubus-poll - Resident Bus Stream
|
|
9
|
-
|
|
10
|
-
This is an opt-in session-start skill. Run it only in a host where a human has
|
|
11
|
-
configured this fallback. Do not install or invoke it for Codex CLI, Claude
|
|
12
|
-
Code CLI, Agy, Kimi, or native ucode; those runtimes already have their own
|
|
13
|
-
ufoo delivery path.
|
|
14
|
-
|
|
15
|
-
## Start once per agent session
|
|
16
|
-
|
|
17
|
-
Reuse the provisioned subscriber identity. Never create a second identity just
|
|
18
|
-
for the poll process.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
SUBSCRIBER="${UFOO_SUBSCRIBER_ID:-}"
|
|
22
|
-
test -n "$SUBSCRIBER" || {
|
|
23
|
-
echo "ubus-poll requires a provisioned UFOO_SUBSCRIBER_ID"
|
|
24
|
-
exit 1
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Identity provisioning is a host/human setup step. Do not guess an agent type,
|
|
29
|
-
call bare `ufoo bus join`, or borrow the workspace's current subscriber.
|
|
30
|
-
|
|
31
|
-
Use the agent host's **streaming background-task** facility to start:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
ufoo bus poll "$SUBSCRIBER" --follow --interval 2
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
The command must remain owned by that facility. Do not use `nohup`, shell `&`,
|
|
38
|
-
or an OS-detached daemon: those routes can put output in a log that never
|
|
39
|
-
reaches the agent. The command rejects a second resident poll for the same
|
|
40
|
-
subscriber.
|
|
41
|
-
|
|
42
|
-
The poll is deliberately queue-read-only. It emits current pending events at
|
|
43
|
-
startup, waits for that batch to be acknowledged, then emits the next pending
|
|
44
|
-
batch. It does not ack, claim, inject, or clear messages itself.
|
|
45
|
-
|
|
46
|
-
## When background output arrives
|
|
47
|
-
|
|
48
|
-
For every `[ufoo]<from:...>` event:
|
|
49
|
-
|
|
50
|
-
1. Read `Content.message` and execute actionable work.
|
|
51
|
-
2. After handling the emitted batch, run the exact `ack --through <seq>`
|
|
52
|
-
command printed by the poll stream. For example:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
ufoo bus ack "$SUBSCRIBER" --through 42
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
`--through` preserves any later message that was not in the displayed batch.
|
|
59
|
-
|
|
60
|
-
3. Reply to the sender only for a requested result, an answer, or information
|
|
61
|
-
they need to continue:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
ufoo bus send "<sender-id>" "<substantive result>"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Ack-only messages, greetings, and thanks need no reply.
|
|
68
|
-
|
|
69
|
-
After sending, do not poll, sleep, or wait for a reply. Keep working; this
|
|
70
|
-
resident stream will emit any follow-up.
|
|
71
|
-
|
|
72
|
-
## Host requirement
|
|
73
|
-
|
|
74
|
-
This flow works only when the agent host forwards incremental output from a
|
|
75
|
-
still-running background task into the agent session. If it only returns output
|
|
76
|
-
after process exit, use an explicitly invoked `/ubus` instead.
|