hivecode-mcp 0.13.1 → 0.14.1
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/docs/SLACK_SETUP.md +24 -0
- package/hive-await.js +23 -5
- package/hive-github.js +2002 -1944
- package/hive-mcp-http.js +12 -1
- package/hive-mcp.js +2129 -2090
- package/hive-mux.js +2 -1
- package/hive-poll-proto.js +50 -0
- package/hive-relay-health.js +5 -1
- package/hive-room-key.js +2 -1
- package/hive-setup.js +4 -2
- package/hive-slack-agent.js +14 -2
- package/hive-slack.js +595 -37
- package/hive-transport.js +578 -0
- package/package.json +192 -190
- package/reconcile.cjs +17 -10
- package/sync.js +5031 -4992
package/docs/SLACK_SETUP.md
CHANGED
|
@@ -236,6 +236,30 @@ Paste the contents of [`slack-app-manifest-inbound.json`](../slack-app-manifest-
|
|
|
236
236
|
3. Enter `socket-mode` as the **Token Name** (required by Slack), click **Add Scope**, select `connections:write`, and click **Generate**.
|
|
237
237
|
4. Copy the resulting `xapp-…` token and set it as `SLACK_APP_TOKEN`.
|
|
238
238
|
|
|
239
|
+
### Networks That Refuse WebSockets (Cloud VMs): `--inbound`
|
|
240
|
+
Socket Mode is a WebSocket. Some networks refuse every WebSocket upgrade. One example is Antigravity's cloud VM, whose proxy answers `403 WebSocket upgrades are not allowed`. On such a network the bridge reads Slack over plain HTTPS instead: `conversations.history` for the channel and `conversations.replies` for the threads it tracks.
|
|
241
|
+
|
|
242
|
+
| `--inbound` (or `HIVE_SLACK_INBOUND`) | What it does |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `auto` (default) | Socket Mode when `SLACK_APP_TOKEN` is set. If the network refuses the upgrade with an HTTP status, it switches to HTTPS polling and prints `[hivecode] Slack replies are read over HTTPS every 3 s (...)`. Without `SLACK_APP_TOKEN`, it is outbound only, as before. |
|
|
245
|
+
| `poll` | HTTPS polling only. **Needs no `SLACK_APP_TOKEN`**, just a bot token from [`slack-app-manifest-inbound.json`](../slack-app-manifest-inbound.json) (`channels:history` or `groups:history`, plus `users:read`). |
|
|
246
|
+
| `socket` | Socket Mode only. Never polls. |
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack check --channel C0123456789 --inbound poll
|
|
250
|
+
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack start "<relay|room>" --channel C0123456789 --allow U123 --inbound poll
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
What to know about polling:
|
|
254
|
+
- **Speed:** a reply reaches the room within about `HIVE_SLACK_POLL_MS` (default 3000 ms). Socket Mode is near-instant.
|
|
255
|
+
- **Restarts:** where it stopped reading is kept in the room and on disk, so a restarted bridge neither replays old messages nor skips new ones. Two bridges on one room claim each message before acting, so a `stop` is acted on once.
|
|
256
|
+
- **Downtime:** after the bridge has been offline, messages older than `HIVE_SLACK_CATCHUP_MS` (default 30 minutes) are **not acted on**. A two-hour-old `resume` should not fire. The bridge posts once in the room's thread with the offline window and the count, so nobody assumes they were handled. Send it again if it still matters.
|
|
257
|
+
- **Rate limits:** two reads per poll, more while tracked threads are active. A `429` is honoured with Slack's `Retry-After`.
|
|
258
|
+
- `--agent` (the native stop button) needs Socket Mode and cannot poll.
|
|
259
|
+
|
|
260
|
+
### Strict or Secured Rooms: the Bridge Needs a Human Token
|
|
261
|
+
`stop` and `resume` from Slack change the room's controls, and the relay lets only a human change controls: an AI's write is reverted. The bridge acts for a named Slack person, so it joins as a human. On an open room this works with no token. On a **strict or secured room**, start the bridge with a human (or admin/maintainer) token in the link or `HIVE_TOKEN`. Otherwise the relay reverts the stop, so check the agents' state in the room after your first `stop`.
|
|
262
|
+
|
|
239
263
|
### Who May Act (`--allow` / `--slack-allow`) & No Impersonation
|
|
240
264
|
- **STOP is fail-safe**: Any full member of your Slack workspace may type `stop`, `stop all`, or `stop <AgentName>` in a thread to pause agents (or stop an active GitHub issue run) immediately. If Slack's `users.info` endpoint fails during an outage, `stop` still succeeds and notes `(stopped; I could not confirm who you are)`.
|
|
241
265
|
- **RESUME, RETRY, and thread replies require the allowlist (`--allow U123,U456` on `slack start`, or `--slack-allow U123,U456` on `github start`)**: Only Slack user IDs explicitly listed in the allowlist (find yours in Slack under **Profile → `⋯` → Copy member ID**) may resume agents (`resume`, `resume all`, `resume <AgentName>`), retry a GitHub issue run (`retry`), or send replies into the room. If the allowlist is omitted, inbound is **stop-only**.
|
package/hive-await.js
CHANGED
|
@@ -165,16 +165,34 @@ if (process.env.HIVE_AWAIT_KEEP_ALIVE !== '1' && PARENT > 1 && !parentGone()) {
|
|
|
165
165
|
}, Number(process.env.HIVE_AWAIT_PARENT_CHECK_MS) || 15000).unref()
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
// A REBUILT ROOM MUST NOT LEAVE THE LISTENER DEAF (7 Nov 2026, found by antigravity-2). A relay
|
|
169
|
+
// that restarts without its rooms (every deploy on Render's free plan) starts chat again from zero,
|
|
170
|
+
// but the cursor on disk still said, say, 300: scanning from 300 of a 3-message chat found nothing,
|
|
171
|
+
// so the agent showed LISTENING and no mention woke it until the idle timeout, which then moved the
|
|
172
|
+
// cursor past everything it had missed. So the cursor records which room it was counted in (its
|
|
173
|
+
// first message) and is ignored when that differs, or when it points past the end -- the rule the
|
|
174
|
+
// room's cursor above and the MCP's catch-up already follow. Chat is never trimmed, so within one
|
|
175
|
+
// room the first message never changes. An older cursor file without `head` is only clamped.
|
|
176
|
+
// (tests/await-rebuilt-room-test.mjs)
|
|
168
177
|
const cursorFile = path.join(dir, `.await-cursor-${room}.json`)
|
|
169
|
-
const
|
|
170
|
-
const
|
|
178
|
+
const headOf = (msgs) => { const m = msgs[0]; return m ? `${m.by}|${m.at}|${String(m.text || '').slice(0, 80)}` : '' }
|
|
179
|
+
const readCursor = (msgs) => {
|
|
180
|
+
try {
|
|
181
|
+
const c = JSON.parse(fs.readFileSync(cursorFile, 'utf8'))
|
|
182
|
+
const at = Number(c.at) || 0
|
|
183
|
+
if (at > msgs.length) return 0
|
|
184
|
+
if (typeof c.head === 'string' && c.head && c.head !== headOf(msgs)) return 0
|
|
185
|
+
return at
|
|
186
|
+
} catch { return 0 }
|
|
187
|
+
}
|
|
188
|
+
const writeCursor = (n, msgs) => { try { fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(cursorFile, JSON.stringify({ at: n, head: headOf(msgs) }), 'utf8') } catch { /* a cursor we cannot persist must not break the wait */ } }
|
|
171
189
|
|
|
172
190
|
function check() {
|
|
173
191
|
if (superseded()) return // the newer listener delivers it; consuming it here would hide it from that one
|
|
174
192
|
const msgs = chat.toArray()
|
|
175
193
|
let lastMine = -1
|
|
176
194
|
for (let i = msgs.length - 1; i >= 0; i--) { if (msgs[i].by === NAME) { lastMine = i; break } }
|
|
177
|
-
const from = Math.max(readCursor(), lastMine + 1, roomCursor(msgs.length))
|
|
195
|
+
const from = Math.max(readCursor(msgs), lastMine + 1, roomCursor(msgs.length))
|
|
178
196
|
const hits = []
|
|
179
197
|
for (let i = from; i < msgs.length; i++) {
|
|
180
198
|
const m = msgs[i]
|
|
@@ -183,7 +201,7 @@ function check() {
|
|
|
183
201
|
// Advance past everything READ, not just everything matched — an unaddressed message I have
|
|
184
202
|
// already seen must not be re-scanned on the next call either.
|
|
185
203
|
if (hits.length) {
|
|
186
|
-
writeCursor(msgs.length)
|
|
204
|
+
writeCursor(msgs.length, msgs)
|
|
187
205
|
publish(msgs.length)
|
|
188
206
|
// THE ORDER TO START AGAIN RIDES ON EVERY WAKE. It used to be said once, on the agent's first
|
|
189
207
|
// quiet hive_wait, and an agent had to remember it after every wake; agents forget (4 Oct 2026).
|
|
@@ -205,7 +223,7 @@ hive.provider.on('sync', (s) => {
|
|
|
205
223
|
// An idle timeout also advances the cursor. Without this, a quiet window followed by a loud one
|
|
206
224
|
// would re-deliver everything that arrived during the quiet window too.
|
|
207
225
|
setTimeout(() => {
|
|
208
|
-
try { writeCursor(chat.length) } catch {}
|
|
226
|
+
try { writeCursor(chat.length, chat.toArray()) } catch {}
|
|
209
227
|
publish(chat.length) // nothing in the window named us, so nothing in it is mail the MCP should raise
|
|
210
228
|
finish('idle', { note: `nothing addressed to ${NAME} in ${IDLE_MIN} min. Run this same command again in the background to keep listening: ${AGAIN}` }, 1500)
|
|
211
229
|
}, Number(IDLE_MIN) * 60 * 1000)
|