@pinet/slack-bridge 0.2.2 → 0.2.6

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.
Files changed (93) hide show
  1. package/README.md +316 -451
  2. package/dist/activity-log.d.ts +10 -3
  3. package/dist/agent-event-runtime.d.ts +0 -2
  4. package/dist/agent-event-runtime.js +0 -1
  5. package/dist/agent-prompt-guidance.d.ts +1 -6
  6. package/dist/agent-prompt-guidance.js +1 -6
  7. package/dist/broker/adapters/slack.d.ts +4 -1
  8. package/dist/broker/adapters/slack.js +16 -9
  9. package/dist/broker/client.d.ts +21 -4
  10. package/dist/broker/client.js +65 -25
  11. package/dist/broker/hibernation-activation-authority.d.ts +22 -0
  12. package/dist/broker/hibernation-activation-authority.js +51 -0
  13. package/dist/broker/hibernation-activation.d.ts +75 -0
  14. package/dist/broker/hibernation-activation.js +95 -0
  15. package/dist/broker/hibernation-command-router.d.ts +54 -0
  16. package/dist/broker/hibernation-command-router.js +124 -0
  17. package/dist/broker/hibernation-runtime-adapters.d.ts +79 -0
  18. package/dist/broker/hibernation-runtime-adapters.js +473 -0
  19. package/dist/broker/hibernation-runtime-helpers.d.ts +146 -0
  20. package/dist/broker/hibernation-runtime-helpers.js +243 -0
  21. package/dist/broker/index.d.ts +16 -1
  22. package/dist/broker/index.js +68 -10
  23. package/dist/broker/lock-conflict.d.ts +109 -0
  24. package/dist/broker/lock-conflict.js +409 -0
  25. package/dist/broker/socket-server.d.ts +26 -0
  26. package/dist/broker/socket-server.js +287 -4
  27. package/dist/broker-delivery.d.ts +1 -0
  28. package/dist/broker-delivery.js +10 -1
  29. package/dist/broker-runtime.d.ts +14 -2
  30. package/dist/broker-runtime.js +114 -3
  31. package/dist/canvases.d.ts +12 -2
  32. package/dist/canvases.js +1 -3
  33. package/dist/compaction-gate.d.ts +7 -0
  34. package/dist/compaction-gate.js +35 -0
  35. package/dist/deploy-manifest.d.ts +8 -2
  36. package/dist/deploy-manifest.js +9 -7
  37. package/dist/follower-runtime.d.ts +5 -2
  38. package/dist/follower-runtime.js +39 -5
  39. package/dist/git-metadata.d.ts +5 -9
  40. package/dist/git-metadata.js +23 -42
  41. package/dist/guardrails.js +5 -0
  42. package/dist/helpers.d.ts +77 -27
  43. package/dist/helpers.js +150 -39
  44. package/dist/hibernation-config.d.ts +13 -0
  45. package/dist/hibernation-config.js +14 -0
  46. package/dist/home-tab.d.ts +13 -2
  47. package/dist/imessage-tools.d.ts +4 -1
  48. package/dist/inbox-drain-runtime.d.ts +1 -0
  49. package/dist/inbox-drain-runtime.js +5 -1
  50. package/dist/index.js +341 -108
  51. package/dist/pinet-commands.d.ts +14 -1
  52. package/dist/pinet-commands.js +93 -22
  53. package/dist/pinet-mesh-ops.d.ts +6 -4
  54. package/dist/pinet-mesh-ops.js +9 -1
  55. package/dist/pinet-runtime-composition.d.ts +2 -1
  56. package/dist/pinet-runtime-composition.js +14 -3
  57. package/dist/pinet-session-formatting.d.ts +26 -2
  58. package/dist/pinet-session-formatting.js +2 -0
  59. package/dist/pinet-tools.d.ts +14 -0
  60. package/dist/pinet-tools.js +355 -50
  61. package/dist/prompts/broker/default.md +3 -3
  62. package/dist/prompts/broker/tmux.md +3 -3
  63. package/dist/runtime-agent-context.d.ts +3 -3
  64. package/dist/runtime-agent-context.js +12 -6
  65. package/dist/runtime-mode.js +4 -0
  66. package/dist/session-ui-runtime.js +3 -1
  67. package/dist/single-player-runtime.d.ts +1 -1
  68. package/dist/single-player-runtime.js +21 -5
  69. package/dist/slack-access.d.ts +10 -2
  70. package/dist/slack-access.js +64 -22
  71. package/dist/slack-block-kit.d.ts +44 -2
  72. package/dist/slack-block-kit.js +5 -5
  73. package/dist/slack-file-access.d.ts +12 -1
  74. package/dist/slack-file-access.js +4 -4
  75. package/dist/slack-message-context.d.ts +9 -2
  76. package/dist/slack-modals.d.ts +4 -1
  77. package/dist/slack-modals.js +5 -5
  78. package/dist/slack-pinet-runtime-adapter.d.ts +1 -0
  79. package/dist/slack-pinet-runtime-adapter.js +10 -0
  80. package/dist/slack-presence.d.ts +5 -4
  81. package/dist/slack-request-runtime.d.ts +3 -2
  82. package/dist/slack-scope-diagnostics.d.ts +27 -2
  83. package/dist/slack-socket-dedup.d.ts +8 -5
  84. package/dist/slack-tools.d.ts +10 -0
  85. package/dist/slack-tools.js +51 -64
  86. package/dist/slack-upload.d.ts +17 -1
  87. package/dist/subtree-broker-runtime.d.ts +83 -2
  88. package/dist/subtree-broker-runtime.js +774 -109
  89. package/dist/tool-output-limits.d.ts +11 -0
  90. package/dist/tool-output-limits.js +20 -0
  91. package/dist/tool-registration-runtime.d.ts +3 -0
  92. package/dist/tool-registration-runtime.js +27 -0
  93. package/package.json +8 -8
package/README.md CHANGED
@@ -1,157 +1,185 @@
1
1
  # slack-bridge (Pinet)
2
2
 
3
- Slack assistant integration for [pi](https://github.com/badlogic/pi-mono) multi-agent broker, thread routing, and inbox tools powered by Socket Mode.
3
+ Connect pi coding agents to Slack. Pinet provides multi-agent coordination, thread routing, and inbox tools through Socket Mode.
4
4
 
5
- ## Install
5
+ ## Install Pinet
6
6
 
7
- Install the latest Pinet Slack bridge pi package:
7
+ Install the latest version:
8
8
 
9
9
  ```bash
10
10
  pi install npm:@pinet/slack-bridge
11
11
  ```
12
12
 
13
- Or pin an exact published version for reproducible installs:
13
+ Pin a specific version:
14
14
 
15
15
  ```bash
16
- pi install npm:@pinet/slack-bridge@0.1.0
16
+ pi install npm:@pinet/slack-bridge@0.2.2
17
17
  ```
18
18
 
19
- For package managers or local inspection, the npm package is also installable directly:
19
+ For direct npm installation:
20
20
 
21
21
  ```bash
22
22
  npm install @pinet/slack-bridge
23
23
  ```
24
24
 
25
- ## Package metadata and publishing
25
+ ## What you need
26
26
 
27
- This package declares pi package/gallery metadata in [`package.json`](./package.json):
27
+ - a Slack workspace where you can install apps
28
+ - Node.js 22 or later
29
+ - pi installed on your system
28
30
 
29
- - `keywords` includes `pi-package` for gallery discovery.
30
- - `pi.extensions` points at the built extension entrypoint, `./dist/index.js`.
31
- - `pi.skills` points at the bundled skill directory, `./skills`.
32
- - No `pi.image` or `pi.video` preview is declared yet because this package does
33
- not currently include a reviewed gallery image/video asset.
31
+ ## Set up your Slack app
34
32
 
35
- The published tarball is expected to include the package metadata, README,
36
- Slack app manifest, built `dist/` files, bundled `skills/`, and LICENSE. Verify
37
- that locally with:
33
+ ### Create the app
38
34
 
39
- ```bash
40
- cd slack-bridge
41
- npm pack --dry-run
42
- ```
43
-
44
- This package is included in the full npm publish set tracked in
45
- [`../plans/npm-publish.md`](../plans/npm-publish.md). Use the GitHub Actions
46
- workflow's default dry-run/readiness path for validation; do not publish, tag, or
47
- bump versions without explicit maintainer release approval.
48
-
49
- ## Prerequisites
50
-
51
- - A Slack workspace where you have permission to install apps
52
- - Node.js 22+ (uses native `fetch` and `WebSocket`)
53
- - [pi](https://github.com/badlogic/pi-mono) installed
54
-
55
- ## Slack App Setup
35
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps)
36
+ 2. Select 'Create New App'
37
+ 3. Choose 'From a manifest'
38
+ 4. Select your workspace
39
+ 5. Paste the contents of [`manifest.yaml`](./manifest.yaml)
40
+ 6. If you want a different Slack command, change `features.slash_commands[0].command` before creating and set `slackCommandName` or `slackCommandNames` in `settings.json`
41
+ 7. Select 'Create'
56
42
 
57
- ### 1. Create the app
43
+ The manifest configures Socket Mode, the assistant view, bot scopes, event subscriptions, and slash commands automatically.
58
44
 
59
- 1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App**
60
- 2. Choose **From a manifest**
61
- 3. Select your workspace
62
- 4. Paste the contents of [`manifest.yaml`](./manifest.yaml) from this directory
63
- 5. If the Slack app is not named Pinet, change `features.slash_commands[0].command` before creating the app (for example, Oathgate uses `/oathgate` instead of the packaged `/pinet` default)
64
- 6. Click **Create**
45
+ ### Get your tokens
65
46
 
66
- The manifest configures Socket Mode, the assistant view, all required bot scopes, event subscriptions, and the packaged Pinet slash-command default automatically.
47
+ Generate two tokens:
67
48
 
68
- ### 2. Generate tokens
49
+ | Token | Where to find it | Format |
50
+ | --------------- | ------------------------------------------------------------------------------- | ------------ |
51
+ | App-Level Token | Basic Information → App-Level Tokens → Generate (add `connections:write` scope) | `xapp-1-...` |
52
+ | Bot Token | OAuth & Permissions → Install to Workspace → Bot User OAuth Token | `xoxb-...` |
69
53
 
70
- You need two tokens:
54
+ ### Required bot scopes
71
55
 
72
- | Token | Where to find it | Looks like |
73
- | ------------------- | -------------------------------------------------------------------------------- | ------------ |
74
- | **App-Level Token** | Basic Information → App-Level Tokens → Generate (with `connections:write` scope) | `xapp-1-...` |
75
- | **Bot Token** | OAuth & Permissions → Install to Workspace → Bot User OAuth Token | `xoxb-...` |
76
-
77
- ### 3. Required bot scopes
78
-
79
- These are included in the manifest, but for reference:
56
+ The manifest includes these scopes:
80
57
 
81
58
  ```
82
59
  app_mentions:read assistant:write bookmarks:read
83
60
  bookmarks:write canvases:read canvases:write
84
61
  channels:history channels:read chat:write
85
- commands files:read files:write groups:history
86
- groups:read im:history im:read
87
- im:write pins:read pins:write
88
- reactions:read reactions:write users:read
62
+ commands files:read files:write
63
+ groups:history groups:read im:history
64
+ im:read im:write pins:read
65
+ pins:write reactions:read reactions:write
66
+ users:read
89
67
  ```
90
68
 
91
- `commands` is required for the Slack slash-command surface (`/<app> agents list [all]`). `files:read` is required because Slack exposes canvas comment pagination through `files.info`, even when the target is first validated via canvas-specific APIs.
69
+ The `commands` scope enables slash commands. The `files:read` scope is needed because Slack uses `files.info` for canvas comment pagination.
92
70
 
93
- Slack thread shimmer/status updates use `assistant.threads.setStatus`; Slack's 2026 scope update allows this method with the existing `chat:write` bot scope, so no new `assistant:write` scope is needed for status-only support.
71
+ ## Configure Pinet
94
72
 
95
- ## Configuration
96
-
97
- Add your tokens to `~/.pi/agent/settings.json`:
73
+ Add tokens, runtime mode, and access rules to `~/.pi/agent/settings.json`:
98
74
 
99
75
  ```json
100
76
  {
101
77
  "slack-bridge": {
102
78
  "botToken": "xoxb-your-bot-token",
103
- "appToken": "xapp-your-app-token"
79
+ "appToken": "xapp-your-app-token",
80
+ "runtimeMode": "single",
81
+ "allowedUsers": ["U_YOUR_USER_ID"]
104
82
  }
105
83
  }
106
84
  ```
107
85
 
108
- That's it for a minimal setup. Start pi and Pinet appears in Slack's sidebar.
86
+ Pinet stays off unless you set `runtimeMode`, `autoConnect`, or `autoFollow`. Start pi after you configure access.
109
87
 
110
- ### Environment variables (alternative)
88
+ ### Use environment variables instead
111
89
 
112
90
  ```bash
113
91
  export SLACK_BOT_TOKEN="xoxb-..."
114
92
  export SLACK_APP_TOKEN="xapp-..."
115
93
  ```
116
94
 
117
- Settings in `settings.json` take priority over env vars.
95
+ Settings in `settings.json` override environment variables.
96
+
97
+ ## Control who can use Pinet
98
+
99
+ Slack access is default-deny. Configure one of these:
100
+
101
+ - `allowedUsers`: list specific Slack user IDs
102
+ - `allowAllWorkspaceUsers: true`: allow everyone in the workspace
103
+
104
+ Example with specific users:
105
+
106
+ ```json
107
+ {
108
+ "slack-bridge": {
109
+ "botToken": "xoxb-...",
110
+ "appToken": "xapp-...",
111
+ "allowedUsers": ["U_USER_ID_1", "U_USER_ID_2"]
112
+ }
113
+ }
114
+ ```
115
+
116
+ Find user IDs by selecting a user's profile in Slack and choosing 'Copy member ID'.
117
+
118
+ ## Optional settings
118
119
 
119
- ### Optional Pinet mesh auth
120
+ ### Mesh authentication
120
121
 
121
- Shared-secret mesh auth is **optional**. You can configure it with either settings keys or environment variables:
122
+ Shared-secret authentication is optional. Configure it with settings or environment variables:
123
+
124
+ Settings:
122
125
 
123
126
  ```json
124
127
  {
125
128
  "slack-bridge": {
126
- "meshSecret": "shared-secret"
129
+ "meshSecret": "your-shared-secret"
127
130
  }
128
131
  }
129
132
  ```
130
133
 
134
+ Or use a file:
135
+
131
136
  ```json
132
137
  {
133
138
  "slack-bridge": {
134
- "meshSecretPath": "/Users/alice/.config/pi/pinet.secret"
139
+ "meshSecretPath": "/path/to/secret.txt"
135
140
  }
136
141
  }
137
142
  ```
138
143
 
144
+ Environment variables:
145
+
139
146
  ```bash
140
- export PINET_MESH_SECRET="shared-secret"
147
+ export PINET_MESH_SECRET="your-shared-secret"
141
148
  # or
142
- export PINET_MESH_SECRET_PATH="$HOME/.config/pi/pinet.secret"
149
+ export PINET_MESH_SECRET_PATH="/path/to/secret.txt"
143
150
  ```
144
151
 
145
- Behavior and precedence:
152
+ How it works:
146
153
 
147
- - `slack-bridge.meshSecret` and `slack-bridge.meshSecretPath` override the environment fallbacks.
148
- - Inline secrets win over secret paths. If `meshSecret` or `PINET_MESH_SECRET` is set, the corresponding `*Path` value is ignored.
149
- - If all four values are unset, broker/follower mesh auth is disabled.
150
- - A broker started with `meshSecretPath` creates the secret file if it does not exist yet.
151
- - A follower started with `meshSecretPath` does **not** create the file. If the configured file is missing, follow fails with a clear error telling you to point at an existing file, provide `meshSecret` directly, or leave both unset to disable shared-secret auth.
152
- - A follower configured for mesh auth will fail closed against an older/no-auth broker with a clear compatibility error. It will **not** silently retry as an unauthenticated follower.
154
+ - settings override environment variables
155
+ - inline secrets override file paths
156
+ - if nothing is set, mesh auth is disabled
157
+ - brokers create the secret file if it does not exist
158
+ - followers need an existing file or will show an error
153
159
 
154
- ### Full settings reference
160
+ ### Require mentions in channels
161
+
162
+ Make Pinet respond only when mentioned in specific channels:
163
+
164
+ ```json
165
+ {
166
+ "slack-bridge": {
167
+ "ingressGuard": {
168
+ "requireMention": {
169
+ "channels": ["C_CHANNEL_ID"],
170
+ "mixedParticipantThreads": {
171
+ "enabled": true,
172
+ "trustedUsers": ["U_TRUSTED_USER"]
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ This is separate from `allowedUsers`. Authorization decides who can use Pinet. The guard decides when a mention is needed.
181
+
182
+ ### All configuration options
155
183
 
156
184
  ```json
157
185
  {
@@ -159,25 +187,32 @@ Behavior and precedence:
159
187
  "botToken": "xoxb-...",
160
188
  "appToken": "xapp-...",
161
189
  "runtimeMode": "single",
162
- "allowedUsers": ["U_EXAMPLE_MEMBER_ID"],
190
+ "allowedUsers": ["U_USER_ID"],
191
+ "allowAllWorkspaceUsers": false,
163
192
  "ingressGuard": {
164
193
  "requireMention": {
165
- "channels": ["C_EXTERNAL_CHANNEL_ID"],
194
+ "channels": ["C_CHANNEL_ID"],
166
195
  "mixedParticipantThreads": {
167
196
  "enabled": true,
168
- "trustedUsers": ["U_EXAMPLE_MEMBER_ID"]
197
+ "trustedUsers": ["U_USER_ID"]
169
198
  }
170
199
  }
171
200
  },
172
- "defaultChannel": "C_EXAMPLE_CHANNEL_ID",
201
+ "defaultChannel": "C_CHANNEL_ID",
173
202
  "logChannel": "#pinet-logs",
174
203
  "logLevel": "actions",
175
- "autoFollow": true,
204
+ "autoConnect": false,
205
+ "autoFollow": false,
176
206
  "ralphLoopIntervalMs": 300000,
177
207
  "ralphSnoozeAfterEmptyCycles": 0,
178
208
  "ralphSnoozeDurationMs": 1800000,
179
- "meshSecretPath": "/Users/alice/.config/pi/pinet.secret",
180
- "suggestedPrompts": [{ "title": "Status", "message": "What are you working on?" }],
209
+ "meshSecretPath": "/path/to/secret",
210
+ "suggestedPrompts": [
211
+ {
212
+ "title": "Status",
213
+ "message": "What are you working on?"
214
+ }
215
+ ],
181
216
  "security": {
182
217
  "readOnly": false,
183
218
  "requireConfirmation": ["slack:create_channel"],
@@ -187,450 +222,280 @@ Behavior and precedence:
187
222
  }
188
223
  ```
189
224
 
190
- Slack access is now **default-deny** unless you configure one of these explicitly:
191
-
192
- - `allowedUsers` / `SLACK_ALLOWED_USERS` allow only specific Slack user IDs
193
- - `allowAllWorkspaceUsers: true` / `SLACK_ALLOW_ALL_WORKSPACE_USERS=true` explicit workspace-wide opt-in
194
-
195
- Optional explicit invocation guard: `ingressGuard.requireMention` is off by default. Set `channels` to Slack channel IDs where otherwise-actionable messages must mention the bot (`@pinet` / `<@bot>`), and set `mixedParticipantThreads.enabled: true` with `trustedUsers` to require a mention in Pinet-owned Slack threads once anyone outside `trustedUsers` plus the bot has participated. This guard is orthogonal to `allowedUsers`: sender authorization still decides who may invoke Pinet; the guard only decides when an explicit mention is required.
196
-
197
- | Key | Required | Description |
198
- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------- |
199
- | `botToken` | **yes** | Bot User OAuth Token (`xoxb-...`) |
200
- | `appToken` | **yes** | App-Level Token for Socket Mode (`xapp-...`) |
201
- | `allowedUsers` | no | Slack user IDs that can interact; when unset, access is denied unless `allowAllWorkspaceUsers` is true |
202
- | `allowAllWorkspaceUsers` | no | Explicit opt-in for workspace-wide Slack access when you do not want a user allowlist |
203
- | `ingressGuard.requireMention` | no | Optional Slack ingress guard requiring `@pinet` in configured channel IDs and/or mixed-participant Pinet-owned threads |
204
- | `defaultChannel` | no | Default channel for the `slack` dispatcher `post_channel` action |
205
- | `logChannel` | no | Channel for broker activity logs |
206
- | `logLevel` | no | `"errors"`, `"actions"` (default), or `"verbose"` |
207
- | `runtimeMode` | no | Explicit startup mode: `"off"`, `"single"`, `"broker"`, or `"follower"` |
208
- | `autoConnect` | no | Legacy compatibility alias for `runtimeMode: "single"` |
209
- | `autoFollow` | no | Legacy compatibility alias for follower startup when a broker socket exists |
210
- | `ralphLoopIntervalMs` | no | Broker RALPH maintenance cadence in milliseconds; defaults to `300000` (5 minutes), valid range `1000`-`2147483647` |
211
- | `ralphSnoozeAfterEmptyCycles` | no | Broker RALPH auto-snooze trigger after N empty cycles; defaults to `0` (disabled), valid range `0`-`100` |
212
- | `ralphSnoozeDurationMs` | no | Broker RALPH auto-snooze duration in milliseconds; defaults to `1800000` (30 minutes), valid range `60000`-`86400000` |
213
- | `skinTheme` | no | Pinet presentation skin selected at broker startup/reload (`default`, `foundation`, `cosmere`, or free-form) |
214
- | `slackCommandName` | no | Slack web app slash command name for `agents list`; defaults to `/pinet`, or `/oathgate` for Oathgate/Cosmere skins |
215
- | `slackCommandNames` | no | Optional list of accepted/deployed Slack slash command aliases when one app needs multiple command names |
216
- | `meshSecret` | no | Optional inline Pinet shared secret; overrides `meshSecretPath` and env fallbacks |
217
- | `meshSecretPath` | no | Optional path to a shared-secret file; broker creates it if missing, followers require an existing file |
218
- | `suggestedPrompts` | no | Prompts shown when a user opens a new conversation |
219
- | `security.readOnly` | no | Runtime-block write-capable tools for Slack-triggered turns, including core tools like `bash`, `edit`, and `write` |
220
- | `security.requireConfirmation` | no | Runtime-require Slack approval before matching tools execute; core tools need a specific Slack thread context |
221
- | `security.blockedTools` | no | Runtime-block matching tools for Slack-triggered turns, including core tools |
222
-
223
- ## Scope carrier model (compatibility-first)
224
-
225
- Slack/Pinet now threads a first-class runtime `scope` carrier through shared message contracts and runtime metadata.
226
-
227
- For this first slice:
228
-
229
- - **workspace/install scope** is carried as compatibility-first metadata for Slack
230
- - **instance scope** is also carried as a first-class compatibility carrier
231
- - today’s single-workspace deployments use one default compatibility scope
232
- - a missing or empty Slack `teamId` stays **unknown** — the bridge does not invent a fake workspace ID
233
- - these carriers are metadata only in this slice; enforcement and multi-install behavior land later in `#547` / `#550`
234
-
235
- ## Usage
236
-
237
- Once configured, Pinet appears in Slack's sidebar. Users open it, type a message, and the pi agent responds.
225
+ | Setting | Description | Default |
226
+ | ------------------------ | ------------------------------------------------------ | -------------------- |
227
+ | `botToken` | Bot User OAuth Token (required) | none |
228
+ | `appToken` | App-Level Token for Socket Mode (required) | none |
229
+ | `runtimeMode` | How Pinet runs (`off`, `single`, `broker`, `follower`) | `off` |
230
+ | `allowedUsers` | Slack user IDs who can use Pinet | none |
231
+ | `allowAllWorkspaceUsers` | Allow all workspace members | `false` |
232
+ | `defaultChannel` | Where to post updates | none |
233
+ | `logChannel` | Where to post logs | none |
234
+ | `logLevel` | What to log (`errors`, `actions`, `verbose`) | `actions` |
235
+ | `autoConnect` | Start as a single instance when `runtimeMode` is unset | `false` |
236
+ | `autoFollow` | Start as follower if broker exists | `false` |
237
+ | `ralphLoopIntervalMs` | How often to check for stalls (milliseconds) | `300000` (5 minutes) |
238
+ | `meshSecret` | Shared secret for mesh auth | none |
239
+ | `meshSecretPath` | File containing shared secret | none |
238
240
 
239
- ```
240
- User opens Pinet in Slack sidebar
241
- └─► types a message
242
- └─► 👀 reaction appears (thinking)
243
- └─► message queued for pi agent
244
- └─► agent responds via slack_send
245
- └─► 👀 removed, reply appears in thread
246
- ```
241
+ ## Using Pinet
247
242
 
248
- Messages queue while the agent is busy. When the agent finishes, it automatically drains the inbox and responds.
249
-
250
- ### Reaction triggers
251
-
252
- Slack emoji reactions are ignored by default: they do not enqueue Pinet work, trigger reviews, steer agents, interrupt owners, or cause broker/worker replies. To opt in deliberately, configure `reactionCommands` for the exact emoji aliases that should become structured Pinet requests from the reacted-to Slack message. Even configured reactions are accepted only inside an already authorized Pinet thread (for example a thread with a current Pinet owner, or persisted Slack assistant-thread context). Reaction authorization is deny-by-default: it requires an explicit broker-backed authorization gate, and a thread the adapter has merely seen or cached never qualifies on its own. Reactions in ordinary, uninvoked Slack channel threads remain no-op — even from authorized users — and they do not enqueue work, persist thread state, claim ownership, or receive a Slack ACK. Messages and interactive events from users outside the allowlist also never mint known-thread state that could later admit reactions or replies. Pinet adds ✅ only when it accepts an opt-in reaction-triggered request. If it cannot process an accepted opted-in reaction, it adds ❌; check broker logs for the underlying Slack/API error. When Slack cannot return the reacted message text, Pinet can still route configured reactions when the message timestamp itself identifies an already authorized thread; otherwise it ignores the reaction safely.
253
-
254
- ### Available tools
255
-
256
- Slack-bridge uses progressive disclosure to keep the per-turn tool surface
257
- small:
258
-
259
- | Tool | Description |
260
- | ------------- | --------------------------------------------------------------------------- |
261
- | `slack_inbox` | Hot-path inbox drain for pending incoming Slack messages |
262
- | `slack_send` | Hot-path reply tool for Slack assistant threads |
263
- | `slack` | Dispatcher for all non-hot Slack actions; call `action: "help"` for schemas |
264
-
265
- Cold Slack actions live behind the `slack` dispatcher:
266
-
267
- | Dispatcher action | Description |
268
- | ---------------------- | --------------------------------------------------------------------------------- |
269
- | `react` | Add an emoji reaction to a message |
270
- | `read` | Read messages from a thread |
271
- | `upload` | Upload files, snippets, or diffs into Slack |
272
- | `file` | Download Slack-hosted files to a controlled local temp cache by file ID |
273
- | `schedule` | Schedule a message for later delivery |
274
- | `post_channel` | Post to a channel (by name or ID) |
275
- | `delete` | Delete a bot-posted message or an entire thread |
276
- | `read_channel` | Read channel history or a thread in a channel |
277
- | `create_channel` | Create a new Slack channel |
278
- | `project_create` | Create a project channel + RFC canvas + bot invite in one call |
279
- | `pin` | Pin or unpin a message |
280
- | `bookmark` | Add, list, or remove channel bookmarks |
281
- | `export` | Export a thread as markdown, plain text, or JSON |
282
- | `presence` | Check if users are active, away, or in DND |
283
- | `canvas_comments_read` | Read comments attached to a verified canvas by canvas ID or channel canvas lookup |
284
- | `canvas_create` | Create a standalone or channel canvas |
285
- | `canvas_update` | Append, prepend, or replace canvas content |
286
- | `modal_open` | Open a modal from a trigger interaction |
287
- | `modal_push` | Push a new step onto a modal stack |
288
- | `modal_update` | Update an existing open modal |
289
- | `confirm_action` | Request user confirmation before a dangerous action |
290
-
291
- Use `slack` with `action: "help"` for the action catalogue, or
292
- `action: "help", args: { "topic": "canvas_update" }` for a specific JSON
293
- schema and example invocations. Dispatcher responses use a consistent
294
- `{ "status", "data", "errors", "warnings" }` envelope. Guardrails match
295
- cold Slack actions as `slack:<action>` (for example `slack:upload` or
296
- `slack:canvas_update`); legacy `slack_<action>` patterns are accepted during
297
- migration.
298
-
299
- #### Tool and workflow usage notes
300
-
301
- - **Reply where the work arrived.** Use `slack_send` for assistant-thread
302
- replies. If a task was delivered in a Slack thread, acknowledge briefly,
303
- do the work, report blockers immediately, and finish with the outcome. If
304
- you know only a channel/thread pair, use dispatcher action `post_channel`
305
- with `channel` and optional `thread_ts` instead.
306
- - **Channel posting is explicit.** `post_channel` posts to a named channel or
307
- channel ID. When `channel` is omitted, it first resolves a provided
308
- `thread_ts` to a tracked thread channel, then falls back to `defaultChannel`
309
- from settings. `slack_send` is intentionally narrower and resolves the
310
- current tracked assistant thread/DM context.
311
- - **Rich messages use Block Kit JSON.** Pass `blocks` directly to
312
- `slack_send` or `post_channel`; keep `text` as the notification/fallback.
313
- Block Kit builder tools are not registered by this package. Load the bundled
314
- `slack-bridge` skill for copyable status-report, button, code, and diff
315
- templates. The package also bundles `pinet-skin-creator` for safely drafting
316
- or reviewing curated Pinet skin descriptors and character/status-vocabulary
317
- pools before changing runtime skin wiring.
318
- - **Modal helpers are patterns, not hot tools.** Use dispatcher actions
319
- `modal_open`, `modal_push`, and `modal_update` with Slack view JSON. Open or
320
- push immediately after receiving a fresh `trigger_id`; Slack trigger IDs
321
- expire quickly. Include `thread_ts` when submissions should route back to an
322
- original assistant thread.
323
- - **Uploads are for bulky artifacts.** Use `upload` for logs, screenshots,
324
- long diffs, and generated files instead of large inline messages. Inline
325
- uploads require `filename`; path uploads are guarded and must stay within the
326
- current working directory or system temp directory. `slack_send` also accepts
327
- `files: [{ path, filename?, title?, filetype? }]` so one assistant-thread
328
- reply can contain both text and local binary attachments in the same Slack
329
- file upload message. Slack external file uploads cannot include Block Kit in
330
- that same message, so omit `blocks` when sending files or send a separate
331
- block-only reply.
332
- - **Inbound Slack files are fetched explicitly.** Incoming file-share messages
333
- preserve safe `slackFiles` metadata such as file ID, name, type, size, and
334
- permalink, but private Slack download URLs are not exposed in normal tool
335
- output. To inspect raw content, call dispatcher action `file` with
336
- `op: "download"`, `file_id`, and optionally `thread_ts`, `message_ts`, and
337
- `channel`. The bot fetches the file with Slack bot auth, stores it under the
338
- system temp `pi-slack-files` cache with best-effort TTL cleanup, and returns a
339
- descriptor containing the local path, filename, type, size, SHA-256, expiry,
340
- and residual privacy risks.
341
- - **Upload host egress note.** The second upload leg goes to Slack file upload
342
- hosts (`files.slack.com`/`uploads.slack.com`) for the raw payload. In
343
- environments with restricted egress this can fail with `403` (proxy
344
- allowlist) or DNS errors after `files.getUploadURLExternal`; verify the proxy
345
- allowlist first, or route through an environment that can reach those hosts.
346
- - **Upload metadata note.** Slack snippet uploads attempt to use inferred
347
- `snippet_type` values for inline content and retry with plain upload metadata
348
- when Slack returns `invalid_arguments`, preserving syntax highlighting for
349
- supported types while avoiding hard failures on unsupported snippet types.
350
- - **Canvases are long-lived docs.** `canvas_create` creates standalone or
351
- channel canvases. If Slack rejects channel tab creation with
352
- `canvas_tab_creation_failed`, it falls back to a standalone canvas attached to
353
- the channel, attempts to bookmark the canvas URL, and returns the fallback
354
- `canvas_id` for future `canvas_update` calls. `canvas_update` can append,
355
- prepend, replace the whole canvas, or replace a matched section;
356
- `canvas_comments_read` is read-only and limited to verified canvas targets.
357
- - **Scheduling, pins, and bookmarks are durable affordances.** Use `schedule`
358
- for delayed reminders instead of waiting; use `pin` for important thread
359
- messages; use `bookmark` for persistent channel-header links to repos,
360
- dashboards, docs, or runbooks.
361
- - **Presence helps choose timing.** Use `presence` before pinging humans when
362
- active/away/DND status affects routing or whether to schedule a follow-up.
363
- - **Destructive actions stay constrained.** `delete` can remove only messages
364
- posted by the current bot and every delete call requires `confirm: true`.
365
- Whole-thread deletion additionally requires `thread: true` and succeeds only
366
- when every message in the target thread belongs to the current bot. Prefer
367
- asking for explicit approval before destructive cleanup.
368
- - **Confirm guarded actions in the same thread.** If guardrails require
369
- confirmation, call `confirm_action` with the target `thread_ts`, exact tool
370
- name, and the exact action string required by the guarded tool. The safest
371
- flow is: attempt the guarded call, copy the `requires confirmation for action
372
- ...` string from the error, request confirmation, wait for the user's approval
373
- via `slack_inbox`, then retry the guarded call unchanged. Batched
374
- multi-thread Slack turns cannot satisfy a single-thread confirmation.
375
- - **Plain emoji reactions are not tasks.** Slack emoji reactions are ignored
376
- unless `reactionCommands` explicitly opts that emoji into structured
377
- reaction-trigger handling and the reacted message belongs to an already
378
- authorized Pinet thread. If an opt-in reaction-triggered request or a Block
379
- Kit/modal interaction payload arrives through `slack_inbox` with metadata,
380
- treat it as a user instruction tied to the referenced Slack thread or
381
- message.
382
-
383
- #### Common dispatcher examples
384
-
385
- Reply in the current Slack assistant thread with Block Kit:
243
+ ### In Slack
386
244
 
387
- ```json
388
- {
389
- "text": "Deploy complete — branch main, checks passed.",
390
- "blocks": [
391
- {
392
- "type": "section",
393
- "fields": [
394
- { "type": "mrkdwn", "text": "*Branch*\n`main`" },
395
- { "type": "mrkdwn", "text": "*Checks*\n✅ lint/typecheck/test" }
396
- ]
397
- }
398
- ]
399
- }
400
- ```
245
+ Talk to Pinet:
401
246
 
402
- Post a channel/thread update through the dispatcher:
247
+ - Direct message: open a DM with Pinet
248
+ - In channels: mention `@pinet` (or your app name)
249
+ - Slack slash command: type `/pinet agents list` or `/pinet agents list all`
403
250
 
404
- ```json
405
- {
406
- "action": "post_channel",
407
- "args": {
408
- "channel": "#pinet-logs",
409
- "thread_ts": "1712345678.000100",
410
- "text": "PR #123 is ready for review."
411
- }
412
- }
413
- ```
251
+ ### Pi commands
414
252
 
415
- Upload a generated diff snippet:
253
+ Run these inside pi.
416
254
 
417
- ```json
418
- {
419
- "action": "upload",
420
- "args": {
421
- "content": "diff --git a/README.md b/README.md\n...",
422
- "filename": "docs.diff",
423
- "filetype": "diff",
424
- "title": "Docs changes",
425
- "thread_ts": "1712345678.000100"
426
- }
427
- }
428
- ```
255
+ Main commands:
256
+
257
+ - `/pinet` - show available commands
258
+ - `/pinet status` - show current Pinet status
259
+ - `/pinet logs` - show recent broker activity logs
260
+ - `/pinet rename [name]` - rename this agent
261
+ - `/pinet free` - mark this agent idle
429
262
 
430
- Request confirmation before a guarded destructive action after copying the
431
- exact action string from the guardrail error:
263
+ Coordinator commands:
264
+
265
+ - `/pinet start` or `/pinet broker` - become the broker
266
+ - `/pinet start replace` - take over a stale or stranded broker (graceful shutdown first, then a fenced SIGTERM fallback)
267
+ - `/pinet follow` - become a follower
268
+ - `/pinet unfollow` - disconnect from broker
269
+ - `/pinet reload <agent>` - ask another agent to reload
270
+ - `/pinet exit <agent>` - ask another agent to exit
271
+ - `/pinet snooze [duration|off|status]` - quiet empty RALPH cycles
272
+ - `/pinet subtree [start|status|spawn|stop]` - manage subtree broker mode
273
+
274
+ ### From pi
275
+
276
+ Use the Pinet dispatcher for agent coordination:
432
277
 
433
278
  ```json
434
279
  {
435
- "action": "confirm_action",
280
+ "action": "send",
436
281
  "args": {
437
- "thread_ts": "1712345678.000100",
438
- "tool": "slack:delete",
439
- "action": "channel=#pinet-logs | thread_ts=1712345678.000100 | ts=1712345678.000200 | thread=false"
282
+ "to": "@worker",
283
+ "message": "Please review PR #123"
440
284
  }
441
285
  }
442
286
  ```
443
287
 
444
- #### Canvas comment inspection
445
-
446
- The `canvas_comments_read` dispatcher action is intentionally narrow:
447
-
448
- - it validates the target with `canvases.sections.lookup` before reading comment pages via `files.info`
449
- - it needs `files:read` because Slack exposes canvas comments through the file API surface
450
- - it will **not** inspect generic Slack files, non-canvas file comments, or full canvas body/history
451
-
452
- ### Slash commands
453
-
454
- | Command | Description |
455
- | -------------------------- | ---------------------------------------------------------- |
456
- | `/pinet <action>` | Unified Pinet command surface; run `/pinet help` for usage |
457
- | `/pinet status` | Show connection status, threads, and agent identity |
458
- | `/pinet rename` | Change the agent's display name |
459
- | `/pinet logs` | Show recent broker activity log entries |
460
- | `/<app> agents list [all]` | Slack-native broker roster, workload, task, and lane view |
288
+ Common actions:
461
289
 
462
- ## Runtime modes
290
+ - `send` - send a message to an agent or broker-only channel
291
+ - `read` - read this agent's inbox
292
+ - `schedule` - schedule a future wake-up
293
+ - `free` - mark this agent idle
294
+ - `help` - discover actions and schemas
463
295
 
464
- `slack-bridge` now treats runtime mode as an explicit concept:
296
+ Use `slack_send` for hot-path Slack replies. Use the `slack` dispatcher for uploads, canvases, pins, bookmarks, and other Slack actions.
465
297
 
466
- | Mode | Meaning |
467
- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
468
- | `off` | Slack bridge is loaded, but **no Slack Socket Mode ingress** and no coordination runtime are started. |
469
- | `single` | One local Pi session owns Slack ingress and local thread/inbox ownership only. No broker DB/socket/client, no RALPH/control plane, no mesh auth, no multi-agent surface. |
470
- | `broker` | The session runs the broker coordination runtime. |
471
- | `follower` | The session connects to an existing broker as a worker runtime. |
298
+ ## Architecture
472
299
 
473
- Startup selection:
300
+ ### Broker and followers
474
301
 
475
- - `runtimeMode` is the explicit startup selector.
476
- - `autoConnect` is a legacy compatibility alias for `runtimeMode: "single"`.
477
- - `autoFollow` is a legacy compatibility alias for `runtimeMode: "follower"` when a broker socket is available.
478
- - explicit `runtimeMode` wins over the legacy flags.
479
- - `/pinet start` and `/pinet follow` still switch the live session into broker/follower runtimes explicitly.
302
+ Pinet can run as:
480
303
 
481
- ## Scope carriers (compatibility-first)
304
+ - single - one instance handles everything
305
+ - broker - coordinates and routes messages
306
+ - follower - receives work from the broker
482
307
 
483
- `slack-bridge` now emits first-class runtime scope carriers in shared transport contracts and agent runtime metadata.
308
+ The broker:
484
309
 
485
- - `scope.workspace` models the current Slack install/workspace scope.
486
- - `scope.instance` models the current broker/runtime instance scope.
487
- - in the first slice, both stay **compatibility-first**: today’s singleton runtime gets one default compatibility scope
488
- - if Slack omits `team_id`, the carrier keeps the workspace id **unknown** instead of inventing a fake one
489
- - this slice is metadata/plumbing only: it does **not** change routing, enforcement, or multi-install orchestration yet
310
+ - watches Slack for messages
311
+ - assigns work to agents
312
+ - tracks who owns what
313
+ - syncs state across followers
490
314
 
491
- ## Pinet (Multi-Agent Mode)
315
+ Followers:
492
316
 
493
- Pinet supports a broker/follower architecture for coordinating multiple pi agents over Slack.
317
+ - connect to the broker
318
+ - receive assigned work
319
+ - stay in sync automatically
494
320
 
495
- ### Runtime composition boundary
321
+ ### RALPH maintenance loop
496
322
 
497
- Broker startup is composed as Pinet core plus injected transport adapter factories. `broker-runtime.ts` starts the broker DB/socket/router and skin/agent state, then calls `createAdapterBindings` to attach transports. The packaged Slack bridge passes `createSlackPinetRuntimeAdapterFactory(...)`, while tests use an in-memory non-Slack adapter to demonstrate the same boundary without Slack tokens or Slack-specific metadata. Adapter factories return `MessageAdapter` bindings; the core wires inbound delivery, registers adapters on the broker, and connects them.
323
+ RALPH keeps broker state healthy. It:
498
324
 
499
- ### Quick start
325
+ - runs every 5 minutes by default
326
+ - checks worker presence
327
+ - releases stale claims held by unavailable workers
328
+ - observes pending backlog while broker maintenance handles assignment
329
+ - triggers wake-ups
500
330
 
501
- **Broker** (one per mesh — coordinates routing and health):
331
+ Configure RALPH:
502
332
 
503
- ```
504
- /pinet start
333
+ ```json
334
+ {
335
+ "slack-bridge": {
336
+ "ralphLoopIntervalMs": 120000,
337
+ "ralphSnoozeAfterEmptyCycles": 3,
338
+ "ralphSnoozeDurationMs": 1800000
339
+ }
340
+ }
505
341
  ```
506
342
 
507
- **Follower** (workers that connect to the broker):
343
+ ### Inbox and threading
508
344
 
509
- ```
510
- /pinet follow
511
- ```
345
+ Pinet maintains an inbox for each agent. Messages are:
512
346
 
513
- Or set `"runtimeMode": "follower"` in settings (or the legacy `"autoFollow": true`) to auto-connect when a broker is running.
347
+ - routed based on thread ownership
348
+ - queued when agents are busy
349
+ - marked read when processed
350
+ - preserved across restarts
514
351
 
515
- ### Broker prompt MD
352
+ Thread ownership ensures continuity. Once an agent owns a thread, it keeps receiving those messages.
516
353
 
517
- Broker coordination policy is loaded from Markdown. Configure `slack-bridge.brokerPrompt` to choose a packaged prompt preset such as `tmux` or to point at a custom Markdown file path. Relative paths resolve under the current repo/worktree root; `~/...` paths resolve under the user home directory. When no setting is present, the broker scans for the first valid prompt in this order:
354
+ ## Troubleshooting
518
355
 
519
- 1. workspace override: `.pi/slack-bridge/tmux.md` under the current repo/worktree root
520
- 2. user-local override: `~/.pi/agent/slack-bridge/tmux.md`
521
- 3. packaged default: `dist/prompts/broker/tmux.md`
356
+ ### Socket Mode connection issues
522
357
 
523
- Invalid higher-priority files (unsafe symlink/path escape, unreadable file, oversized content, invalid UTF-8/binary-looking content, or empty file) emit a concise warning and fall through to lower-priority candidates. Warnings identify only the candidate kind and reason; prompt bodies and private paths are not echoed.
358
+ If you see 'WebSocket error' or connection failures:
524
359
 
525
- The packaged `tmux.md` captures the default fully autonomous / unchained broker operating policy: the broker coordinates and never implements, delegates to repo-scoped workers, starts fresh tmux-backed workers on the Mac mini for new repo-scoped tasks/lanes unless a maintainer explicitly asks for reuse, marks broker-launched followers with `PINET_BROKER_MANAGED=1 PINET_BROKER_AGENT_ID=<current-broker-agent-id> PINET_LAUNCH_SOURCE=broker-tmux PINET_TMUX_SESSION=<session>` so PID ownership is inspectable, records tmux session/socket and repo/worktree metadata in durable lane state, keeps completed workers available for a one-hour follow-up grace period, routes follow-up back to the same Pi instance when possible, asks grace-expired healthy idle broker-managed workers to exit only when inspectable Pinet signals or the worker confirm they are free, fails closed by reporting ambiguous cleanup candidates, prunes old broker-managed tmux capacity instead of recycling stale context into new lanes, observes RALPH loop maintenance expectations, handles Slack thread ownership/reporting caveats, and describes GitHub/secret handling without exposing secrets.
360
+ 1. Check your app token is valid
361
+ 2. Verify Socket Mode is enabled in your Slack app
362
+ 3. Check network connectivity
363
+ 4. Look for rate limiting (Slack allows 10 connections per app)
526
364
 
527
- Only broker prompt content is replaceable. Broker runtime/tool restrictions remain code-owned and are appended after the loaded MD prompt, including the forbidden local `Agent` path and broker `edit`/`write` blocking. Followers keep append-only worker guidance and do not load broker prompt MD. Prompt changes are picked up on `/pinet start` / runtime restart; this slice does not hot-reload per turn.
365
+ ### Permission errors
528
366
 
529
- ### Multi-agent tools
367
+ If Pinet cannot perform actions:
530
368
 
531
- | Tool | Description |
532
- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
533
- | `pinet` | Pinet dispatcher with token-efficient `action`-based routing (`help`, `send`, `read`, `free`, `snooze`, `schedule`, `agents`, `sessions`, `lanes`, `ports`, `spawn`, `reload`, `exit`) |
369
+ 1. Check the bot is in the channel (invite with `/invite @pinet`)
370
+ 2. Verify bot scopes match the manifest
371
+ 3. Reinstall the app to update permissions
372
+ 4. Check `allowedUsers` includes the right user IDs
534
373
 
535
- Use the dispatcher for Pinet tool actions: `pinet action=send`, `pinet action=read`, `pinet action=free`, `pinet action=snooze`, `pinet action=schedule`, `pinet action=agents`, `pinet action=sessions`, `pinet action=lanes`, `pinet action=ports`, `pinet action=spawn`, `pinet action=reload`, and `pinet action=exit`. Use slash commands for UI lifecycle transitions: `/pinet start`, `/pinet follow`, `/pinet unfollow`, and `/pinet subtree start`. Dedicated direct Pinet tools (`pinet_message`, `pinet_read`, `pinet_agents`, `pinet_free`, `pinet_schedule`) are no longer registered. Legacy `pinet_*` guardrail patterns still match dispatcher action names, and legacy send policies such as `pinet_send` or `pinet_message` also cover `pinet action=send`, so existing security configs fail closed during migration.
374
+ ### Messages not received
536
375
 
537
- Worker-owned subtree brokers let a follower worker supervise its own child mesh without registering those children in the central broker. Run `/pinet subtree start` (alias: `/pinet subbroker start`) from a follower worker. The worker remains connected to the central broker as a normal worker, and it also starts a separate broker socket/database under `~/.pi/pinet-subtrees/<worker>/`. Child workers launched by this worker receive `PINET_SOCKET_PATH`, `PINET_PARENT_AGENT_ID`, `PINET_ROOT_AGENT_ID`, `PINET_LAUNCH_ID`, `PINET_SUBTREE_ROLE`, and related metadata, so they follow the worker's subtree broker instead of the central Pinet broker.
376
+ If Pinet does not respond:
538
377
 
539
- Use `pinet action=spawn args.repo=<repo> args.task=<task> [args.role=<role>] [args.lane_id=<lane>]` or `/pinet subtree spawn repo=<repo> [role=<role>] [lane=<lane>] <task>` to launch a tmux-backed child worker, wait for it to register in the subtree broker, and deliver the task over private Pinet A2A. Use `pinet action=agents args.scope=subtree args.full=true` from the supervising worker to list subtree children, `pinet action=send args.to=<child> args.message=<message>` to reply/control them, `pinet action=read` to read child reports, and `pinet action=exit args.target=<child>` or `/pinet subtree stop` to clean them up. The central broker sees only the supervising worker; the subtree DB contains the child roster and messages.
378
+ 1. Check Socket Mode shows 'Connected' in Slack app settings
379
+ 2. Verify event subscriptions are enabled
380
+ 3. Check `allowedUsers` or `allowAllWorkspaceUsers`
381
+ 4. Look in the log channel for errors
382
+ 5. Try `/pinet status` to check if Pinet is running
540
383
 
541
- Dispatcher content defaults to terse CLI-style confirmations/summaries for noisy reads, sends, agent lists, and session lookups. Bulky read/agent/session payloads are compacted in `data.details` by default, including when `args.format="json"` (or `args.f` / `args["-f"]`) renders the dispatcher envelope in content. Use `args.full=true` / `args["--full"]=true` only when you need verbose text and full structured debug details such as exact message bodies, agent metadata, stable session IDs, or local session JSONL paths.
384
+ ### Stranded broker (lock held, controlling session lost)
542
385
 
543
- `pinet action=agents` shows a broker-safe session reference (`session:<digest>`) alongside pid in verbose roster output without exposing raw local paths by default. Use `pinet action=sessions args.agent_name="Frozen Hazel Whale"` to search live and historical worker sessions by display name; the search also accepts `agent_id`, `thread_id`, `repo`, `worktree_path`, `tmux_session`, `since`, `until`, and `limit`. Default output redacts path-bearing stable IDs; add `args.full=true` (or JSON plus `full`) only in local/debug contexts when the broker needs the exact stable ID or Pi session JSONL path for inspection/resume.
386
+ If `/pinet start` reports that another broker is already running but you no
387
+ longer have that broker's Pi session (crash, laptop restart, stalled process):
544
388
 
545
- Durable Pinet inbox notifications are classified as `steering`, `fwup`, or `maintenance/context` from explicit metadata or message cues. Follower prompts receive compact pointers such as `pinet action=read args.thread_id=...` instead of the full durable message body; agents use `pinet action=read` to retrieve the actual context. Delivery, read/ack state, and mail classification remain separate.
389
+ 1. Run `/pinet status` from any session it reports machine-wide broker lock
390
+ ownership and socket health even while disconnected.
391
+ 2. If the broker is healthy and you just want to participate, run
392
+ `/pinet follow`.
393
+ 3. If the broker is stranded (or you need the broker in this session), run
394
+ `/pinet start replace`. It asks the current broker to shut down gracefully
395
+ over the socket, falls back to a verified SIGTERM against the recorded lock
396
+ owner, and never escalates to SIGKILL.
546
397
 
547
- Scheduled Pinet wake-ups use the same durable read surface: due wake-ups are persisted/stamped as Pinet follow-up mail and surfaced through compact `pinet action=read` pointers rather than direct reminder-body prompts. Wake-up bodies and metadata are treated as mail content only; they do not trigger Pinet remote-control commands such as `/exit`, `/reload`, or structured `pinet:control` JSON.
398
+ Two cases intentionally refuse automatic termination:
548
399
 
549
- Durable lane metadata is stored in SQLite and can be inspected/updated with `pinet action=lanes`. PM-mode lanes can record the accountable follower/PM, implementation lead, participant roles (`pm`, `lead`, `implementer`, `reviewer`, `second_pass_reviewer`, etc.), linked issue/PR, state, and summary. The `detached` lane state means a lane is manually supervised by a human; broker/RALPH/status surfaces keep it visible but should not treat it as normal auto-reassignment work without explicit human/broker action.
400
+ - **Legacy locks** (written by older builds, PID-only): there is no recorded
401
+ process start identity, so a SIGTERM could hit an unrelated process that
402
+ reused the PID. Inspect the process manually (`ps -p <pid>`), terminate it
403
+ yourself if it is truly the stranded broker, then run `/pinet start`.
404
+ - **Rejected shutdown**: a broker that responds but rejects the shutdown
405
+ request (usually a mesh secret mismatch) is alive, not stranded. Fix the
406
+ mesh secret configuration or stop that broker from its own session.
550
407
 
551
- Durable local port leases are stored in SQLite and can be managed with `pinet action=ports`. Use `op=acquire` with `purpose` and `ttl_ms` to reserve either a requested `port` (for example `3000`) or the first free port in `min_port..max_port` (default `49152..65535`, host default `127.0.0.1`). Use `op=renew` with `lease_id` and `ttl_ms`, `op=release`, `op=status`, `op=list`, or `op=expire`. Follower RPC access is scoped to the caller-owned leases; broker-local maintenance can still expire all stale leases. Active leases are unique by `(host, port)`; broker maintenance expires stale leases conservatively, and process-kill behavior should be layered on explicit cleanup hooks rather than hidden in lease acquisition.
408
+ ### Stalled agents
552
409
 
553
- Broker-mode ghost cleanup is deliberately conservative. The broker stores follower PIDs in the agent registry, but it only sends real process signals for ghosts that registered with broker-managed launch metadata (`PINET_BROKER_MANAGED=1`) and still verify as Pi follower processes. Reaping sends `SIGTERM` first and schedules a bounded `SIGKILL` only if the same verified broker-managed process remains; unmarked or mismatched PIDs are never killed.
410
+ If work gets stuck:
554
411
 
555
- RALPH snooze quiets non-urgent empty maintenance cycles without disabling human-triggered routing. Use `/pinet snooze 30m no work available` or `pinet action=snooze args.op=set args.duration=30m` to quiet the broker manually, `/pinet snooze off` or `op=clear` to wake it, and `/pinet status` / Home tab to inspect snooze state. An empty cycle means no active live workers, no active tracked assignments, no visible RALPH anomalies, no pending backlog, no assigned backlog from broker maintenance, no maintenance anomalies, no pending task-assignment report, and no tracked task progress change. If active work, anomalies, or task progress appears during snooze, RALPH wakes and reports normally. Auto-snooze is opt-in via `ralphSnoozeAfterEmptyCycles`; the default is disabled.
412
+ 1. Check `/pinet status` for current state.
413
+ 2. Check `/pinet logs` for repeated failures.
414
+ 3. Wait for RALPH to run automatically.
415
+ 4. Reduce `ralphLoopIntervalMs` for faster recovery if needed.
556
416
 
557
- ### Pinet command surface
417
+ ## Package information
558
418
 
559
- Use `/pinet <action> [args]` for mesh lifecycle and broker operations. In the Slack web app, use `/<app> agents list [all]` for the Slack-native broker roster/current-work view: `/pinet agents list` for the Pinet app, or `/oathgate agents list` for an Oathgate-named app. Set `slackCommandName` (or `slackCommandNames`) in `slack-bridge` settings before deploying the manifest when the Slack command should match a non-default app name.
419
+ ### Publishing metadata
560
420
 
561
- | Command | Description |
562
- | ------------------------------------------ | ----------------------------------------------------------------------------- |
563
- | `/pinet start` | Start as the mesh broker |
564
- | `/pinet follow` | Connect as a follower worker |
565
- | `/pinet unfollow` | Disconnect from the broker |
566
- | `/pinet reload <agent>` | Ask another agent to reload |
567
- | `/pinet exit <agent>` | Ask another agent to exit |
568
- | `/pinet free` | Mark this agent as idle |
569
- | `/pinet snooze [duration/off/status]` | Quiet empty RALPH cycles while preserving human-triggered wake/route behavior |
570
- | `/pinet subtree [start/status/spawn/stop]` | Run this worker as a local subtree broker for child followers |
421
+ The package declares pi metadata in [`package.json`](./package.json):
571
422
 
572
- ### Pinet skins
423
+ - `keywords` includes `pi-package` for gallery discovery
424
+ - `pi.extensions` points to `./dist/index.js`
425
+ - `pi.skills` points to bundled skills
426
+ - No preview assets yet
573
427
 
574
- Pinet skin selection is configuration-driven. Set `skinTheme` under the `slack-bridge` settings object (for example, `"skinTheme": "foundation"`) and restart/reload the broker; broker startup applies the configured presentation to broker and follower registrations. Skin selection updates mesh presentation only: names, emoji palette, persona/tone guidance, and optional display vocabulary for statuses. Core roles and states stay skin-neutral (`broker`, `worker`, `idle`, `working`, routing, repo, and guardrails are not redefined by skins).
428
+ Check the package contents:
575
429
 
576
- Built-in skins:
430
+ ```bash
431
+ cd slack-bridge
432
+ npm pack --dry-run
433
+ ```
577
434
 
578
- - `default` / `classic` — preserves the current whimsical animal names, animal emoji palette, and playful-but-focused persona.
579
- - `foundation` / `foundation/space` / `space` — JSON descriptor with curated institutional sci-fi characters, full-name aliases, and archive, relay, frontier, and crisis-room flavor.
580
- - `cosmere` / `cosmere-inspired` / `oathgate` — JSON descriptor with curated/prebaked 1–3 word identities, static emoji, and whimsical Mistborn/Stormlight/Emberdark-inspired agents, spren, artifacts, places, and jokes while avoiding exact third-party character names.
435
+ ### Development
581
436
 
582
- Free-form themes are still accepted as deterministic legacy/custom presentation themes. Shipped non-default skins live in `skins/*.json`; use the bundled `pinet-skin-creator` skill to author and review curated character/name/persona/status-vocabulary pools before adding runtime descriptors.
437
+ Build the package:
583
438
 
584
- ### How it works
439
+ ```bash
440
+ cd slack-bridge
441
+ pnpm build
442
+ ```
585
443
 
586
- - The **broker** runs Slack Socket Mode, routes messages to agents, and monitors health via the RALPH loop. The loop defaults to every 5 minutes and can be configured with `ralphLoopIntervalMs` under `slack-bridge` settings.
587
- - **Followers** connect to the broker over a local Unix socket, poll for work, and report results
588
- - Agents can optionally authenticate using a shared local secret (`meshSecret` or `meshSecretPath`); when both are unset, mesh auth is disabled
589
- - Thread ownership is first-responder-wins — the first agent to reply claims the thread
444
+ Run tests:
590
445
 
591
- ## Security
446
+ ```bash
447
+ pnpm test
448
+ ```
592
449
 
593
- - **User access**: Slack access is default-deny. Set `allowedUsers` for a narrow allowlist, or `allowAllWorkspaceUsers: true` only if you explicitly want workspace-wide access
594
- - **Tool guardrails**: `security.readOnly`, `security.requireConfirmation`, and `security.blockedTools` are runtime-enforced for Slack-triggered turns, including core tools such as `bash`, `edit`, and `write`
595
- - **Guardrail posture**: If Slack/Pinet access is enabled for admitted users and `security.readOnly`, `security.blockedTools`, and `security.requireConfirmation` are all effectively empty (`readOnly !== true` and both arrays are absent or empty), the bridge emits a startup/runtime warning and `/pinet status` shows `Guardrails: empty (warn-first posture; behavior unchanged)`. This is visibility-only: it does **not** auto-enable `readOnly`, block startup, or require an acknowledgement flow.
596
- - **Mesh authentication**: Optional. Configure `meshSecret` or `meshSecretPath` (or `PINET_MESH_SECRET` / `PINET_MESH_SECRET_PATH`) to require a shared secret; leave them unset to disable shared-secret auth. Configured followers fail closed on missing secret files or older/no-auth brokers rather than silently downgrading.
450
+ Deploy the Slack manifest:
597
451
 
598
- Find Slack user IDs: click a user's profile → **More** → **Copy member ID**.
452
+ ```bash
453
+ pnpm deploy:slack
454
+ ```
599
455
 
600
- ---
456
+ ## Security
601
457
 
602
- ## Development
458
+ ### Default-deny access
603
459
 
604
- ### Build
460
+ Pinet requires explicit configuration to allow users. Without `allowedUsers` or `allowAllWorkspaceUsers`, nobody can use it.
605
461
 
606
- ```bash
607
- pnpm run build
608
- ```
462
+ ### Token safety
609
463
 
610
- ### Lint / Typecheck / Test
464
+ - Never commit tokens to git
465
+ - Use environment variables in production
466
+ - Rotate tokens regularly
467
+ - Use separate apps for development and production
611
468
 
612
- ```bash
613
- pnpm lint
614
- pnpm typecheck
615
- pnpm test
616
- ```
469
+ ### Confirmation for dangerous actions
617
470
 
618
- ### Deploy manifest to Slack
471
+ Configure confirmation for sensitive operations:
619
472
 
620
- ```bash
621
- pnpm deploy:slack
473
+ ```json
474
+ {
475
+ "slack-bridge": {
476
+ "security": {
477
+ "requireConfirmation": ["slack:create_channel", "slack:upload", "slack:delete"]
478
+ }
479
+ }
480
+ }
622
481
  ```
623
482
 
624
- Requires `appId` and `appConfigToken` in settings (or `SLACK_APP_ID` / `SLACK_APP_CONFIG_TOKEN` env vars). The deploy path rewrites `features.slash_commands` from `slackCommandName` / `slackCommandNames` (or the configured `skinTheme`) before validating and uploading, so set `slackCommandName: "/oathgate"` for an Oathgate app and leave it unset for the packaged Pinet `/pinet` default.
483
+ ### Read-only mode
625
484
 
626
- ### Architecture
485
+ Prevent all modifications:
627
486
 
628
- - **Socket Mode** — outbound WebSocket, no public URL needed
629
- - **Zero runtime npm deps** — native `fetch`, `WebSocket`, `node:sqlite` (Node 22+)
630
- - **Hybrid inbox** — queue when busy, auto-drain when idle
631
- - **Reactions** — 👀 as a lightweight "thinking" indicator
632
- - **Thread persistence** — thread state survives `/reload`
487
+ ```json
488
+ {
489
+ "slack-bridge": {
490
+ "security": {
491
+ "readOnly": true
492
+ }
493
+ }
494
+ }
495
+ ```
633
496
 
634
- ## License
497
+ ## Support
635
498
 
636
- MIT. See [`LICENSE`](./LICENSE).
499
+ - [GitHub repository](https://github.com/gugu91/pinet)
500
+ - [Architecture documentation](../plans/)
501
+ - Check the log channel in Slack for runtime issues