hivecode-mcp 0.14.11 → 0.14.13
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/GITHUB_WORKFLOW.md +13 -4
- package/docs/SLACK_SETUP.md +126 -327
- package/docs/TROUBLESHOOTING.md +10 -1
- package/hive-gh-action.js +108 -30
- package/hive-land.js +3 -1
- package/hive-room-mode.js +6 -0
- package/hive-slack-connect.js +150 -0
- package/hive-slack.js +13 -2
- package/package.json +2 -1
package/docs/GITHUB_WORKFLOW.md
CHANGED
|
@@ -175,9 +175,15 @@ short job on each event and every 10 minutes, with the token it gives the job.
|
|
|
175
175
|
`npx -y hivecode-mcp@latest branch <branch>` then `git push origin <branch>`
|
|
176
176
|
2. Add the room's link as a repository secret named `HIVE_ROOM`: Settings → Secrets and variables →
|
|
177
177
|
Actions → New repository secret.
|
|
178
|
-
3.
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
3. Allow GitHub Actions to create pull requests: Settings → Actions → General → Workflow permissions →
|
|
179
|
+
select "Allow GitHub Actions to create and approve pull requests" and save. Without this, GitHub
|
|
180
|
+
refuses the pull request (HTTP 403 / Q574) when the work lands.
|
|
181
|
+
4. Add the workflow file. `npx -y hivecode-mcp@latest github action --workflow --branch <branch>`
|
|
182
|
+
detects your repository's package manager and lockfile (`package-lock.json`, `pnpm-lock.yaml`, or
|
|
183
|
+
`yarn.lock`) to insert the dependency installation step (`npm ci`, `pnpm install`, or `yarn install`),
|
|
184
|
+
then prints the workflow; save it as `.github/workflows/hivecode.yml`. For other package managers
|
|
185
|
+
or languages, add your dependency installation step before the Hivecode step.
|
|
186
|
+
5. The agents join the room. They can all run in the cloud.
|
|
181
187
|
|
|
182
188
|
### What happens
|
|
183
189
|
|
|
@@ -185,10 +191,13 @@ short job on each event and every 10 minutes, with the token it gives the job.
|
|
|
185
191
|
|---|---|
|
|
186
192
|
| A person who can push labels an issue `hivecode` | Posts it into the room as one task for @all, quoted as data, and comments on the issue who is in the room. Another labelled issue waits, and is told so. |
|
|
187
193
|
| The room marks the task done, with every file reviewed by someone who did not write it | The next run lands it: the tests on the exact commit, a push to the branch, and the pull request (`<title> (#N)`, `Closes #N`). An agent can start that run at once (Actions → Hivecode → Run workflow); otherwise the 10-minute timer does. |
|
|
188
|
-
| A
|
|
194
|
+
| A comment on the issue or the pull request, or a review, from someone who can push | One task for @all with the comments, quoted as data. Each comment is sent once. Comments from bots and from people who cannot push are left out. |
|
|
195
|
+
| A failed check on the pull request | One task for @all naming the checks, once per commit. |
|
|
189
196
|
| The room fixes it | The next run pushes the fix to the same pull request. |
|
|
190
197
|
| The pull request is merged or closed | The room is told, the label comes off, and the next labelled issue comes in. |
|
|
191
198
|
| The issue is closed or unlabelled mid-work | The room is told to stop. Labelling it again sends it again, as a new attempt. |
|
|
199
|
+
| Tests fail during landing due to missing dependencies | The landing is refused (Q576); the issue gets a comment naming the fix (`Add an install step` in `.github/workflows/hivecode.yml`). Once the workflow file is updated on the default branch, the next run tries landing again. |
|
|
200
|
+
| The work is pushed to the branch, but GitHub refuses to open the PR (HTTP 403) | The issue is told the work is pushed to the branch, names the missing permission (Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests"), and offers opening the PR manually. Once the setting is enabled, the next run opens the PR automatically without changes in the room. |
|
|
192
201
|
|
|
193
202
|
### No state file
|
|
194
203
|
|
package/docs/SLACK_SETUP.md
CHANGED
|
@@ -1,195 +1,150 @@
|
|
|
1
|
-
# Slack Setup
|
|
1
|
+
# Slack Setup
|
|
2
2
|
|
|
3
|
-
Hivecode
|
|
3
|
+
Hivecode connects Slack and your swarm rooms so humans and agents collaborate where they already work.
|
|
4
|
+
|
|
5
|
+
There are two ways to connect:
|
|
6
|
+
1. **Relay Connection (`slack connect`) — Recommended (2 minutes):** Zero processes running on anyone's machine. The relay securely routes events between Slack and the room.
|
|
7
|
+
2. **Self-Hosted Bridge (`slack start`):** Runs a local bridge process on your machine using Slack Socket Mode (or HTTPS polling).
|
|
4
8
|
|
|
5
9
|
---
|
|
6
10
|
|
|
7
|
-
## 1.
|
|
11
|
+
## 1. Fast Setup with `slack connect` (Under 2 Minutes, Zero Local Processes)
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
2. Pick your Slack workspace and switch the tab to **JSON**.
|
|
11
|
-
3. Paste the contents of [`slack-app-manifest.json`](../slack-app-manifest.json):
|
|
13
|
+
With `slack connect`, you don't need to keep a terminal running or worry about laptops sleeping. The Hivecode relay handles event routing directly between Slack webhooks and room state.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"description": "One Slack thread per task or GitHub issue for Hivecode rooms.",
|
|
18
|
-
"background_color": "#111318"
|
|
19
|
-
},
|
|
20
|
-
"features": {
|
|
21
|
-
"bot_user": {
|
|
22
|
-
"display_name": "Hivecode",
|
|
23
|
-
"always_online": true
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"oauth_config": {
|
|
27
|
-
"scopes": {
|
|
28
|
-
"bot": [
|
|
29
|
-
"chat:write",
|
|
30
|
-
"channels:read",
|
|
31
|
-
"groups:read"
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"settings": {
|
|
36
|
-
"org_deploy_enabled": false,
|
|
37
|
-
"socket_mode_enabled": false,
|
|
38
|
-
"token_rotation_enabled": false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
15
|
+
### Step 1: Run `slack connect` & Create the App from the Manifest
|
|
16
|
+
Run the connect command with your room link:
|
|
17
|
+
```bash
|
|
18
|
+
npx -y hivecode-mcp@latest slack connect "wss://livecode-xoss.onrender.com|room-<id>"
|
|
41
19
|
```
|
|
20
|
+
*(On Windows PowerShell, quote the room link as shown).*
|
|
42
21
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
*(This posts one message in `#your-channel` — `"Hivecode check: reply anything in this thread"` — and one reply in its thread once your reply arrives.)*
|
|
22
|
+
The CLI contacts the relay (`POST /slack/prepare`), generates a dedicated connection ID and event URL (`https://<relay>/slack/events/<id>`), and outputs a ready-to-paste Slack application manifest:
|
|
23
|
+
1. Open [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App** → **From a manifest**.
|
|
24
|
+
2. Select your Slack workspace and switch to the **JSON** tab.
|
|
25
|
+
3. Paste the JSON manifest printed in your terminal and click **Next** → **Create**.
|
|
26
|
+
4. Click **Install to Workspace** and authorize the bot.
|
|
27
|
+
|
|
28
|
+
The manifest configures:
|
|
29
|
+
- **Bot Scopes**: `chat:write`, `channels:read`, `groups:read`, `channels:history`, `groups:history`, `users:read`.
|
|
30
|
+
- **Event Subscriptions**: `message.channels`, `message.groups` pointing directly to your relay's `/slack/events/<id>` URL.
|
|
31
|
+
- **No Socket Mode required** (no `xapp-…` token needed).
|
|
32
|
+
|
|
33
|
+
### Step 2: Invite `@Hivecode` to Your Channel
|
|
34
|
+
In Slack, open the channel you want to bridge and invite the newly created bot:
|
|
35
|
+
```text
|
|
36
|
+
/invite @Hivecode
|
|
37
|
+
```
|
|
61
38
|
|
|
62
|
-
|
|
39
|
+
### Step 3: Enter Tokens & Select Channel
|
|
40
|
+
Now return to your terminal. The CLI securely prompts for your credentials (input is hidden with no terminal echo):
|
|
41
|
+
1. **Bot User OAuth Token (`xoxb-…`)**: In Slack app settings under **OAuth & Permissions**, copy the Bot User OAuth Token.
|
|
42
|
+
2. **Signing Secret**: Under **Basic Information → App Credentials**, copy the Signing Secret.
|
|
43
|
+
*(Alternatively, you can provide them via environment variables `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`).*
|
|
63
44
|
|
|
64
|
-
|
|
45
|
+
3. **Choose Channel**:
|
|
46
|
+
The CLI queries the channels the bot belongs to. Since you invited the bot in Step 2, your channel appears immediately. If running in an interactive terminal, it lists them for you to pick. In non-interactive environments, pass `--channel <id>` (or if the bot is in only one channel, that channel is selected automatically).
|
|
65
47
|
|
|
66
|
-
|
|
48
|
+
### Step 4: Done!
|
|
49
|
+
The relay verifies the bot token with `auth.test`, validates channel membership, stores the credentials encrypted (`AES-256-GCM` using `HIVE_SLACK_KEY`), and posts:
|
|
50
|
+
`✓ Slack is connected to this room (#channel)` in both the Slack channel and the room.
|
|
67
51
|
|
|
68
|
-
|
|
52
|
+
### Disconnecting
|
|
53
|
+
To remove the connection and wipe the stored credentials from the relay:
|
|
69
54
|
```bash
|
|
70
|
-
|
|
71
|
-
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack check --channel C0123456789
|
|
72
|
-
|
|
73
|
-
# Outbound + inbound stop/retry/replies (Socket Mode manifest):
|
|
74
|
-
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack check --channel C0123456789
|
|
75
|
-
|
|
76
|
-
# Slack AI Agent surface + native stop button (Paid Slack plan manifest):
|
|
77
|
-
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack check --channel D0123456789 --agent
|
|
55
|
+
npx -y hivecode-mcp@latest slack disconnect "wss://livecode-xoss.onrender.com|room-<id>"
|
|
78
56
|
```
|
|
57
|
+
Only the holder of the same `SLACK_SIGNING_SECRET` can disconnect the room.
|
|
79
58
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
$env:SLACK_APP_TOKEN="xapp-…" # optional: include when using inbound stop/retry/replies or --agent
|
|
84
|
-
npx -y hivecode-mcp@latest slack check --channel C0123456789
|
|
85
|
-
```
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. What Gets Posted (and What Is Scrubbed)
|
|
86
62
|
|
|
87
|
-
|
|
63
|
+
Hivecode posts room activity into Slack and forwards Slack messages into the room:
|
|
88
64
|
|
|
89
|
-
|
|
|
65
|
+
| Source | Posting Model | Description |
|
|
90
66
|
| :--- | :--- | :--- |
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
| **`Q631 Slack channel … could not be accessed (missing_scope: needs groups:read)`** | Target channel is private, or the bot lacks the channel read scope (`needed` scope named in error). | Add the named scope (`groups:read` / `channels:read`) under **OAuth & Permissions → Bot Token Scopes** and click **Reinstall to Workspace**. |
|
|
95
|
-
| **`Q634 SLACK_BOT_TOKEN is missing required inbound scope(s): users:read, channels:history`** | `SLACK_APP_TOKEN` is set for inbound `stop`/`retry`, but the app was created from the outbound-only manifest. | Recreate or update the app's manifest from [`slack-app-manifest-inbound.json`](../slack-app-manifest-inbound.json) (Section 5 below) and click **Reinstall to Workspace**. |
|
|
96
|
-
| **`Q634 SLACK_BOT_TOKEN is missing required agent scope(s): assistant:write, im:history`** | `--agent` (or `hive-slack-agent.js`) was started with a token from the outbound or inbound-only manifest. | Update the app's manifest from [`slack-app-manifest-agent.json`](../slack-app-manifest-agent.json) (Section 5 below) and click **Reinstall to Workspace**. |
|
|
97
|
-
| **`Q634 SLACK_APP_TOKEN was rejected` / `Socket Mode did not open (no hello frame)`** | Socket Mode is disabled in the Slack app, or `SLACK_APP_TOKEN` (`xapp-…`) lacks `connections:write`. | Enable **Socket Mode** in your Slack app settings and generate a new App-Level Token (`xapp-…`) with `connections:write` under **Basic Information → App-Level Tokens**. |
|
|
98
|
-
| **`Q634 Slack agents.sessions.setStatus returned warning missing_agent_session_stopped_event_subscription`** | `--agent` checked `agents.sessions.setStatus`, and Slack reported that `agent_session_stopped` is not subscribed (so the native stop button is not rendered). | Subscribe to `agent_session_stopped` by updating your app's manifest from [`slack-app-manifest-agent.json`](../slack-app-manifest-agent.json) and reinstalling. |
|
|
99
|
-
| **`Q634 Slack agents.sessions.setStatus failed (feature_disabled)`** | The workspace is on a Free Slack plan or Agents & Assistants is disabled for the workspace. | Use [`slack-app-manifest-inbound.json`](../slack-app-manifest-inbound.json) for typed `stop` / `retry` in channel threads (works on all Slack plans), or enable Agents & Assistants on a paid Slack plan. |
|
|
100
|
-
| **`Q634 Slack agents.sessions.setStatus failed (thread_ts_required)`** | Slack rejected `agents.sessions.setStatus` because the DM session requires a valid `thread_ts`. | Run `slack check --channel <D…> --agent` on a DM or channel where `@Hivecode` can post a thread root message first. |
|
|
101
|
-
| **`Q634 replies did not arrive. Event subscriptions are probably missing`** | You replied in the check thread, but Slack never sent the event (`message.channels` / `message.groups` / `message.im` not subscribed). | Update your app's manifest with [`slack-app-manifest-inbound.json`](../slack-app-manifest-inbound.json) (or [`slack-app-manifest-agent.json`](../slack-app-manifest-agent.json) for `--agent`) and reinstall the app. |
|
|
102
|
-
| **`Q635 Another process is already connected to Slack Socket Mode`** | Another running terminal or service is connected with the same `SLACK_APP_TOKEN` (e.g. `slack start`, `github start --slack-allow`, or `hive-slack-agent`). | Use a separate Slack app per process that reads Slack. If you recently stopped a bridge or watcher, wait a minute for Slack to release the socket and retry. |
|
|
103
|
-
|
|
104
|
-
Once `slack check` passes, you can also run `--dry-run` on `slack start` or `github start` before launching:
|
|
105
|
-
|
|
106
|
-
### Option A — Bridge a Live Hivecode Room (`slack start`)
|
|
107
|
-
|
|
108
|
-
> **Where do I get the `"wss://…|room-…"` link?** Running `npx -y hivecode-mcp@latest branch <name>` or `npx -y hivecode-mcp@latest setup` prints your room's `wss://…|room-…` link.
|
|
109
|
-
|
|
110
|
-
**macOS / Linux (bash, zsh):**
|
|
111
|
-
```bash
|
|
112
|
-
# 1. Verify token and channel membership first
|
|
113
|
-
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --dry-run
|
|
67
|
+
| **Relay Connection (`slack connect`)** | Direct channel messages | Each room chat line is posted directly into the channel with `chat.postMessage`, and messages posted in Slack are added to room chat. |
|
|
68
|
+
| **Room Bridge (`slack start`)** | One thread per task | Dedicated threads for tasks (`task-open` and `task-done` broadcast to channel), plus a shared room thread for untasked chatter. |
|
|
69
|
+
| **GitHub Watcher (`--slack-channel`)** | One thread per issue | Issue threads with broadcast replies for PR ready, waiting on human, and merged/closed. |
|
|
114
70
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
71
|
+
- **Security & Scrubbing**: Before lines leave the relay, API keys and tokens are scrubbed, and the room ID is replaced with `[room]`.
|
|
72
|
+
- **Deduplication & Deadlines**: The relay acknowledges Slack event webhooks (`HTTP 200`) immediately to satisfy Slack's 3-second deadline, deduplicates `X-Slack-Retry-Num` and `event_id`, and avoids echoing bot or Slack-origin messages.
|
|
73
|
+
- **Attribution**: Messages originating from Slack are attributed in room chat as `slack:<handle>` (e.g. `slack:ganesh`), preventing impersonation of local room participants.
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
```powershell
|
|
121
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
122
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --dry-run
|
|
123
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789
|
|
124
|
-
```
|
|
75
|
+
---
|
|
125
76
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
```
|
|
77
|
+
## 3. Controlling Agents from Slack
|
|
78
|
+
|
|
79
|
+
Full workspace members can control agents directly from Slack:
|
|
80
|
+
- **`stop` / `pause` / `halt`** (or `stop all`, `stop <AgentName>`): Pauses agents on their clients immediately.
|
|
81
|
+
- **`resume` / `unpause`** (or `resume all`, `resume <AgentName>`): Resumes paused agents.
|
|
132
82
|
|
|
133
83
|
---
|
|
134
84
|
|
|
135
|
-
|
|
85
|
+
## 4. Self-Hosted Fallback (`slack start`)
|
|
136
86
|
|
|
137
|
-
|
|
87
|
+
If you run your own private relay or prefer running a dedicated local bridge process on your machine, use `slack start`:
|
|
138
88
|
|
|
139
|
-
|
|
89
|
+
### Setup Steps:
|
|
90
|
+
1. Create a Slack app using [`slack-app-manifest-inbound.json`](../slack-app-manifest-inbound.json) with Socket Mode enabled.
|
|
91
|
+
2. Under **Basic Information → App-Level Tokens**, create an app token with `connections:write` scope (`SLACK_APP_TOKEN=xapp-…`).
|
|
92
|
+
3. Under **OAuth & Permissions**, copy `SLACK_BOT_TOKEN=xoxb-…`.
|
|
93
|
+
4. Invite `@Hivecode` to your channel (`/invite @Hivecode`).
|
|
94
|
+
5. Run the bridge:
|
|
140
95
|
|
|
141
|
-
**macOS / Linux
|
|
96
|
+
**macOS / Linux:**
|
|
142
97
|
```bash
|
|
143
|
-
|
|
144
|
-
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest github start --agent "claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode" --slack-channel C0123456789 --dry-run
|
|
145
|
-
|
|
146
|
-
# 2. Start watching issues
|
|
147
|
-
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest github start --agent "claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode" --slack-channel C0123456789
|
|
98
|
+
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack start "wss://relay|room-…" --channel C0123456789
|
|
148
99
|
```
|
|
149
100
|
|
|
150
101
|
**Windows (PowerShell):**
|
|
151
102
|
```powershell
|
|
152
103
|
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
153
|
-
|
|
154
|
-
npx -y hivecode-mcp@latest
|
|
104
|
+
$env:SLACK_APP_TOKEN="xapp-…"
|
|
105
|
+
npx -y hivecode-mcp@latest slack start "wss://relay|room-…" --channel C0123456789
|
|
155
106
|
```
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
npx -y hivecode-mcp@latest
|
|
161
|
-
npx -y hivecode-mcp@latest github start --agent "claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode" --slack-channel C0123456789
|
|
108
|
+
### Preflight Check (`slack check`)
|
|
109
|
+
You can test your tokens, channel permissions, and Socket Mode connectivity anytime:
|
|
110
|
+
```bash
|
|
111
|
+
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack check --channel C0123456789
|
|
162
112
|
```
|
|
163
113
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
## 4. What Gets Posted (and What Is Scrubbed)
|
|
167
|
-
|
|
168
|
-
| Source | Shown in Channel | Inside the Thread Only |
|
|
169
|
-
| :--- | :--- | :--- |
|
|
170
|
-
| **GitHub Watcher (`--slack-channel`)** | Thread opening post (`#12 — Title`), plus thread replies broadcast to the channel (`reply_broadcast`): PR ready for review, waiting on a person (`hivecode:needs-human`), merged / closed | Run started, CI failed, the tester's verdict, resumed, stopped |
|
|
171
|
-
| **Room Bridge (`slack start`)** | `task-open` and `task-done` | Gate pass/fail (`gate-pass`, `gate-fail`), task chat, and all untasked room chat (grouped inside one shared room thread so chatter never floods the channel) |
|
|
172
|
-
|
|
173
|
-
- **What `slack start` announces, and when**: it watches the room's task list and gate results as well as its chat. A new task opens its thread (and appears in the channel); when the assignee completes it, the "done" reply is also broadcast to the channel. A gate pass or fail is posted inside the shared room thread. The first time a bridge ever attaches to a room it only records what is already there, so starting it never replays the room's history; what it has announced is kept in room state (`slack_seen`), so a restart does not repeat itself. A forced gate release is not posted as a gate result: it is already in room chat.
|
|
174
|
-
- **Secret & Room Link Scrubbing**: Every message passes through `scrubSlack()` before leaving your machine. API keys, tokens, bearer headers, room capability links (`wss://…|room-…`), and room IDs (`room-…`) are stripped automatically.
|
|
175
|
-
- **Rate Limits & Retries**: Slack's ~1 message/second per-channel pacing is enforced by a single outbound queue. HTTP `429` (`ratelimited`) responses are retried up to 5 times honouring `Retry-After`.
|
|
176
|
-
- **Durable Thread State**: Thread timestamps (`thread_ts`) are stored in room state (`slack_threads` map) for `slack start` and in `.git/hivecode/github.json` for `github start --slack-channel`, so restarting the process continues posting into the existing Slack thread.
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## 5. Stopping Agents & Replying from Slack (Optional — Socket Mode)
|
|
181
|
-
|
|
182
|
-
By default, `slack-app-manifest.json` requests only the three outbound scopes (`chat:write`, `channels:read`, `groups:read`). If you also want to **stop/resume agents** or **reply in task threads** from Slack (with zero public webhook URL), enable **Socket Mode**:
|
|
183
|
-
|
|
184
|
-
### Privacy & Scope Notice
|
|
185
|
-
Inbound Socket Mode requires adding `channels:history`, `groups:history`, and `users:read` to **Bot Token Scopes**, plus an **App-Level Token** (`SLACK_APP_TOKEN=xapp-…`) with `connections:write` and Event Subscriptions for `message.channels` and `message.groups`.
|
|
186
|
-
> **Plain-spoken scope warning:** `channels:history` and `groups:history` let the bot read every message posted in the channels you invite it to. If you only want outbound thread feeds, keep the default 3-scope manifest above.
|
|
187
|
-
|
|
188
|
-
<details>
|
|
189
|
-
<summary><strong>Optional Inbound Manifest (Socket Mode enabled — <code>slack-app-manifest-inbound.json</code>)</strong></summary>
|
|
114
|
+
### Self-Hosted Manifest Templates
|
|
190
115
|
|
|
191
|
-
|
|
116
|
+
#### 1. Outbound Manifest (`slack-app-manifest.json`)
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"display_information": {
|
|
120
|
+
"name": "Hivecode",
|
|
121
|
+
"description": "One Slack thread per task or GitHub issue for Hivecode rooms.",
|
|
122
|
+
"background_color": "#111318"
|
|
123
|
+
},
|
|
124
|
+
"features": {
|
|
125
|
+
"bot_user": {
|
|
126
|
+
"display_name": "Hivecode",
|
|
127
|
+
"always_online": true
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"oauth_config": {
|
|
131
|
+
"scopes": {
|
|
132
|
+
"bot": [
|
|
133
|
+
"chat:write",
|
|
134
|
+
"channels:read",
|
|
135
|
+
"groups:read"
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"settings": {
|
|
140
|
+
"org_deploy_enabled": false,
|
|
141
|
+
"socket_mode_enabled": false,
|
|
142
|
+
"token_rotation_enabled": false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
192
146
|
|
|
147
|
+
#### 2. Inbound Socket Mode Manifest (`slack-app-manifest-inbound.json`)
|
|
193
148
|
```json
|
|
194
149
|
{
|
|
195
150
|
"display_information": {
|
|
@@ -228,77 +183,8 @@ Paste the contents of [`slack-app-manifest-inbound.json`](../slack-app-manifest-
|
|
|
228
183
|
}
|
|
229
184
|
}
|
|
230
185
|
```
|
|
231
|
-
</details>
|
|
232
|
-
|
|
233
|
-
### Generating the App-Level Token (`SLACK_APP_TOKEN`)
|
|
234
|
-
1. In your Slack app settings, go to **Basic Information** and scroll down to **App-Level Tokens**.
|
|
235
|
-
2. Click **Generate Token and Scopes**.
|
|
236
|
-
3. Enter `socket-mode` as the **Token Name** (required by Slack), click **Add Scope**, select `connections:write`, and click **Generate**.
|
|
237
|
-
4. Copy the resulting `xapp-…` token and set it as `SLACK_APP_TOKEN`.
|
|
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
|
-
|
|
263
|
-
### Who May Act (`--allow` / `--slack-allow`) & No Impersonation
|
|
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)`.
|
|
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**.
|
|
266
|
-
- **Guests & Slack Connect users are refused**: Single-channel/multi-channel guests (`is_restricted` / `is_ultra_restricted`) and external Slack Connect users from other workspaces (`team_id` mismatch via `users.info`) are refused for all actions (including `stop`) and receive a refusal reply in the thread.
|
|
267
|
-
- **One Slack app per process (Socket Mode multi-connection guard)**: Each process that reads Slack (`slack start`, `github start --slack-allow`, or `hive-slack-agent`) requires its own dedicated Slack app and App-Level Token (`xapp-…`). Slack Socket Mode supports up to 10 connections per token and arbitrarily distributes incoming events across them without channel routing. Sharing an app token across two processes causes events (including `stop all` and thread replies) to split between the two processes (`Q635`).
|
|
268
|
-
- **No impersonation (`slack:<handle>`)**: Every message delivered from Slack into the room is attributed as `slack:<handle>` (for example, `slack:ganesh`), never as a bare room member name (`Ganesh`), so a Slack display name cannot impersonate the room owner.
|
|
269
|
-
|
|
270
|
-
### Free vs Paid Slack Plans & the Slack AI Agent Surface (`hive-slack-agent.js`)
|
|
271
|
-
- **Typed `stop` works on every Slack plan (including Free)**: Replying `stop`, `stop all`, `stop <AgentName>`, `resume <AgentName>`, or `retry` in a channel thread requires only standard channel events (`slack-app-manifest-inbound.json`) and works on **all Slack plans**. We keep `slack-app-manifest-inbound.json` separate from `slack-app-manifest-agent.json` so Free-plan workspaces can install inbound `stop`/`retry` without hitting `feature_disabled`.
|
|
272
|
-
- **Native AI stop button & streamed plan cards (`hive-slack-agent.js`) require a paid Slack plan**: `hive-slack-agent.js` renders live room tasks as a streamed plan (`chat.startStream` / `chat.appendStream` / `chat.stopStream` with `task_display_mode: "plan"`) and enables Slack's native stop button via `agents.sessions.setStatus` (`status: "processing"` / `"active"`) and `agent_session_stopped`.
|
|
273
|
-
- **Who may act on the agent surface (same rules as `slack start`)**: `hive-slack-agent.js` checks every sender with the same `makeSlackActorCheck` rules. Any full workspace member may `stop` / `stop all` (or press Slack's native stop button); if Slack's `users.info` is down it still stops, and says `(stopped; I could not confirm who you are)`. `resume` needs the sender's member ID in `--allow <U123,U456>` (or `SLACK_ALLOW`). Guests and users from other workspaces are refused for everything, `status` included, and are told so in the thread; the room's tasks are never streamed to them. At startup it needs your workspace id from `auth.test` (refused with `Q634` if Slack omits it). A typed message counts only in the configured `--channel` or in a DM with the app (each user's own assistant DM is answered in that DM); messages from any other channel the bot is in are ignored.
|
|
274
|
-
- **Official Slack requirements for `hive-slack-agent.js`**:
|
|
275
|
-
- **Bot Token Scopes**: `chat:write` ([`chat.startStream`](https://docs.slack.dev/reference/methods/chat.startStream)), `assistant:write` ([`agents.sessions.setStatus`](https://docs.slack.dev/reference/methods/agents.sessions.setStatus) and [`agent_session_stopped`](https://docs.slack.dev/reference/events/agent_session_stopped)), `im:history`, `im:read`, `im:write` ([Developing AI Apps](https://docs.slack.dev/ai/developing-ai-apps)), plus `channels:read`, `groups:read`, `channels:history`, `groups:history`, `users:read`.
|
|
276
|
-
- **Bot Events**: `agent_session_stopped` ([`agent_session_stopped` event](https://docs.slack.dev/reference/events/agent_session_stopped)), `assistant_thread_started`, `assistant_thread_context_changed`, `message.im`, `message.channels`, `message.groups`.
|
|
277
|
-
- **App Settings**: `features.assistant_view` (`assistant_description` and `suggested_prompts`) and `settings.socket_mode_enabled: true` ([Agent Sessions](https://docs.slack.dev/ai/agent-sessions)).
|
|
278
|
-
- **Startup & `slack check --agent` verification**:
|
|
279
|
-
- At startup, `hive-slack-agent.js` (`preflightSlackAgent`) checks `x-oauth-scopes` from `auth.test` via `checkAgentScopes` and refuses with `Q634` (naming missing scopes such as `assistant:write, im:history` and `slack-app-manifest-agent.json`) before opening Socket Mode.
|
|
280
|
-
- Running `SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack check --channel D0123456789 --agent` verifies those scopes, posts a check message (`checkTs`), and calls `agents.sessions.setStatus({ status: 'processing', channel_id, thread_ts: checkTs })`. Per Slack's [`agent_session_stopped` docs](https://docs.slack.dev/reference/events/agent_session_stopped), if your app is not subscribed to `agent_session_stopped`, Slack returns a `missing_agent_session_stopped_event_subscription` warning (checked in both top-level `warning` and `response_metadata.warnings[]`) and renders a non-interactive spinner instead of the stop button; `slack check --agent` catches that warning (as well as `feature_disabled` and `thread_ts_required`) and refuses with `Q634`. On every exit path (pass, fail, timeout, or `Ctrl+C`), `slack check --agent` clears the session status back to `active` in a `finally` block so the DM is never left spinning.
|
|
281
|
-
- *(Note: `features.assistant_view` and `suggested_prompts` control the top-bar split-view UI in Slack and cannot be probed via a Web API read call; installing from `slack-app-manifest-agent.json` configures them declaratively.)*
|
|
282
|
-
|
|
283
|
-
**Starting the Agent Surface (`slack agent`):**
|
|
284
|
-
|
|
285
|
-
macOS / Linux (bash, zsh):
|
|
286
|
-
```bash
|
|
287
|
-
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack agent "wss://livecode-xoss.onrender.com|room-abc123" --channel D0123456789 --allow U012ABCDEF
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Windows (PowerShell):
|
|
291
|
-
```powershell
|
|
292
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
293
|
-
$env:SLACK_APP_TOKEN="xapp-…"
|
|
294
|
-
npx -y hivecode-mcp@latest slack agent "wss://livecode-xoss.onrender.com|room-abc123" --channel D0123456789 --allow U012ABCDEF
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
<details>
|
|
298
|
-
<summary><strong>Optional Paid-Plan AI Agent Surface Manifest (<code>slack-app-manifest-agent.json</code>)</strong></summary>
|
|
299
|
-
|
|
300
|
-
Paste the contents of [`slack-app-manifest-agent.json`](../slack-app-manifest-agent.json):
|
|
301
186
|
|
|
187
|
+
#### 3. Agent Surface Manifest (`slack-app-manifest-agent.json`)
|
|
302
188
|
```json
|
|
303
189
|
{
|
|
304
190
|
"display_information": {
|
|
@@ -362,113 +248,26 @@ Paste the contents of [`slack-app-manifest-agent.json`](../slack-app-manifest-ag
|
|
|
362
248
|
}
|
|
363
249
|
}
|
|
364
250
|
```
|
|
365
|
-
</details>
|
|
366
|
-
|
|
367
|
-
**macOS / Linux (bash, zsh):**
|
|
368
|
-
```bash
|
|
369
|
-
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF --dry-run
|
|
370
|
-
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
**Windows (PowerShell):**
|
|
374
|
-
```powershell
|
|
375
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
376
|
-
$env:SLACK_APP_TOKEN="xapp-…"
|
|
377
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF --dry-run
|
|
378
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Windows (Command Prompt / `cmd.exe`):**
|
|
382
|
-
```cmd
|
|
383
|
-
set SLACK_BOT_TOKEN=xoxb-…
|
|
384
|
-
set SLACK_APP_TOKEN=xapp-…
|
|
385
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF --dry-run
|
|
386
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### Slackbot Remote MCP Server (`hive-mcp-http.js`)
|
|
390
|
-
|
|
391
|
-
If you connect Slackbot's remote MCP client over Streamable HTTP (`hive-mcp-http.js`), it exposes fast room inspection and control tools (`hive_status`, `hive_members`, `hive_tasks`, `hive_claims`, `hive_recent`, `hive_pause`, `hive_resume`, `hive_say`).
|
|
392
|
-
|
|
393
|
-
- **Requires both `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN`**: In `--auth slack` mode (the default), `hive-mcp-http.js` refuses to start without `SLACK_SIGNING_SECRET` (`Q510`, copied from **Basic Information → App Credentials → Signing Secret**) **and** `SLACK_BOT_TOKEN` (`Q630`, `xoxb-…` with `users:read` scope so `users.info` can verify callers).
|
|
394
|
-
- **Discovers your workspace (`team_id`) from Slack at startup**: When you start `hive-mcp-http.js` without `--team` (`SLACK_TEAM_ID`), it calls Slack's `auth.test` using `SLACK_BOT_TOKEN` before serving requests. If `auth.test` rejects the token (`Q630`) or does not return a `team_id` (`Q634`), startup is refused immediately so external Slack Connect workspace users can never slip through by default.
|
|
395
|
-
- **Same access rules (`makeSlackActorCheck`)**:
|
|
396
|
-
- **Every request is HMAC-verified first** (`X-Slack-Signature` + 5-minute replay window, `Q511`–`Q513`) before `_meta.slack.user_id` is read.
|
|
397
|
-
- **Guests and other workspaces are refused** for all tools (`hive_pause`, `hive_resume`, `hive_say`).
|
|
398
|
-
- **`hive_pause` (`stop`) is fail-safe** for any full member of your workspace (and fails open with `(stopped; I could not confirm who you are)` if `users.info` is unreachable during a Slack outage).
|
|
399
|
-
- **`hive_resume` and `hive_say` require `--allow <U123,U456>`** (or `SLACK_ALLOW`). Messages sent via `hive_say` are recorded in room chat as `slack:<handle>`.
|
|
400
|
-
|
|
401
|
-
**macOS / Linux (bash, zsh):**
|
|
402
|
-
```bash
|
|
403
|
-
SLACK_SIGNING_SECRET=… SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack mcp-server "wss://livecode-xoss.onrender.com|room-abc123" --port 8790 --allow U012ABCDEF
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
**Windows (PowerShell):**
|
|
407
|
-
```powershell
|
|
408
|
-
$env:SLACK_SIGNING_SECRET="…"
|
|
409
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
410
|
-
npx -y hivecode-mcp@latest slack mcp-server "wss://livecode-xoss.onrender.com|room-abc123" --port 8790 --allow U012ABCDEF
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**What a First-Time User Sees Step by Step (`hive-mcp-http.js`):**
|
|
414
|
-
1. **If `SLACK_SIGNING_SECRET` is missing**: Process exits immediately (`code 2`) with:
|
|
415
|
-
`Q510 --auth slack needs SLACK_SIGNING_SECRET. Use --auth none only on localhost.`
|
|
416
|
-
2. **If `SLACK_BOT_TOKEN` is missing**: Process exits immediately (`code 2`) with:
|
|
417
|
-
`Q630 --auth slack needs SLACK_BOT_TOKEN. Set SLACK_BOT_TOKEN=xoxb-… in your environment (docs/SLACK_SETUP.md).`
|
|
418
|
-
3. **When both are set**: `hive-mcp-http.js` calls Slack `auth.test`, locks in your workspace `team_id`, joins the room, and prints:
|
|
419
|
-
`[hive-mcp-http] room room-abc123 on :8790/ auth=slack`
|
|
420
|
-
4. **When someone asks Slackbot what is happening (`hive_status`)**: Returns the live room name, agent count, paused list, and task counts.
|
|
421
|
-
5. **When a coworker triggers `hive_pause`**: Pauses the named agent (or `all`) on its own client (`paused AgentA — it will finish its current step and then stop picking up work`), while a guest or Slack Connect user from another workspace gets `:no_entry: Refused: guests and external Slack Connect users cannot control or message this room.`
|
|
422
|
-
6. **When someone not in `--allow` triggers `hive_resume` or `hive_say`**: Refused with `:no_entry: Refused: only Slack user IDs listed in --allow may resume/retry agents or send replies into the room (your ID: U…). Any full workspace member may still type stop.` When a user listed in `--allow` calls `hive_say`, it lands in the room attributed as `slack:<handle>`.
|
|
423
251
|
|
|
424
252
|
---
|
|
425
253
|
|
|
426
|
-
##
|
|
427
|
-
|
|
428
|
-
> **Running from a local checkout before npm publish:** Until the latest release is published to npm (`npx -y hivecode-mcp@latest` on npm is `0.12.4` prior to release), replace `npx -y hivecode-mcp@latest` in the commands below with `node C:/Users/G1/Desktop/N-team/hive-mcp.js` (or `node ./hive-mcp.js` from your repo root).
|
|
429
|
-
|
|
430
|
-
Use these 5 steps to demonstrate either **GitHub-to-Slack issue threads** (`github start --slack-channel --slack-allow`) or a **live room bridge** (`slack start --channel --allow`) end to end:
|
|
431
|
-
|
|
432
|
-
1. **Install the App & Copy Tokens**:
|
|
433
|
-
- Create the Slack app from the **Optional Inbound Manifest** in Section 5 above and install it to your workspace.
|
|
434
|
-
- Copy `SLACK_BOT_TOKEN` (`xoxb-…`) from **OAuth & Permissions**, and generate `SLACK_APP_TOKEN` (`xapp-…`, named `socket-mode` with `connections:write`) under **Basic Information → App-Level Tokens**.
|
|
435
|
-
2. **Invite `@Hivecode` & Copy IDs**:
|
|
436
|
-
- In your Slack channel, run `/invite @Hivecode` and copy the **Channel ID** (`C…`) from channel details.
|
|
437
|
-
- Copy your own **Slack Member ID** (`U…`) from your Slack profile (**Profile → `⋯` → Copy member ID**).
|
|
438
|
-
3. **Start the Watcher or Room Bridge**:
|
|
439
|
-
- **Flow A — GitHub Issue Threads + Slack `stop` / `retry` (PowerShell):**
|
|
440
|
-
```powershell
|
|
441
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
442
|
-
$env:SLACK_APP_TOKEN="xapp-…"
|
|
443
|
-
npx -y hivecode-mcp@latest github start --agent 'claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode' --slack-channel C0123456789 --slack-allow U012ABCDEF --dry-run
|
|
444
|
-
npx -y hivecode-mcp@latest github start --agent 'claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode' --slack-channel C0123456789 --slack-allow U012ABCDEF
|
|
445
|
-
```
|
|
446
|
-
- **Flow B — Live Room Bridge + Slack `stop` / `resume` / Thread Replies (PowerShell):**
|
|
447
|
-
```powershell
|
|
448
|
-
$env:SLACK_BOT_TOKEN="xoxb-…"
|
|
449
|
-
$env:SLACK_APP_TOKEN="xapp-…"
|
|
450
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF --dry-run
|
|
451
|
-
npx -y hivecode-mcp@latest slack start "wss://livecode-xoss.onrender.com|room-abc123" --channel C0123456789 --allow U012ABCDEF
|
|
452
|
-
```
|
|
453
|
-
4. **Trigger a Thread**:
|
|
454
|
-
- **In Flow A**: Add the `hivecode` label to a GitHub issue. Watch Slack open a single thread (`#12 — Title`) for that issue and post run progress inside it.
|
|
455
|
-
- **In Flow B**: Assign a task in the room. Watch Slack open a thread for that task and post gate checks and task chat inside it.
|
|
456
|
-
5. **Control from the Slack Thread**:
|
|
457
|
-
- **In Flow A (`github start`)**: Reply `stop` inside the issue's Slack thread to stop the active run (posts `/hivecode stop` on GitHub and ends the room). Reply `retry` (permitted because your ID is in `--slack-allow`) to post `/hivecode retry` and start a fresh run.
|
|
458
|
-
- **In Flow B (`slack start`)**: Reply `stop all` (or `stop <AgentName>`) inside the thread to pause agents on their own clients (`controls` map -> `paused`). Reply `resume all` or reply with instructions (`@AgentName …`, delivered into the room as `slack:<your-handle>`).
|
|
459
|
-
|
|
460
|
-
---
|
|
254
|
+
## 5. Troubleshooting Reference
|
|
461
255
|
|
|
462
|
-
|
|
256
|
+
For complete error explanations and remedies, refer to [`docs/TROUBLESHOOTING.md`](TROUBLESHOOTING.md).
|
|
463
257
|
|
|
464
|
-
| Code |
|
|
258
|
+
| Code | Component | Cause & Remedy |
|
|
465
259
|
| :--- | :--- | :--- |
|
|
466
|
-
| **`
|
|
467
|
-
| **`
|
|
468
|
-
| **`
|
|
469
|
-
| **`
|
|
470
|
-
| **`
|
|
471
|
-
| **`
|
|
472
|
-
| **`
|
|
473
|
-
| **`
|
|
474
|
-
| **`
|
|
260
|
+
| **`Q660`** | Relay | `HIVE_SLACK_KEY` encryption key is not configured on the relay (HTTP 503). Set a 32-byte hex secret on the relay server. |
|
|
261
|
+
| **`Q661`** | Relay / CLI | Connection ID is unknown or expired. Re-run `slack connect`. |
|
|
262
|
+
| **`Q662`** | CLI / Relay | Slack rejected the bot token (`auth.test` failed). Ensure token begins with `xoxb-` and the app is installed. |
|
|
263
|
+
| **`Q663`** | Relay | Bad Slack signature or timestamp outside 5-minute replay window (HTTP 401). Verify Signing Secret. |
|
|
264
|
+
| **`Q664`** | Relay | Bot is not in the channel. In Slack, run `/invite @Hivecode` in the target channel. |
|
|
265
|
+
| **`Q665`** | CLI | Invalid room join link syntax provided to `slack connect` or `slack disconnect`. |
|
|
266
|
+
| **`Q666`** | CLI | Network failure reaching relay HTTP endpoint. |
|
|
267
|
+
| **`Q667`** | CLI | Required `SLACK_BOT_TOKEN` or `SLACK_SIGNING_SECRET` was not provided. |
|
|
268
|
+
| **`Q668`** | CLI | Bot is in no channel (run `/invite @Hivecode`), or multiple channels without `--channel` in non-interactive mode. |
|
|
269
|
+
| **`Q630`** | `slack start` | `SLACK_BOT_TOKEN` missing or revoked. |
|
|
270
|
+
| **`Q631`** | `slack start` | Bot not invited to `--channel`. Run `/invite @Hivecode`. |
|
|
271
|
+
| **`Q632`** | `slack start` | Another bridge with the same name is already running in this room. |
|
|
272
|
+
| **`Q634`** | `slack start` | Missing `SLACK_APP_TOKEN` (`xapp-…`) or required scopes for Socket Mode. |
|
|
273
|
+
| **`Q635`** | `slack start` | Multiple active processes sharing the same `SLACK_APP_TOKEN`. |
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -56,7 +56,7 @@ This guide explains every error code (`Qxxx`) emitted by Hivecode CLI commands,
|
|
|
56
56
|
| `Q571` | `hive-branch.js` | Landing branch PR | Working tree has uncommitted changes on the checkout branch. | Commit or stash changes (`git stash`) before landing PR. |
|
|
57
57
|
| `Q572` | `hive-branch.js` | Landing branch PR | The room deleted files that were changed on the branch upstream since seeded. | Restore deleted files in the room or delete them upstream on the branch manually. |
|
|
58
58
|
| `Q573` | `hive-branch.js` | Landing branch PR | Local commits landed on branch successfully, but `git push` failed. | Check remote permissions and push manually (`git push origin <branch>`). |
|
|
59
|
-
| `Q574` | `hive-branch.js` | Opening PR from branch | No base branch specified
|
|
59
|
+
| `Q574` | `hive-branch.js`, `hive-land.js`, `hive-gh-action.js` | Opening PR from branch or Action landing | No base branch specified (nothing changed), `gh pr create` failed, or GitHub Actions lacks PR creation permissions (HTTP 403; work is already pushed to branch). | Enable Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests", or open the PR manually from the branch. Once enabled, the next run opens the PR without changes in the room. For local runs, pass `--base <branch>` or check `gh auth`. |
|
|
60
60
|
| `Q580` | `sync.js` | Syncing a room folder | Room directory was deleted or moved while sync was active. | Recreate the folder or re-join the room; files on teammates' clients were protected. |
|
|
61
61
|
| `Q581` | `hive-mcp.js` | Joining a room (`hive_join`) | Participant name is already in use by another connected participant. | Choose a distinct display name via `--name` or in `hive_join`. |
|
|
62
62
|
| `Q600` | `hive-github.js` | Running `github start` or watcher background polling | GitHub CLI (`gh`) not installed, `gh auth login` required, or token expired/HTTP 401. | Install GitHub CLI (`gh`), run `gh auth login`, or set valid `GH_TOKEN`. |
|
|
@@ -115,3 +115,12 @@ This guide explains every error code (`Qxxx`) emitted by Hivecode CLI commands,
|
|
|
115
115
|
| `Q655` | `hive-room-mode.js` | Starting room-mode watcher | Another room-mode watcher is already active for this room (watcher lock held). | Stop the other running watcher process, or remove the stale lock file in `.git/hive-room-watcher.lock` if crashed. |
|
|
116
116
|
| `Q656` | `hive-room-mode.js` | Room-mode watcher connecting to relay | Could not connect to relay at specified URL, or connection timed out. | Verify internet connectivity, check relay status, and ensure the relay URL is correct. |
|
|
117
117
|
| `Q657` | `hive-room-mode.js` | Dispatching an issue task or chat into room | Delivery verification failed: task assignment or chat message was not confirmed in room within timeout. | Check relay stability, ensure room has reachable participants, and retry with `/hivecode retry`. |
|
|
118
|
+
| `Q660` | `server.js`, `hive-slack-relay.js` | Calling relay Slack endpoints (`/slack/connect`, `/slack/prepare`) | `HIVE_SLACK_KEY` encryption key is not set in the relay environment (HTTP 503). | When self-hosting a relay, set `HIVE_SLACK_KEY` to a 64-hex-character (32-byte) secret to enable encrypted credential storage. |
|
|
119
|
+
| `Q661` | `server.js`, `hive-slack-relay.js` | Slack events webhook or `slack connect` / `disconnect` | Connection ID is unknown, expired, or invalid. | Run `slack connect "<room link>"` to establish a new Slack connection. Prepared IDs expire after 24 hours if unconnected. |
|
|
120
|
+
| `Q662` | `server.js`, `hive-slack-connect.js` | Verifying bot token during `slack connect` | Slack rejected the bot token (`auth.test` failed or invalid bot token). | Check that `SLACK_BOT_TOKEN` is the Bot User OAuth Token (`xoxb-…`) and the Slack app is installed to the workspace. |
|
|
121
|
+
| `Q663` | `server.js`, `hive-slack-relay.js` | Relay receiving Slack event notification | Slack request verification failed: invalid v0 HMAC-SHA256 signature or timestamp outside 5-minute replay window (HTTP 401). | Verify that the signing secret registered during `slack connect` matches the Slack app's Signing Secret. Check server clock NTP sync. |
|
|
122
|
+
| `Q664` | `server.js`, `hive-slack-relay.js` | Relay connecting Slack to room channel | Bot user is not a member of the designated Slack channel. | In Slack, open the target channel and run `/invite @Hivecode`, then run `slack connect` again. |
|
|
123
|
+
| `Q665` | `hive-slack-connect.js` | Running `slack connect` or `slack disconnect` | Provided target is not a valid room link. | Pass a valid room join link (format: `wss://<relay>|room-<id>` or `https://<relay>/#room-<id>`). |
|
|
124
|
+
| `Q666` | `hive-slack-connect.js` | Running `slack connect` or `slack disconnect` | Could not reach the relay server over HTTP/HTTPS. | Verify network connectivity to the relay host and check that the relay server is up and responsive. |
|
|
125
|
+
| `Q667` | `hive-slack-connect.js` | Running `slack connect` or `slack disconnect` | Missing required secret: `SLACK_BOT_TOKEN` or `SLACK_SIGNING_SECRET`. | Provide the missing secret interactively when prompted (hidden input) or set the environment variables `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. |
|
|
126
|
+
| `Q668` | `hive-slack-connect.js` | Selecting channel during `slack connect` | The bot is in no channels (or multiple channels without `--channel` in non-interactive mode). | In Slack, invite the bot with `/invite @Hivecode`. In non-interactive mode with multiple channels, specify `--channel <id>`. |
|
package/hive-gh-action.js
CHANGED
|
@@ -40,6 +40,11 @@ const markOf = (body, kind) => {
|
|
|
40
40
|
const oneLine = (s, n = 300) => String(s == null ? '' : s).replace(/\s+/g, ' ').trim().slice(0, n)
|
|
41
41
|
const PUSHERS = new Set(['admin', 'maintain', 'write'])
|
|
42
42
|
const ISSUE_TASK = (n) => new RegExp(`^Issue #${n}(?: \\(attempt \\d+\\))?:`)
|
|
43
|
+
// Test output that means "the runner has no installed dependencies", in Node and in Python. The fix
|
|
44
|
+
// is a step in the workflow file, which nobody in the room can add, so the issue comment names it.
|
|
45
|
+
const MISSING_DEPS = /Cannot find (?:module|package)|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|No module named/
|
|
46
|
+
const PR_NOT_ALLOWED = /not permitted to create or approve pull requests/i
|
|
47
|
+
const PR_SETTING = 'Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests"'
|
|
43
48
|
|
|
44
49
|
/** The issue a pull request closes, from the "Closes #N" Hivecode writes first in its body. */
|
|
45
50
|
export function closesOf(body) {
|
|
@@ -124,7 +129,9 @@ export async function roomDeps({ link, root, branch, github }) {
|
|
|
124
129
|
* Returns { did: [what it did, for the log] }. A refusal is a result; a throw is a failed job, which
|
|
125
130
|
* GitHub shows in red and the next run retries.
|
|
126
131
|
*/
|
|
127
|
-
export async function runAction({ gh, deps, eventName = '', event = {}, branch, label = LABEL, base = '', log = () => { }, notify = async () => false }) {
|
|
132
|
+
export async function runAction({ gh, deps, eventName = '', event = {}, branch, label = LABEL, base = '', runAt = '', log = () => { }, notify = async () => false }) {
|
|
133
|
+
// runAt: the commit this run's checkout is at (the default branch, where the workflow file lives).
|
|
134
|
+
// A landing refused for something the workflow has to fix is tried again when it changes.
|
|
128
135
|
const did = []
|
|
129
136
|
const note = (s) => { did.push(s); log(s) }
|
|
130
137
|
// What a person follows in Slack or Discord (HIVE_NOTIFY_WEBHOOK): the issue's life, never the room link.
|
|
@@ -228,32 +235,45 @@ export async function runAction({ gh, deps, eventName = '', event = {}, branch,
|
|
|
228
235
|
const task = issueTasks(tasks, n)[0]
|
|
229
236
|
if (!task) { note(`#${n}: its task is not in the room (the relay lost the room?); nothing to land`); return { did } }
|
|
230
237
|
let openPr = pulls.find((p) => p.state === 'open' && closesOf(p.body) === n) || null
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
//
|
|
238
|
+
const lands = commentsOf(n).map((c) => markOf(c.body, 'land')).filter(Boolean)
|
|
239
|
+
const lastLand = lands[lands.length - 1] || null
|
|
240
|
+
// The issue's work is on the branch on origin once a landing pushed it: one that opened or updated
|
|
241
|
+
// a pull request (ok=1), or one that pushed and could not open the pull request (pushed=1).
|
|
242
|
+
const workOnBranch = lands.some((l) => l.ok === '1' || l.pushed === '1')
|
|
243
|
+
// "Closes #N" goes in first, so merging the pull request closes the issue and the next run sees
|
|
244
|
+
// it as the issue's.
|
|
245
|
+
const link = async (pr) => {
|
|
246
|
+
try {
|
|
247
|
+
gh.updatePull(pr.number, { body: `Closes #${n}\n\n${String(pr.body || '')}` })
|
|
248
|
+
pr.body = `Closes #${n}\n\n${String(pr.body || '')}`
|
|
249
|
+
comment(n, `#${pr.number} is this issue's pull request: it is on the room's branch and holds this issue's work, so Hivecode linked it.\n\n${MARK('adopted', { pr: pr.number })}`)
|
|
250
|
+
await deps.say(`@all PR #${pr.number} on the room's branch is now issue #${n}'s pull request (it closes #${n} when merged).`)
|
|
251
|
+
await tell(`PR #${pr.number} is now the pull request for issue #${n}.`)
|
|
252
|
+
note(`#${n}: linked PR #${pr.number}`)
|
|
253
|
+
return true
|
|
254
|
+
} catch (e) { note(`#${n}: could not link PR #${pr.number}: ${oneLine(e.message, 160)}`); return false }
|
|
255
|
+
}
|
|
256
|
+
// A PULL REQUEST ON THE BRANCH THAT SOMEONE ELSE OPENED (an agent, a person) is this issue's ONLY
|
|
257
|
+
// ONCE IT HOLDS THIS ISSUE'S WORK (11 Nov 2026). A pull request head is the branch, so it holds the
|
|
258
|
+
// work from the landing's push on. Before that it is somebody's own change that happens to sit on
|
|
259
|
+
// the room's branch: linking it made merging a setup fix close issue #22 with nothing landed.
|
|
234
260
|
if (!openPr) {
|
|
235
261
|
const other = pulls.find((p) => p.state === 'open' && !closesOf(p.body))
|
|
236
|
-
if (other) {
|
|
237
|
-
|
|
238
|
-
gh.updatePull(other.number, { body: `Closes #${n}\n\n${String(other.body || '')}` })
|
|
239
|
-
other.body = `Closes #${n}\n\n${String(other.body || '')}`
|
|
240
|
-
openPr = other
|
|
241
|
-
comment(n, `#${other.number} is this issue's pull request: it was opened on the room's branch, so Hivecode linked it.\n\n${MARK('adopted', { pr: other.number })}`)
|
|
242
|
-
await deps.say(`@all PR #${other.number} on the room's branch is now issue #${n}'s pull request (it closes #${n} when merged).`)
|
|
243
|
-
await tell(`PR #${other.number} is now the pull request for issue #${n}.`)
|
|
244
|
-
note(`#${n}: linked PR #${other.number}`)
|
|
245
|
-
} catch (e) { note(`#${n}: could not link PR #${other.number}: ${oneLine(e.message, 160)}`) }
|
|
246
|
-
}
|
|
262
|
+
if (other && workOnBranch) { if (await link(other)) openPr = other }
|
|
263
|
+
else if (other) note(`#${n}: PR #${other.number} is open on ${branch} but holds none of this issue's work yet; left alone`)
|
|
247
264
|
}
|
|
248
|
-
const
|
|
249
|
-
const
|
|
265
|
+
const prOnlyBefore = !!lastLand && lastLand.code === 'Q574' && lastLand.pushed === '1'
|
|
266
|
+
const tryAgain = prOnlyBefore || (!!lastLand && lastLand.deps === '1' && !!runAt && lastLand.at !== runAt)
|
|
250
267
|
if (task.status === 'done') {
|
|
251
268
|
await deps.bind()
|
|
252
269
|
const r = await deps.landIssue({
|
|
253
270
|
taskId: task.id,
|
|
254
271
|
issue: { number: n, title: active.title },
|
|
255
|
-
// The same plan, branch and room as the last landing, ok or not, is not run again.
|
|
256
|
-
|
|
272
|
+
// The same plan, branch and room as the last landing, ok or not, is not run again. Two refusals
|
|
273
|
+
// are, because what fixes them changes none of the three: the pull request GitHub would not
|
|
274
|
+
// let the job open (a repository setting; nothing new lands, so no tests run again), and tests
|
|
275
|
+
// that found no installed dependencies, once the workflow file has changed.
|
|
276
|
+
state: { lastFailedFingerprint: lastLand && !tryAgain ? lastLand.fp : '' },
|
|
257
277
|
by: 'GitHub Actions',
|
|
258
278
|
closes: true,
|
|
259
279
|
skipEmpty: !!openPr,
|
|
@@ -267,21 +287,49 @@ export async function runAction({ gh, deps, eventName = '', event = {}, branch,
|
|
|
267
287
|
: `@all PR #${r.pr.number} for issue #${n} was updated${r.pr.newCommits ? ` with ${r.pr.newCommits} new commit(s)` : ''}.`)
|
|
268
288
|
note(`#${n}: ${made ? 'opened' : 'updated'} PR #${r.pr.number}`)
|
|
269
289
|
await tell(made ? `issue #${n} is up for review: ${r.pr.url || `PR #${r.pr.number}`}` : `PR #${r.pr.number} for issue #${n} was updated with the room's fixes.`)
|
|
290
|
+
// The landing went onto a pull request that was already open on the branch and is not yet the
|
|
291
|
+
// issue's: it holds the issue's work now, so it is linked in this run.
|
|
292
|
+
if (!openPr) {
|
|
293
|
+
const landedOn = pulls.find((p) => p.state === 'open' && p.number === r.pr.number && !closesOf(p.body))
|
|
294
|
+
if (landedOn && await link(landedOn)) openPr = landedOn
|
|
295
|
+
}
|
|
270
296
|
} else if (r && r.code) {
|
|
271
297
|
// A refusal with a code is said once per landing (its fingerprint), on the issue and in the room.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
298
|
+
const fp = r.fingerprint || ''
|
|
299
|
+
const why = String(r.why || '')
|
|
300
|
+
// PUSHED, BUT NO PULL REQUEST (11 Nov 2026): the commits were on the branch and the comment
|
|
301
|
+
// said "Nothing was pushed". Said once however often it is tried again.
|
|
302
|
+
const prOnly = r.code === 'Q574' && !!r.onBranch
|
|
303
|
+
const noDeps = r.code === 'Q576' && MISSING_DEPS.test(why)
|
|
304
|
+
const repeat = prOnly ? prOnlyBefore
|
|
305
|
+
: !!lastLand && lastLand.fp === fp && lastLand.code === r.code && (!noDeps || !runAt || lastLand.at === runAt)
|
|
306
|
+
if (prOnly && !repeat) {
|
|
307
|
+
const reason = oneLine(why.replace(/^[\s\S]*?opening the PR failed:\s*/, ''), 400)
|
|
308
|
+
const setting = PR_NOT_ALLOWED.test(why)
|
|
309
|
+
comment(n, `The work for this issue is pushed to \`${branch}\`, but Hivecode could not open the pull request (Q574): ${reason}\n` +
|
|
310
|
+
(setting ? `GitHub does not let Actions open pull requests in this repository. Turn it on: ${PR_SETTING} (in an organization, an owner may have to allow it first). The next run then opens the pull request: Actions → Hivecode → Run workflow, or the 10-minute timer.\n`
|
|
311
|
+
: 'The next run tries to open it again.\n') +
|
|
312
|
+
`Or open a pull request from \`${branch}\` by hand: Hivecode links it to this issue.\n\n${MARK('land', { fp, code: r.code, pushed: 1 })}`)
|
|
313
|
+
await tell(`issue #${n}: the work is pushed to ${branch}, but the pull request could not be opened (Q574). ${setting ? 'Turn on "Allow GitHub Actions to create and approve pull requests" in the repository\'s Actions settings, or open it by hand.' : 'Open it by hand, or wait for the next run.'}`)
|
|
314
|
+
await deps.say(`@all issue #${n} is done and its work is pushed to ${branch}, but the pull request could not be opened (Q574): ${reason}. A person has to ${setting ? 'change a repository setting' : 'look at it'} or open the pull request by hand; there is nothing to fix in the room.`)
|
|
315
|
+
} else if (!repeat) {
|
|
316
|
+
const hint = noDeps ? `The tests did not find the project's dependencies on the runner. Add an install step before the Hivecode step in \`.github/workflows/hivecode.yml\` (for npm: \`- run: npm ci\`); the landing is tried again once that change is on the default branch.\n` : ''
|
|
317
|
+
comment(n, `Hivecode could not land this (${r.code}): ${oneLine(why, 600)}\nNothing ${workOnBranch ? 'new ' : ''}was pushed. ${hint}It is tried again when the room's files or the branch change.\n\n${MARK('land', { fp, code: r.code, ...(noDeps ? { deps: 1, ...(runAt ? { at: runAt } : {}) } : {}) })}`)
|
|
318
|
+
await tell(`issue #${n}: the landing was refused (${r.code}). Nothing was pushed; the room was told.${noDeps ? ' The workflow file needs an install step.' : ''}`)
|
|
319
|
+
if (r.code !== 'Q576') await deps.say(`@all issue #${n} is done, but its landing was refused (${r.code}): ${oneLine(why, 400)} Nothing was pushed.`)
|
|
276
320
|
}
|
|
277
321
|
note(`#${n}: landing refused (${r.code})`)
|
|
278
322
|
} else note(`#${n}: not ready to land: ${oneLine(r && r.why, 200)}`)
|
|
279
323
|
} else note(`#${n}: task ${task.id} is ${task.status}; waiting for the room`)
|
|
280
324
|
|
|
281
|
-
// ── 6.
|
|
325
|
+
// ── 6. comments back to the room: every comment on the issue from someone who can push, and
|
|
326
|
+
// once its pull request is open, the reviews and comments there too; failed checks ──────
|
|
327
|
+
{
|
|
328
|
+
const fb = await deps.prFeedback({ gh, issueNumber: n, prNumber: openPr ? openPr.number : 0, state: { seen: seenFromTasks(tasks), allComments: true } })
|
|
329
|
+
const where = openPr ? `PR #${openPr.number} and issue #${n}` : `issue #${n}`
|
|
330
|
+
if (fb && fb.ok && fb.items && fb.items.length && !fb.already) { note(`${where}: ${fb.items.length} comment(s) sent to the room`); await tell(`${fb.items.length} comment(s) on ${where} went to the room.`) }
|
|
331
|
+
}
|
|
282
332
|
if (openPr) {
|
|
283
|
-
const fb = await deps.prFeedback({ gh, issueNumber: n, prNumber: openPr.number, state: { seen: seenFromTasks(tasks) } })
|
|
284
|
-
if (fb && fb.ok && fb.items && fb.items.length && !fb.already) { note(`PR #${openPr.number}: ${fb.items.length} review comment(s) sent to the room`); await tell(`${fb.items.length} review comment(s) on PR #${openPr.number} went to the room.`) }
|
|
285
333
|
const sha = openPr.head && openPr.head.sha
|
|
286
334
|
if (sha) {
|
|
287
335
|
const GH = await import('./hive-github.js')
|
|
@@ -304,9 +352,35 @@ export async function runAction({ gh, deps, eventName = '', event = {}, branch,
|
|
|
304
352
|
}
|
|
305
353
|
|
|
306
354
|
// ── the workflow file a repository adds ──────────────────────────────────────────────────────────
|
|
307
|
-
|
|
355
|
+
/** How this repository installs what its tests need, from the lockfile it keeps: { found, commands }.
|
|
356
|
+
* Nothing recognised is found '' and no commands; the workflow then says where the step goes. */
|
|
357
|
+
export function installStepsFor(dir) {
|
|
358
|
+
const has = (f) => { try { return fs.statSync(path.join(dir, f)).isFile() } catch { return false } }
|
|
359
|
+
for (const [file, command] of [
|
|
360
|
+
['package-lock.json', 'npm ci'],
|
|
361
|
+
['npm-shrinkwrap.json', 'npm ci'],
|
|
362
|
+
['pnpm-lock.yaml', 'corepack enable && pnpm install --frozen-lockfile'],
|
|
363
|
+
['yarn.lock', 'yarn install --frozen-lockfile'],
|
|
364
|
+
['package.json', 'npm install'],
|
|
365
|
+
]) if (has(file)) return { found: file, commands: [command] }
|
|
366
|
+
return { found: '', commands: [] }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function workflowFile({ branch = '<branch>', version = 'latest', install = [] } = {}) {
|
|
370
|
+
// THE LANDING RUNS THE REPOSITORY'S TESTS ON THE RUNNER, which starts with no dependencies: without
|
|
371
|
+
// this step every first landing on a project with dependencies failed (11 Nov 2026).
|
|
372
|
+
// `install` is what installStepsFor answers, or just its list of commands.
|
|
373
|
+
const steps = (Array.isArray(install) ? install : (install && Array.isArray(install.commands) ? install.commands : [])).map((c) => String(c || '').replace(/[\r\n]+/g, ' ').trim()).filter(Boolean)
|
|
374
|
+
const installBlock = steps.length
|
|
375
|
+
? ` # The landing runs your tests on this runner, so what they need is installed first.\n${steps.map((c) => ` - run: ${c}`).join('\n')}\n`
|
|
376
|
+
: ` # The landing runs your tests on this runner. No package.json or lockfile was found, so nothing
|
|
377
|
+
# is installed. If your tests need dependencies, add the install step here, for example:
|
|
378
|
+
# - run: pip install -r requirements.txt
|
|
379
|
+
`
|
|
308
380
|
return `# Hivecode: GitHub issues go to the agents in a room, and their work comes back as a pull request.
|
|
309
381
|
# Needs one repository secret, HIVE_ROOM: the room's link (Settings -> Secrets and variables -> Actions).
|
|
382
|
+
# And one setting, so the job may open the pull request: Settings -> Actions -> General ->
|
|
383
|
+
# Workflow permissions -> "Allow GitHub Actions to create and approve pull requests".
|
|
310
384
|
name: Hivecode
|
|
311
385
|
|
|
312
386
|
on:
|
|
@@ -347,7 +421,7 @@ jobs:
|
|
|
347
421
|
- uses: actions/setup-node@v4
|
|
348
422
|
with:
|
|
349
423
|
node-version: 22
|
|
350
|
-
- run: npx -y hivecode-mcp@${version} github action
|
|
424
|
+
${installBlock} - run: npx -y hivecode-mcp@${version} github action
|
|
351
425
|
env:
|
|
352
426
|
HIVE_ROOM: \${{ secrets.HIVE_ROOM }}
|
|
353
427
|
HIVE_BRANCH: ${branch}
|
|
@@ -361,7 +435,10 @@ jobs:
|
|
|
361
435
|
export async function main(argv = [], { cwd = process.cwd(), env = process.env, log = console.log, logErr = console.error, call } = {}) {
|
|
362
436
|
if (argv.includes('--workflow')) {
|
|
363
437
|
const bi = argv.indexOf('--branch')
|
|
364
|
-
|
|
438
|
+
// What to install is read from the repository this is run in (its top level, when it is one).
|
|
439
|
+
const here = git(cwd, ['rev-parse', '--show-toplevel'])
|
|
440
|
+
const found = installStepsFor(here.code === 0 && here.out ? path.resolve(here.out) : cwd)
|
|
441
|
+
log(workflowFile({ branch: bi !== -1 && argv[bi + 1] ? argv[bi + 1] : '<branch>', install: found.commands }))
|
|
365
442
|
return 0
|
|
366
443
|
}
|
|
367
444
|
const link = String(env.HIVE_ROOM || '').trim()
|
|
@@ -395,7 +472,8 @@ export async function main(argv = [], { cwd = process.cwd(), env = process.env,
|
|
|
395
472
|
try {
|
|
396
473
|
const W = await import('./hive-webhook.js')
|
|
397
474
|
const notify = W.makeNotifier(String(env.HIVE_NOTIFY_WEBHOOK || '').trim(), { log })
|
|
398
|
-
const
|
|
475
|
+
const runAt = git(root, ['rev-parse', 'HEAD']).out.slice(0, 12)
|
|
476
|
+
const r = await runAction({ gh, deps, eventName: env.GITHUB_EVENT_NAME || '', event, branch, label, base, runAt, log, notify })
|
|
399
477
|
if (!r.did.length) log('nothing to do')
|
|
400
478
|
return 0
|
|
401
479
|
} catch (e) {
|
package/hive-land.js
CHANGED
|
@@ -382,7 +382,9 @@ export async function landAndOpen(opts = {}) {
|
|
|
382
382
|
let pr = null
|
|
383
383
|
try { pr = github.createPull({ title: o.title || `Hivecode: ${name}`, head: name, base: into, body }) } catch (e) {
|
|
384
384
|
const again = e && e.status === 422 ? (github.pullsFor(name) || []).find((p) => p.state === 'open') : null
|
|
385
|
-
|
|
385
|
+
// onBranch: origin has the work and only the pull request is missing. A caller that speaks for
|
|
386
|
+
// the landing (the GitHub Action) must not say "nothing was pushed", and tries the PR again.
|
|
387
|
+
if (!again) return refuse('Q574', `the work is pushed to ${name}, but opening the PR failed: ${e.message}`, { onBranch: true })
|
|
386
388
|
pr = again
|
|
387
389
|
}
|
|
388
390
|
const closed = prs.filter((p) => p.state !== 'open').map((p) => `#${p.number}`)
|
package/hive-room-mode.js
CHANGED
|
@@ -1314,6 +1314,8 @@ async function landIssueOn(opts = {}) {
|
|
|
1314
1314
|
why: landRes.why,
|
|
1315
1315
|
fingerprint,
|
|
1316
1316
|
state: nextState,
|
|
1317
|
+
// Q574 after the push: the branch on origin holds the work, only the pull request is missing.
|
|
1318
|
+
...(landRes.onBranch ? { onBranch: true } : {}),
|
|
1317
1319
|
}
|
|
1318
1320
|
}
|
|
1319
1321
|
|
|
@@ -1398,6 +1400,10 @@ function askOfPrComment(key, c, state = {}) {
|
|
|
1398
1400
|
}
|
|
1399
1401
|
const m = body.match(/^\/hivecode\b\s*([\s\S]*)$/i)
|
|
1400
1402
|
if (m) return /^(stop|retry)\b/i.test(m[1].trim()) ? '' : m[1].trim()
|
|
1403
|
+
// EVERY COMMENT (the GitHub Action, 0.14.12): a plain comment on the issue or the pull request,
|
|
1404
|
+
// from someone who can push, goes to the room too. The watcher leaves this off, so "thanks" there
|
|
1405
|
+
// still spends no run.
|
|
1406
|
+
if (state.allComments && (key === 'ic' || key === 'pc')) return body
|
|
1401
1407
|
return key === 'ic' && state.question ? body : ''
|
|
1402
1408
|
}
|
|
1403
1409
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// hive-slack-connect.js -- `slack connect` / `slack disconnect`: Slack <-> a room through the relay, with
|
|
2
|
+
// nothing running on anyone's machine (issue #22, part B). The relay does the work (parts A and C); this
|
|
3
|
+
// is the setup: ask the relay for a request URL, print the app manifest with it, take the bot token and
|
|
4
|
+
// signing secret once, let the person pick the channel, hand all of it to the relay.
|
|
5
|
+
//
|
|
6
|
+
// Relay contract (v2): POST /slack/prepare {room} -> {ok,id,eventsUrl}; POST /slack/connect {id,botToken,
|
|
7
|
+
// signingSecret,channel} -> {ok,team,channel,eventsUrl}; POST /slack/disconnect {id|room,signingSecret} -> {ok}.
|
|
8
|
+
// Errors come back as {ok:false,code,why}. Codes here: Q665 bad room link, Q666 relay not reached,
|
|
9
|
+
// Q667 token or signing secret missing, Q668 channel to choose.
|
|
10
|
+
// The secrets go to the relay and nowhere else: not to the log, not to a file, not to the room.
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import os from 'os'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
import readline from 'readline'
|
|
15
|
+
import { parseLink } from './sync.js'
|
|
16
|
+
import { slackPost } from './hive-slack.js'
|
|
17
|
+
|
|
18
|
+
export const CONNECT_USAGE = `usage:
|
|
19
|
+
npx -y hivecode-mcp@latest slack connect "<relay|room>" [--channel <id>] (SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET, or it asks)
|
|
20
|
+
npx -y hivecode-mcp@latest slack disconnect "<relay|room>" [--id <connection id>] (SLACK_SIGNING_SECRET, or it asks)
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const BOT_SCOPES = ['chat:write', 'channels:history', 'groups:history', 'users:read', 'channels:read', 'groups:read']
|
|
24
|
+
|
|
25
|
+
/** The Slack app to create from a manifest: events to the relay, no Socket Mode, so no xapp- token. */
|
|
26
|
+
export function slackManifest(eventsUrl) {
|
|
27
|
+
return {
|
|
28
|
+
display_information: { name: 'Hivecode', description: 'Messages between a Slack channel and a Hivecode room.', background_color: '#111318' },
|
|
29
|
+
features: { bot_user: { display_name: 'Hivecode', always_online: true } },
|
|
30
|
+
oauth_config: { scopes: { bot: BOT_SCOPES } },
|
|
31
|
+
settings: {
|
|
32
|
+
event_subscriptions: { request_url: eventsUrl, bot_events: ['message.channels', 'message.groups'] },
|
|
33
|
+
org_deploy_enabled: false, socket_mode_enabled: false, token_rotation_enabled: false,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** One line from the person. Hidden (no echo) for secrets. Only used on a terminal. */
|
|
39
|
+
export function askLine(prompt, { hidden = false, input = process.stdin, output = process.stderr } = {}) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const rl = readline.createInterface({ input, output, terminal: true })
|
|
42
|
+
if (hidden) rl._writeToOutput = (s) => { if (s === prompt || /[\r\n]/.test(s)) output.write(s) }
|
|
43
|
+
rl.question(prompt, (a) => { rl.close(); resolve(String(a).trim()) })
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const stateFile = (home) => path.join(home, '.hivecode', 'slack-connect.json')
|
|
48
|
+
function readState(home) { try { return JSON.parse(fs.readFileSync(stateFile(home), 'utf8')) || {} } catch { return {} } }
|
|
49
|
+
function writeState(home, s) {
|
|
50
|
+
fs.mkdirSync(path.dirname(stateFile(home)), { recursive: true })
|
|
51
|
+
fs.writeFileSync(stateFile(home), JSON.stringify(s, null, 2), { mode: 0o600 })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function callRelay(base, route, body, fetchImpl) {
|
|
55
|
+
try {
|
|
56
|
+
const r = await fetchImpl(`${base}${route}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), signal: AbortSignal.timeout(45000) })
|
|
57
|
+
const j = await r.json().catch(() => null)
|
|
58
|
+
if (j && typeof j === 'object') return j.ok ? j : { ok: false, code: j.code || `http_${r.status}`, why: j.why || `the relay answered ${r.status}` }
|
|
59
|
+
return { ok: false, code: `http_${r.status}`, why: `the relay answered ${r.status} without a reason` }
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return { ok: false, code: 'Q666', why: `could not reach ${base} (${e && e.message ? e.message : e})` }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function target(link, logErr) {
|
|
66
|
+
const { relay, room } = parseLink(link || '')
|
|
67
|
+
if (!relay || !room) { logErr(`Q665 "${link || ''}" is not a room link (expected wss://relay|room-…).`); return null }
|
|
68
|
+
return { base: String(relay).replace(/^ws/i, 'http').replace(/\/+$/, ''), room }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Channels the bot is in: id, name, privacy. Follows Slack's cursor, at most 5 pages. */
|
|
72
|
+
async function botChannels(token, fetchImpl, api) {
|
|
73
|
+
const out = []
|
|
74
|
+
let cursor = ''
|
|
75
|
+
for (let i = 0; i < 5; i++) {
|
|
76
|
+
const r = await slackPost('users.conversations', { types: 'public_channel,private_channel', exclude_archived: true, limit: 200, cursor: cursor || undefined }, token, fetchImpl, api)
|
|
77
|
+
for (const c of r.channels || []) out.push({ id: c.id, name: c.name || c.id, isPrivate: !!(c.is_private || c.is_group) })
|
|
78
|
+
cursor = (r.response_metadata && r.response_metadata.next_cursor) || ''
|
|
79
|
+
if (!cursor) break
|
|
80
|
+
}
|
|
81
|
+
return out
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function runSlackConnect({ link, channel = '', env = process.env, log = console.log, logErr = console.error, fetchImpl = fetch, ask = askLine, interactive = Boolean(process.stdin.isTTY), home = os.homedir(), api = env.HIVE_SLACK_API || 'https://slack.com/api' } = {}) {
|
|
85
|
+
const t = target(link, logErr)
|
|
86
|
+
if (!t) return 2
|
|
87
|
+
|
|
88
|
+
const prep = await callRelay(t.base, '/slack/prepare', { room: t.room }, fetchImpl)
|
|
89
|
+
if (!prep.ok || !prep.id || !prep.eventsUrl) { logErr(`${prep.code || 'Q666'} the relay did not prepare a Slack connection: ${prep.why || 'no id came back'}`); return 1 }
|
|
90
|
+
|
|
91
|
+
log('1. In Slack, open https://api.slack.com/apps -> Create New App -> From a manifest, and paste this:\n')
|
|
92
|
+
log(JSON.stringify(slackManifest(prep.eventsUrl), null, 2))
|
|
93
|
+
log('\n2. Install the app to your workspace. Copy the Bot User OAuth Token (xoxb-…) from "OAuth & Permissions"\n and the Signing Secret from "Basic Information". Both stay between you and the relay.\n')
|
|
94
|
+
|
|
95
|
+
const need = async (name, prompt) => {
|
|
96
|
+
const v = String(env[name] || '').trim()
|
|
97
|
+
if (v) return v
|
|
98
|
+
return interactive ? ask(prompt, { hidden: true }) : ''
|
|
99
|
+
}
|
|
100
|
+
const botToken = await need('SLACK_BOT_TOKEN', 'Bot token (xoxb-…): ')
|
|
101
|
+
const signingSecret = botToken ? await need('SLACK_SIGNING_SECRET', 'Signing secret: ') : ''
|
|
102
|
+
if (!botToken || !signingSecret) { logErr(`Q667 ${!botToken ? 'SLACK_BOT_TOKEN' : 'SLACK_SIGNING_SECRET'} is missing. Set it in the environment (it is never taken as a flag) and run this again.`); return 1 }
|
|
103
|
+
|
|
104
|
+
let chosen = String(channel || '').trim()
|
|
105
|
+
if (!chosen) {
|
|
106
|
+
let list
|
|
107
|
+
try { list = await botChannels(botToken, fetchImpl, api) } catch (e) {
|
|
108
|
+
const why = (e && (e.slackError || e.message)) || 'unknown'
|
|
109
|
+
logErr(`Q662 Slack refused the bot token (${why}). Check it is the Bot User OAuth Token and the app is installed.`)
|
|
110
|
+
return 1
|
|
111
|
+
}
|
|
112
|
+
if (!list.length) { logErr('Q668 the bot is in no channel. In Slack, open the channel you want and run: /invite @Hivecode'); return 1 }
|
|
113
|
+
if (list.length === 1 && !interactive) chosen = list[0].id
|
|
114
|
+
else if (!interactive) {
|
|
115
|
+
logErr(`Q668 the bot is in ${list.length} channels; say which with --channel <id>:\n${list.map((c) => ` ${c.id} ${c.isPrivate ? '🔒' : '#'}${c.name}`).join('\n')}`)
|
|
116
|
+
return 1
|
|
117
|
+
} else {
|
|
118
|
+
log('Channels the bot is in:')
|
|
119
|
+
list.forEach((c, i) => log(` ${i + 1}. ${c.isPrivate ? '🔒' : '#'}${c.name} (${c.id})`))
|
|
120
|
+
const n = Number(await ask(`Pick one [1-${list.length}]: `)) || 1
|
|
121
|
+
if (n < 1 || n > list.length) { logErr(`Q668 ${n} is not one of the ${list.length} channels.`); return 1 }
|
|
122
|
+
chosen = list[n - 1].id
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const done = await callRelay(t.base, '/slack/connect', { id: prep.id, botToken, signingSecret, channel: chosen }, fetchImpl)
|
|
127
|
+
if (!done.ok) { logErr(`${done.code} ${done.why}`); return 1 }
|
|
128
|
+
const s = readState(home)
|
|
129
|
+
s[t.room] = { id: prep.id, relay: t.base }
|
|
130
|
+
try { writeState(home, s) } catch { /* disconnecting then needs --id; connecting worked */ }
|
|
131
|
+
log(`✓ Slack is connected to this room${done.channel ? ` (#${done.channel})` : ''}. Post a line in the channel: it shows up in the room.\n To disconnect: npx -y hivecode-mcp@latest slack disconnect "<room link>"`)
|
|
132
|
+
return 0
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function runSlackDisconnect({ link, id = '', env = process.env, log = console.log, logErr = console.error, fetchImpl = fetch, ask = askLine, interactive = Boolean(process.stdin.isTTY), home = os.homedir() } = {}) {
|
|
136
|
+
const t = target(link, logErr)
|
|
137
|
+
if (!t) return 2
|
|
138
|
+
const state = readState(home)
|
|
139
|
+
// The id this computer remembers, or --id; failing both, the relay finds the connection by room (the
|
|
140
|
+
// signing secret is still what proves it is the person who connected).
|
|
141
|
+
const connection = String(id || (state[t.room] && state[t.room].id) || '')
|
|
142
|
+
let signingSecret = String(env.SLACK_SIGNING_SECRET || '').trim()
|
|
143
|
+
if (!signingSecret && interactive) signingSecret = await ask('Signing secret: ', { hidden: true })
|
|
144
|
+
if (!signingSecret) { logErr('Q667 SLACK_SIGNING_SECRET is missing. Only the person who connected can disconnect: it is the same secret.'); return 1 }
|
|
145
|
+
const r = await callRelay(t.base, '/slack/disconnect', connection ? { id: connection, signingSecret } : { room: t.room, signingSecret }, fetchImpl)
|
|
146
|
+
if (!r.ok) { logErr(`${r.code} ${r.why}`); return 1 }
|
|
147
|
+
if (state[t.room]) { delete state[t.room]; try { writeState(home, state) } catch { } }
|
|
148
|
+
log('✓ Slack is disconnected from this room. The relay no longer holds the token.')
|
|
149
|
+
return 0
|
|
150
|
+
}
|
package/hive-slack.js
CHANGED
|
@@ -61,6 +61,8 @@ export function makeChatForwarder({ chat, forwarded, key, at, threads, onChat })
|
|
|
61
61
|
const MAX_SEEN_EVENTS = 500
|
|
62
62
|
|
|
63
63
|
export const USAGE = `usage:
|
|
64
|
+
npx -y hivecode-mcp@latest slack connect "<relay|room>" [--channel <id>] Slack <-> room through the relay, nothing to keep running
|
|
65
|
+
npx -y hivecode-mcp@latest slack disconnect "<relay|room>" [--id <connection id>]
|
|
64
66
|
SLACK_BOT_TOKEN=xoxb-… npx -y hivecode-mcp@latest slack start "<relay|room>" --channel <id> [--name SlackBridge] [--allow <U123,U456>] [--inbound auto|poll|socket] [--dry-run] [--start-retry <ms>]
|
|
65
67
|
SLACK_BOT_TOKEN=xoxb-… [SLACK_APP_TOKEN=xapp-…] npx -y hivecode-mcp@latest slack check --channel <id> [--agent] [--inbound auto|poll|socket] [--timeout <ms>] [--start-retry <ms>]
|
|
66
68
|
SLACK_BOT_TOKEN=xoxb-… SLACK_APP_TOKEN=xapp-… npx -y hivecode-mcp@latest slack agent "<relay|room>" --channel <id> [--name <name>] [--allow <U123,U456>]
|
|
@@ -2205,13 +2207,14 @@ export async function runSlackCheck({
|
|
|
2205
2207
|
}
|
|
2206
2208
|
}
|
|
2207
2209
|
|
|
2208
|
-
export async function main(argv = process.argv.slice(2), { log = console.log, logErr = console.error, fetchImpl = fetch, env = process.env, WebSocketImpl = WebSocket } = {}) {
|
|
2210
|
+
export async function main(argv = process.argv.slice(2), { log = console.log, logErr = console.error, fetchImpl = fetch, env = process.env, WebSocketImpl = WebSocket , connect = {}} = {}) {
|
|
2209
2211
|
let mode = 'start'
|
|
2210
2212
|
let CHANNEL = env.SLACK_CHANNEL || ''
|
|
2211
2213
|
let NAME = 'SlackBridge'
|
|
2212
2214
|
let ALLOW = env.SLACK_ALLOW || ''
|
|
2213
2215
|
let allowSource = env.SLACK_ALLOW ? 'SLACK_ALLOW' : '--allow'
|
|
2214
2216
|
let LINK = ''
|
|
2217
|
+
let CONN_ID = ''
|
|
2215
2218
|
let dryRun = false
|
|
2216
2219
|
let agent = false
|
|
2217
2220
|
let timeoutMs = Number(env.HIVE_SLACK_CHECK_TIMEOUT_MS) || 120000
|
|
@@ -2222,7 +2225,8 @@ export async function main(argv = process.argv.slice(2), { log = console.log, lo
|
|
|
2222
2225
|
let inbound = String(env.HIVE_SLACK_INBOUND || 'auto').toLowerCase()
|
|
2223
2226
|
for (let i = 0; i < argv.length; i++) {
|
|
2224
2227
|
const a = argv[i]
|
|
2225
|
-
if ((a === 'start' || a === 'check') && !LINK && i === 0) { mode = a; continue }
|
|
2228
|
+
if ((a === 'start' || a === 'check' || a === 'connect' || a === 'disconnect') && !LINK && i === 0) { mode = a; continue }
|
|
2229
|
+
if (a === '--id') { CONN_ID = argv[++i] || ''; continue }
|
|
2226
2230
|
if (a === '--inbound') { inbound = String(argv[++i] || '').toLowerCase(); continue }
|
|
2227
2231
|
if (a === '--help' || a === '-h') { log(USAGE.trim()); return 0 }
|
|
2228
2232
|
if (a === '--dry-run') { dryRun = true; continue }
|
|
@@ -2243,6 +2247,13 @@ export async function main(argv = process.argv.slice(2), { log = console.log, lo
|
|
|
2243
2247
|
const TOKEN = env.SLACK_BOT_TOKEN || ''
|
|
2244
2248
|
const APP_TOKEN = env.SLACK_APP_TOKEN || ''
|
|
2245
2249
|
const pollMs = Math.max(50, Number(env.HIVE_SLACK_POLL_MS) || INBOUND_DEFAULT_POLL_MS)
|
|
2250
|
+
if (mode === 'connect' || mode === 'disconnect') {
|
|
2251
|
+
const C = await import('./hive-slack-connect.js') // lazy: it imports slackPost from here
|
|
2252
|
+
if (!LINK) { logErr(C.CONNECT_USAGE.trim()); return 2 }
|
|
2253
|
+
return mode === 'connect'
|
|
2254
|
+
? C.runSlackConnect({ link: LINK, channel: CHANNEL, env, log, logErr, fetchImpl, ...connect })
|
|
2255
|
+
: C.runSlackDisconnect({ link: LINK, id: CONN_ID, env, log, logErr, fetchImpl, ...connect })
|
|
2256
|
+
}
|
|
2246
2257
|
if (mode === 'check') {
|
|
2247
2258
|
return await runSlackCheck({
|
|
2248
2259
|
token: TOKEN,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hivecode-mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.13",
|
|
4
4
|
"description": "MCP server that lets any AI coding agent join a Hivecode room — humans and multiple AI agents edit one live codebase with folder-scoped permissions and approval gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"hive-notify.js",
|
|
88
88
|
"hive-webhook.js",
|
|
89
89
|
"hive-slack.js",
|
|
90
|
+
"hive-slack-connect.js",
|
|
90
91
|
"hive-slack-agent.js",
|
|
91
92
|
"hive-mcp-http.js",
|
|
92
93
|
"slack-app-manifest.json",
|