herdr-plugin-amq 0.1.9 → 0.1.11
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 +168 -26
- package/docs/architecture.md +3 -1
- package/docs/images/agmail-agent-activity.webp +0 -0
- package/docs/images/agmail-mail-thread.webp +0 -0
- package/docs/images/agmail-mobile-new-task.webp +0 -0
- package/docs/images/agmail-mobile-presence.webp +0 -0
- package/docs/images/agmail-persona-switcher.webp +0 -0
- package/docs/images/agmail-task-drawer.webp +0 -0
- package/docs/installation.md +29 -1
- package/docs/ui-screenshots.md +19 -1
- package/package.json +17 -2
- package/src/actions.mjs +3 -2
- package/src/blobs.mjs +9 -0
- package/src/bridge.mjs +19 -4
- package/src/fleet.mjs +1 -1
- package/src/herdr.mjs +19 -6
- package/src/protocol.mjs +48 -16
- package/src/server.mjs +154 -9
- package/src/store.mjs +19 -4
- package/src/web/app.js +367 -45
- package/src/web/index.html +45 -5
- package/src/web/style.css +153 -2
package/README.md
CHANGED
|
@@ -3,72 +3,214 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/herdr-plugin-amq)
|
|
4
4
|
[](https://github.com/cabra-lat/herdr-plugin-amq/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/cabra-lat/herdr-plugin-amq/actions/workflows/security.yml)
|
|
6
|
+
[](https://github.com/cabra-lat/herdr-plugin-amq/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/cabra-lat/herdr-plugin-amq/actions/workflows/ci.yml)
|
|
6
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://vibecoded.fyi/)
|
|
7
10
|
|
|
8
11
|
> The asynchronous nervous system for autonomous AI agent swarms in [Herdr](https://herdr.dev/).
|
|
9
12
|
|
|
10
|
-
Herdr AMQ combines native pure-JS Maildir messaging, lifecycle-aware doorbells, a decentralized task bus, immutable CAS evidence, and the local-first **AGmail** dashboard.
|
|
13
|
+
Herdr AMQ is a local coordination layer for turn-based coding agents. It combines native pure-JS Maildir messaging, lifecycle-aware doorbells, a decentralized task bus, immutable CAS evidence, and the local-first **AGmail** dashboard.
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
The core problem is simple: an agent finishes a turn and goes idle, while a message or task waits in its queue. Herdr AMQ watches the queue, checks the agent's lifecycle, and rings the doorbell only when the agent can act.
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
## The origin & the problem
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
> *"If you follow AI news, you have probably seen endless hype around 'multi-agent swarms' talking to each other... That is cute for a 30-second screen recording. In a real codebase with actual physics, compiler errors, and Git history, it is a complete disaster."*
|
|
20
|
+
> — Read the full story: [**My AI Agents Send Me Emails: Office Drama in a Godot Repo**](https://cabra.pw/my-ai-agents-send-me-emails.html)
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
2. The bridge detects the unread item and checks the Herdr pane state.
|
|
20
|
-
3. An idle agent receives a precise drain/claim prompt.
|
|
21
|
-
4. The agent works in its isolated worktree and replies on the original thread.
|
|
22
|
-
5. AGmail provides a human view of messages, activity, tasks, and evidence.
|
|
22
|
+
Synchronous chat rooms and blocking `wait` loops fall apart for turn-based coding agents:
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
1. **Context window bloat**: group chats flood agent context with irrelevant noise.
|
|
25
|
+
2. **Turn-based nature of LLMs**: when an agent finishes its tool execution, it terminates its turn and goes to sleep. It cannot run a busy-wait loop.
|
|
26
|
+
3. **Dead mailboxes without a doorbell**: an inbox directory is inert storage. If the agent is asleep, incoming messages sit unread forever.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
### The missing piece: the doorbell bridge
|
|
29
|
+
|
|
30
|
+
The bridge daemon continuously inspects agent inboxes. When an agent is `idle` or `done` in its Herdr terminal pane and has unread mail or an assigned backlog card, the bridge **rings the doorbell** via `herdr agent prompt`. The sleeping agent wakes up, drains its inbox, does the work, replies on-thread, and goes back to sleep.
|
|
31
|
+
|
|
32
|
+
```mermaid
|
|
33
|
+
flowchart TD
|
|
34
|
+
AMQ[".agent-mail/ (Maildir + RFC 5322)<br/>Decoupled Markdown Transmissions"]
|
|
35
|
+
BUS[".agent-mail/bus/ (Task Cards)<br/>backlog/ → doing/ → blocked/ → done/"]
|
|
36
|
+
DAEMON["Bridge Daemon<br/>Watches mailboxes & checks Herdr agent states"]
|
|
37
|
+
H_BUSY["working → Leave alone (no spam)"]
|
|
38
|
+
H_BLOCKED["blocked → Alert coordinator / human"]
|
|
39
|
+
H_IDLE["idle / done → RING DOORBELL<br/>(herdr agent prompt)"]
|
|
40
|
+
AGENT["Awakened Agent<br/>1. drain inbox<br/>2. claim task & execute<br/>3. reply on-thread<br/>4. back to sleep"]
|
|
41
|
+
AGMAIL["AGmail Dashboard<br/>http://127.0.0.1:8505 (Strictly Local)"]
|
|
42
|
+
|
|
43
|
+
AMQ -->|New mail arrives| DAEMON
|
|
44
|
+
BUS -->|Assigned card waits| DAEMON
|
|
45
|
+
DAEMON --> H_BUSY
|
|
46
|
+
DAEMON --> H_BLOCKED
|
|
47
|
+
DAEMON --> H_IDLE
|
|
48
|
+
H_IDLE --> AGENT
|
|
49
|
+
AGENT -->|Sends mail + evidence| AMQ
|
|
50
|
+
AGENT -->|Claims / updates tasks| BUS
|
|
51
|
+
AMQ -.->|Monitored & inspected by| AGMAIL
|
|
52
|
+
BUS -.->|Rendered live in Kanban| AGMAIL
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Choose an install
|
|
56
|
+
|
|
57
|
+
### Published CLI: fastest path
|
|
58
|
+
|
|
59
|
+
Install the executable directly from npm when you want the dashboard, task bus, or CLI without setting up a plugin checkout:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install --global herdr-plugin-amq
|
|
63
|
+
herdr-amq status
|
|
64
|
+
herdr-amq dashboard
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You can also run a one-off command without a global install:
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
```bash
|
|
70
|
+
npx --yes herdr-plugin-amq status
|
|
71
|
+
npx --yes herdr-plugin-amq dashboard
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The npm package is the CLI and dashboard entry point. It does not automatically register Herdr actions or panes; use the full plugin setup below when you want those integrations.
|
|
75
|
+
|
|
76
|
+
### Full Herdr plugin
|
|
77
|
+
|
|
78
|
+
Link the plugin from a checkout to register its bridge actions, panes, and agent events:
|
|
33
79
|
|
|
34
80
|
```bash
|
|
81
|
+
git clone https://github.com/cabra-lat/herdr-plugin-amq.git herdr-plugin-amq
|
|
82
|
+
cd herdr-plugin-amq
|
|
35
83
|
npm ci --ignore-scripts
|
|
36
84
|
herdr plugin link .
|
|
85
|
+
herdr plugin action list --plugin cabra.amq
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
From that checkout, start the local dashboard without installing a global command:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
node bin/herdr-amq.mjs dashboard
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
To use the `herdr-amq` command in this source setup, run `npm link` once and then use the normal CLI commands. For a new swarm, bootstrap the queue, worktrees, bridge daemon, and first doorbell pass:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm link
|
|
37
98
|
herdr-amq bootstrap --kind opencode
|
|
38
99
|
herdr-amq dashboard
|
|
39
100
|
```
|
|
40
101
|
|
|
41
|
-
|
|
102
|
+
Run commands from the project or workspace that owns your `.agent-mail` queue. If you already have a queue, skip `bootstrap`.
|
|
103
|
+
|
|
104
|
+
## What you get
|
|
105
|
+
|
|
106
|
+
### A doorbell for sleeping agents
|
|
107
|
+
|
|
108
|
+
The bridge watches Maildir messages and assigned backlog cards, then checks Herdr's pane state:
|
|
109
|
+
|
|
110
|
+
- `idle` or `done` with new work receives a precise drain and claim prompt.
|
|
111
|
+
- `working` panes are left alone so a prompt cannot interrupt an active turn.
|
|
112
|
+
- Delivered message and task IDs are recorded so the same event is not announced twice.
|
|
113
|
+
- Blocked agents raise an actionable alert for the coordinator or human operator.
|
|
42
114
|
|
|
43
|
-
|
|
115
|
+
### Mail, tasks, and evidence that stay inspectable
|
|
116
|
+
|
|
117
|
+
Messages are RFC 5322 Markdown files in Maildir, with real `In-Reply-To`, `References`, and thread metadata. Task cards move through `backlog/`, `doing/`, `blocked/`, and `done/`. Attachments and verification evidence can be stored in the CAS blobstore or pinned to a Git object, so a handoff does not depend on terminal scrollback.
|
|
118
|
+
|
|
119
|
+
### AGmail mission control
|
|
120
|
+
|
|
121
|
+
AGmail is a local webmail and Kanban interface for the swarm. It provides:
|
|
122
|
+
|
|
123
|
+
- Inbox, sent mail, starred mail, all-mail search, and threaded conversations.
|
|
124
|
+
- A responsive board with owners, stage controls, linked transmissions, and dispatch composer.
|
|
125
|
+
- Agent presence with pane state, unread counts, current activity, and the model reported by the live harness when available. `Working` means an active turn; `Idle` means the turn ended and the agent is ready for input.
|
|
126
|
+
- Human personas, including an explicit God Mode identity for sending as the operator without impersonating an agent.
|
|
127
|
+
- Responsive desktop, tablet, and mobile layouts with a pull-to-refresh guard and compact task actions.
|
|
128
|
+
|
|
129
|
+
### Fleet lifecycle and worktree isolation
|
|
130
|
+
|
|
131
|
+
`bootstrap` and `fleet` commands discover supported agent personas, provision Maildirs, prepare isolated Git worktrees, start the bridge, and perform an initial doorbell pass. Agents can therefore resume from a clean turn without sharing a monolithic chat context.
|
|
132
|
+
|
|
133
|
+
## How it works
|
|
134
|
+
|
|
135
|
+
1. A coordinator or human creates a message or task.
|
|
136
|
+
2. The bridge sees the unread Maildir item or assigned backlog card.
|
|
137
|
+
3. Herdr reports whether the target agent is working, idle, done, or blocked.
|
|
138
|
+
4. Only an actionable agent is prompted to drain and claim the work.
|
|
139
|
+
5. The agent replies on the original thread and attaches evidence when needed.
|
|
140
|
+
6. AGmail shows the message, task, status, and proof in one local view.
|
|
141
|
+
|
|
142
|
+
## AGmail visual tour
|
|
143
|
+
|
|
144
|
+
The captures below come from the isolated browser fixture. They contain fixture data rather than a live mailbox.
|
|
145
|
+
|
|
146
|
+
### Threaded mail and verification evidence
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
AGmail keeps the latest message, its sender metadata, and quick-reply actions together while the thread remains navigable.
|
|
151
|
+
|
|
152
|
+
### Live agent activity
|
|
44
153
|
|
|
45
154
|

|
|
46
155
|
|
|
47
|
-
The activity
|
|
156
|
+
The activity sheet reports the current task, pane, unread count, and live harness model. If no live model or explicit profile model is configured, it shows `Not configured` instead of inventing a placeholder.
|
|
157
|
+
|
|
158
|
+
### Task dossier and dispatch
|
|
48
159
|
|
|
49
160
|

|
|
50
161
|
|
|
51
|
-
The
|
|
162
|
+
The task drawer keeps the board context, owner, stage controls, linked AMQ thread, transmissions, and dispatch composer in one place.
|
|
163
|
+
|
|
164
|
+
### Human and agent personas
|
|
165
|
+
|
|
166
|
+

|
|
167
|
+
|
|
168
|
+
The persona switcher makes the active identity explicit. God Mode is the human operator; selecting an agent persona scopes the mailbox and compose identity to that agent.
|
|
169
|
+
|
|
170
|
+
### Mobile swarm presence
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+
|
|
174
|
+
The mobile layout keeps the inbox, agent presence, and navigation usable on a narrow screen.
|
|
175
|
+
|
|
176
|
+
### Compact task creation
|
|
52
177
|
|
|
53
178
|

|
|
54
179
|
|
|
180
|
+
At 320×568, owner guidance and the Cancel/Create Task actions remain visible without horizontal overflow.
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
- [Architecture and live model reporting](docs/architecture.md)
|
|
185
|
+
- [Installation and Herdr setup](docs/installation.md)
|
|
186
|
+
- [CLI, fleet lifecycle, and templates](docs/cli-and-workflows.md)
|
|
187
|
+
- [Security and testing](docs/security-and-testing.md)
|
|
188
|
+
- [AGmail visual tour](docs/ui-screenshots.md)
|
|
189
|
+
|
|
55
190
|
## Requirements
|
|
56
191
|
|
|
57
|
-
- Node.js
|
|
58
|
-
- Herdr
|
|
59
|
-
- Chrome
|
|
60
|
-
-
|
|
192
|
+
- Node.js 18 or newer.
|
|
193
|
+
- Herdr 0.7.0 or newer for bridge actions, panes, and fleet lifecycle features.
|
|
194
|
+
- Chrome or Chromium only for the optional browser journeys.
|
|
195
|
+
- No npm runtime dependencies; `playwright-core` is development-only.
|
|
61
196
|
|
|
62
|
-
##
|
|
197
|
+
## Security note
|
|
198
|
+
|
|
199
|
+
AGmail and the AMQ bridge are local development tools. The server binds to loopback, validates `Host` headers, and rejects path traversal and credential access. Never expose the dashboard to a public network or untrusted LAN. Do not put secrets in messages, task descriptions, prompt templates, or screenshots. See [Security and testing](docs/security-and-testing.md).
|
|
200
|
+
|
|
201
|
+
## Development and verification
|
|
202
|
+
|
|
203
|
+
Install the development dependencies from a checkout, then run the same gates used by CI:
|
|
63
204
|
|
|
64
205
|
```bash
|
|
206
|
+
npm ci --ignore-scripts
|
|
65
207
|
npm test
|
|
66
208
|
npm run test:e2e
|
|
67
209
|
npm run check
|
|
68
210
|
npm audit --audit-level=high
|
|
69
211
|
```
|
|
70
212
|
|
|
71
|
-
The browser suite uses an isolated Maildir, board, and fake Herdr socket, so screenshots and tests never touch the live swarm.
|
|
213
|
+
The browser suite uses an isolated Maildir, board, and fake Herdr socket, so screenshots and tests never touch the live swarm. CI runs the test matrix on Ubuntu and macOS across Node 18, 20, and 22, plus a dedicated security audit workflow.
|
|
72
214
|
|
|
73
215
|
## License
|
|
74
216
|
|
package/docs/architecture.md
CHANGED
|
@@ -12,6 +12,8 @@ Synchronous group chat does not fit autonomous coding sessions:
|
|
|
12
12
|
|
|
13
13
|
AMQ provides persistent Maildir messages, a file-based task bus, and immutable CAS attachments. The bridge watches Herdr panes and prompts only agents that are `idle` or `done`. Working panes are left alone; blocked panes raise an actionable alert.
|
|
14
14
|
|
|
15
|
+
Herdr lifecycle semantics are explicit: `working` is an active turn, while `idle` and `done` are terminal turn states where a new prompt can be delivered. AGmail coalesces rapid status events before rendering so transient output or stale snapshots do not make the indicator flicker.
|
|
16
|
+
|
|
15
17
|
## Data flow
|
|
16
18
|
|
|
17
19
|
```mermaid
|
|
@@ -45,6 +47,6 @@ flowchart TD
|
|
|
45
47
|
|
|
46
48
|
## Live model reporting
|
|
47
49
|
|
|
48
|
-
The profile model is configuration metadata, not proof of the model currently selected by a running harness. When Herdr exposes model fields, AGmail uses them first. For OpenCode panes, the Herdr `agent_session.value` is resolved against the local OpenCode session database and rendered as `provider/model (variant)`. The API
|
|
50
|
+
The profile model is optional configuration metadata, not proof of the model currently selected by a running harness. When Herdr exposes model fields, AGmail uses them first. For OpenCode panes, the Herdr `agent_session.value` is resolved against the local OpenCode session database and rendered as `provider/model (variant)`. The API returns `modelSource` when a model is known; when neither live nor explicit profile metadata is available, the model remains `null` and the UI shows `Not configured`.
|
|
49
51
|
|
|
50
52
|
The resolver is best-effort: unavailable Herdr/OpenCode data leaves the configured profile visible rather than inventing a model.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/installation.md
CHANGED
|
@@ -9,17 +9,45 @@
|
|
|
9
9
|
|
|
10
10
|
The runtime has no npm production dependencies. `playwright-core` is a development-only dependency for the browser suite.
|
|
11
11
|
|
|
12
|
+
## Published CLI
|
|
13
|
+
|
|
14
|
+
Install the CLI and dashboard entry point from npm:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install --global herdr-plugin-amq
|
|
18
|
+
herdr-amq status
|
|
19
|
+
herdr-amq dashboard
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For a one-off command, use `npx` instead of a global install:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx --yes herdr-plugin-amq status
|
|
26
|
+
npx --yes herdr-plugin-amq dashboard
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The npm package does not automatically register Herdr actions or panes. Use the checkout flow below when you want the full plugin integration.
|
|
30
|
+
|
|
12
31
|
## Link the plugin
|
|
13
32
|
|
|
14
33
|
```bash
|
|
15
34
|
git clone https://github.com/cabra-lat/herdr-plugin-amq.git herdr-plugin-amq
|
|
16
35
|
cd herdr-plugin-amq
|
|
36
|
+
npm ci --ignore-scripts
|
|
17
37
|
herdr plugin link .
|
|
18
38
|
herdr plugin list
|
|
19
39
|
herdr plugin action list --plugin cabra.amq
|
|
20
40
|
```
|
|
21
41
|
|
|
22
|
-
The dashboard and queue root are discovered from the current workspace. The web server binds to `127.0.0.1` and refuses foreign `Host` headers.
|
|
42
|
+
The dashboard and queue root are discovered from the current workspace. Run commands from the project or worktree that owns the queue. The web server binds to `127.0.0.1` and refuses foreign `Host` headers.
|
|
43
|
+
|
|
44
|
+
From the checkout, the dashboard can be started without a global CLI link:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node bin/herdr-amq.mjs dashboard
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Run `npm link` if you want the `herdr-amq` command available globally while developing from this checkout. For a new swarm, `herdr-amq bootstrap --kind opencode` provisions the queue, worktrees, bridge daemon, and first doorbell pass.
|
|
23
51
|
|
|
24
52
|
## Herdr actions
|
|
25
53
|
|
package/docs/ui-screenshots.md
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
These captures come from the isolated browser fixture. They contain no live mailbox data and are checked by the desktop/mobile journey suite.
|
|
4
4
|
|
|
5
|
+
## Threaded mail
|
|
6
|
+
|
|
7
|
+
The mail view keeps the latest message, sender metadata, thread navigation, and quick-reply actions together.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
5
11
|
## Agent activity
|
|
6
12
|
|
|
7
|
-
The activity sheet combines the live Herdr state, assigned task, unread count, pane ID, and the model reported by the running harness. The model is sourced from Herdr/OpenCode session metadata when available, rather than blindly displaying the
|
|
13
|
+
The activity sheet combines the live Herdr state, assigned task, unread count, pane ID, and the model reported by the running harness. The model is sourced from Herdr/OpenCode session metadata when available, rather than blindly displaying a placeholder; if no model is available, the sheet shows `Not configured`.
|
|
8
14
|
|
|
9
15
|

|
|
10
16
|
|
|
@@ -14,6 +20,18 @@ The task drawer keeps the stage controls, owner, linked AMQ thread, transmission
|
|
|
14
20
|
|
|
15
21
|

|
|
16
22
|
|
|
23
|
+
## Human and agent personas
|
|
24
|
+
|
|
25
|
+
The persona switcher makes the active identity explicit and scopes mailbox and compose identity to the selected human or agent persona.
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Mobile presence
|
|
30
|
+
|
|
31
|
+
The mobile layout keeps the inbox, agent presence, and navigation usable on a narrow screen.
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
17
35
|
## Compact New Task flow
|
|
18
36
|
|
|
19
37
|
At 320×568, the owner tip and sticky action row remain visible. The form warns before assigning a card to an owner with claimed or blocked work.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "herdr-plugin-amq",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Herdr plugin for AMQ (Agent Message Queue) autonomous bridge, status monitoring, and AGmail dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -45,8 +45,23 @@
|
|
|
45
45
|
"amq",
|
|
46
46
|
"multi-agent",
|
|
47
47
|
"autonomous-agents",
|
|
48
|
+
"ai-agents",
|
|
49
|
+
"agent-swarm",
|
|
50
|
+
"agent-coordination",
|
|
51
|
+
"agent-orchestration",
|
|
52
|
+
"maildir",
|
|
53
|
+
"kanban",
|
|
54
|
+
"task-board",
|
|
55
|
+
"webmail",
|
|
56
|
+
"dashboard",
|
|
48
57
|
"cas-blobstore",
|
|
49
|
-
"agmail"
|
|
58
|
+
"agmail",
|
|
59
|
+
"git-worktree",
|
|
60
|
+
"local-first",
|
|
61
|
+
"opencode",
|
|
62
|
+
"claude-code",
|
|
63
|
+
"coding-agents",
|
|
64
|
+
"cli"
|
|
50
65
|
],
|
|
51
66
|
"author": "cabra.lat",
|
|
52
67
|
"license": "MIT",
|
package/src/actions.mjs
CHANGED
|
@@ -115,8 +115,9 @@ export function handleDoorbell() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
119
|
+
console.log(`🔔 Checking AMQ inboxes at ${amqRoot}${force ? " (force=true)" : ""}${dryRun ? " [dry-run: no prompts, heals, or state writes]" : ""}...`);
|
|
120
|
+
const res = runDoorbellPass({ amqRoot, force, dryRun, allowPrompt: !dryRun, persistState: !dryRun });
|
|
120
121
|
|
|
121
122
|
if (!res.ok) {
|
|
122
123
|
console.error(`❌ Doorbell check failed: ${res.error}`);
|
package/src/blobs.mjs
CHANGED
|
@@ -34,6 +34,11 @@ export const MIME_TYPES = {
|
|
|
34
34
|
".zip": "application/zip",
|
|
35
35
|
".tar": "application/x-tar",
|
|
36
36
|
".gz": "application/gzip",
|
|
37
|
+
|
|
38
|
+
// Video (pilot: inline <video> playback, same-origin blob stream)
|
|
39
|
+
".mp4": "video/mp4",
|
|
40
|
+
".m4v": "video/mp4",
|
|
41
|
+
".webm": "video/webm",
|
|
37
42
|
};
|
|
38
43
|
|
|
39
44
|
export function getMimeType(ext) {
|
|
@@ -104,6 +109,7 @@ export function storeBlob(input, amqRoot, originalName = "") {
|
|
|
104
109
|
|
|
105
110
|
const sizeBytes = contentBuffer.length;
|
|
106
111
|
const isImage = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"].includes(ext);
|
|
112
|
+
const isVideo = [".mp4", ".m4v", ".webm"].includes(ext);
|
|
107
113
|
const isLog = [".log", ".txt", ".csv", ".json", ".out", ".diff", ".patch"].includes(ext);
|
|
108
114
|
|
|
109
115
|
return {
|
|
@@ -114,6 +120,7 @@ export function storeBlob(input, amqRoot, originalName = "") {
|
|
|
114
120
|
mime: getMimeType(ext),
|
|
115
121
|
sizeBytes,
|
|
116
122
|
isImage,
|
|
123
|
+
isVideo,
|
|
117
124
|
isLog,
|
|
118
125
|
exists: true,
|
|
119
126
|
url: `/api/blob/${sha256}${ext ? `?ext=${encodeURIComponent(ext)}` : ""}`,
|
|
@@ -269,6 +276,7 @@ export function pinGitRef(repoRoot, relativePath, commit = "HEAD") {
|
|
|
269
276
|
|
|
270
277
|
const ext = path.extname(cleanRel).toLowerCase();
|
|
271
278
|
const isImage = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"].includes(ext);
|
|
279
|
+
const isVideo = [".mp4", ".m4v", ".webm"].includes(ext);
|
|
272
280
|
const isLog = [".log", ".txt", ".csv", ".json", ".out", ".diff", ".patch"].includes(ext);
|
|
273
281
|
|
|
274
282
|
const ref = {
|
|
@@ -282,6 +290,7 @@ export function pinGitRef(repoRoot, relativePath, commit = "HEAD") {
|
|
|
282
290
|
mime: getMimeType(ext),
|
|
283
291
|
sizeBytes,
|
|
284
292
|
isImage,
|
|
293
|
+
isVideo,
|
|
285
294
|
isLog,
|
|
286
295
|
exists: true,
|
|
287
296
|
url: `/api/git-file?commit=${commitSha}&path=${encodeURIComponent(cleanRel)}`,
|
package/src/bridge.mjs
CHANGED
|
@@ -104,20 +104,35 @@ function getAgentStatus(handle) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function healAgentName(handle, dryRun = false) {
|
|
107
|
+
export function healAgentName(handle, dryRun = false, run = runHerdr) {
|
|
108
108
|
try {
|
|
109
|
-
const out =
|
|
109
|
+
const out = run(["pane", "list"]);
|
|
110
110
|
const panes = JSON.parse(out)?.result?.panes ?? [];
|
|
111
111
|
const needle = `- ${handle} - `;
|
|
112
|
-
|
|
112
|
+
let hit = panes.find((p) =>
|
|
113
113
|
(p.terminal_title_stripped || p.terminal_title || "").includes(needle)
|
|
114
114
|
);
|
|
115
|
+
if (!hit) {
|
|
116
|
+
// Fallback: the terminal title is often overwritten by the foreground
|
|
117
|
+
// program (e.g. "OpenCode"), while the tab label keeps the canonical
|
|
118
|
+
// handle. Match the tab label exactly, and heal only when that tab
|
|
119
|
+
// holds a single pane (multi-pane tabs are ambiguous — skip them).
|
|
120
|
+
try {
|
|
121
|
+
const tabsOut = run(["tab", "list"]);
|
|
122
|
+
const tabs = JSON.parse(tabsOut)?.result?.tabs ?? [];
|
|
123
|
+
const tab = tabs.find((t) => (t.label || "") === handle);
|
|
124
|
+
if (tab) {
|
|
125
|
+
const inTab = panes.filter((p) => p.tab_id === tab.tab_id);
|
|
126
|
+
if (inTab.length === 1) hit = inTab[0];
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
115
130
|
if (!hit) return false;
|
|
116
131
|
if (dryRun) {
|
|
117
132
|
console.log(`[bridge] DRY: would heal name ${handle} <- pane ${hit.pane_id}`);
|
|
118
133
|
return true;
|
|
119
134
|
}
|
|
120
|
-
|
|
135
|
+
run(["agent", "rename", hit.pane_id, handle]);
|
|
121
136
|
console.log(`[bridge] Healed pane ${hit.pane_id} -> renamed back to '${handle}'`);
|
|
122
137
|
return true;
|
|
123
138
|
} catch (err) {
|
package/src/fleet.mjs
CHANGED
|
@@ -113,7 +113,7 @@ export function prepopulateFleet(amqRoot, repoRoot) {
|
|
|
113
113
|
role: persona.role || persona.description,
|
|
114
114
|
description: persona.description,
|
|
115
115
|
prompt: persona.prompt,
|
|
116
|
-
model: persona.model ||
|
|
116
|
+
model: persona.model || null,
|
|
117
117
|
worktree: worktreeResult.ok ? worktreeResult.path : undefined,
|
|
118
118
|
});
|
|
119
119
|
|
package/src/herdr.mjs
CHANGED
|
@@ -120,7 +120,8 @@ export async function getHerdrStatusMap() {
|
|
|
120
120
|
|
|
121
121
|
export function normalizeHerdrStatus(value) {
|
|
122
122
|
const status = String(value || "").trim().toLowerCase();
|
|
123
|
-
if (status === "online"
|
|
123
|
+
if (status === "online") return "idle";
|
|
124
|
+
if (status === "active") return "working";
|
|
124
125
|
if (["idle", "working", "blocked", "done", "error", "unknown"].includes(status)) return status;
|
|
125
126
|
return "unknown";
|
|
126
127
|
}
|
|
@@ -180,6 +181,18 @@ export function mapHerdrAgentActivity(agent, observedAt = new Date().toISOString
|
|
|
180
181
|
};
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
export function normalizeHerdrEvent(message) {
|
|
185
|
+
if (!message || typeof message !== "object") return null;
|
|
186
|
+
if (message.method && message.params && typeof message.params === "object") {
|
|
187
|
+
return { type: String(message.method), ...message.params };
|
|
188
|
+
}
|
|
189
|
+
if (message.event && message.data && typeof message.data === "object") {
|
|
190
|
+
const payload = message.data.pane && typeof message.data.pane === "object" ? message.data.pane : message.data;
|
|
191
|
+
return { ...payload, type: String(message.event).replace("_", ".") };
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
// ─── Long-lived event subscription ──────────────────────────────────────────
|
|
184
197
|
|
|
185
198
|
/**
|
|
@@ -200,7 +213,9 @@ export function subscribeHerdrEvents({ onEvent, onDisconnect, onConnect } = {})
|
|
|
200
213
|
JSON.stringify({
|
|
201
214
|
id: subId,
|
|
202
215
|
method: "events.subscribe",
|
|
203
|
-
params: {
|
|
216
|
+
params: {
|
|
217
|
+
subscriptions: [{ type: "pane.updated" }],
|
|
218
|
+
},
|
|
204
219
|
}) + "\n";
|
|
205
220
|
|
|
206
221
|
let sock = null;
|
|
@@ -226,11 +241,9 @@ export function subscribeHerdrEvents({ onEvent, onDisconnect, onConnect } = {})
|
|
|
226
241
|
if (!line.trim()) continue;
|
|
227
242
|
try {
|
|
228
243
|
const msg = JSON.parse(line);
|
|
229
|
-
// Skip the ack for the subscribe call itself
|
|
230
244
|
if (msg.id === subId && msg.result) continue;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
245
|
+
const event = normalizeHerdrEvent(msg);
|
|
246
|
+
if (event && onEvent) onEvent(event);
|
|
234
247
|
} catch {}
|
|
235
248
|
}
|
|
236
249
|
});
|