@pinet/slack-bridge 0.1.0
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/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/activity-log.d.ts +62 -0
- package/dist/activity-log.js +293 -0
- package/dist/agent-completion-runtime.d.ts +17 -0
- package/dist/agent-completion-runtime.js +26 -0
- package/dist/agent-event-runtime.d.ts +13 -0
- package/dist/agent-event-runtime.js +27 -0
- package/dist/agent-prompt-guidance.d.ts +22 -0
- package/dist/agent-prompt-guidance.js +46 -0
- package/dist/broker/adapters/slack.d.ts +74 -0
- package/dist/broker/adapters/slack.js +563 -0
- package/dist/broker/adapters/types.d.ts +1 -0
- package/dist/broker/adapters/types.js +1 -0
- package/dist/broker/agent-messaging.d.ts +1 -0
- package/dist/broker/agent-messaging.js +1 -0
- package/dist/broker/auth.d.ts +1 -0
- package/dist/broker/auth.js +1 -0
- package/dist/broker/client.d.ts +167 -0
- package/dist/broker/client.js +624 -0
- package/dist/broker/control-plane-dashboard.d.ts +98 -0
- package/dist/broker/control-plane-dashboard.js +213 -0
- package/dist/broker/ghost-reaper.d.ts +49 -0
- package/dist/broker/ghost-reaper.js +209 -0
- package/dist/broker/index.d.ts +36 -0
- package/dist/broker/index.js +90 -0
- package/dist/broker/leader.d.ts +1 -0
- package/dist/broker/leader.js +1 -0
- package/dist/broker/maintenance.d.ts +1 -0
- package/dist/broker/maintenance.js +1 -0
- package/dist/broker/message-send.d.ts +1 -0
- package/dist/broker/message-send.js +1 -0
- package/dist/broker/paths.d.ts +1 -0
- package/dist/broker/paths.js +1 -0
- package/dist/broker/raw-tcp-loopback.d.ts +1 -0
- package/dist/broker/raw-tcp-loopback.js +1 -0
- package/dist/broker/router.d.ts +1 -0
- package/dist/broker/router.js +1 -0
- package/dist/broker/schema.d.ts +9 -0
- package/dist/broker/schema.js +63 -0
- package/dist/broker/socket-server.d.ts +120 -0
- package/dist/broker/socket-server.js +1087 -0
- package/dist/broker/types.d.ts +1 -0
- package/dist/broker/types.js +1 -0
- package/dist/broker-delivery.d.ts +10 -0
- package/dist/broker-delivery.js +26 -0
- package/dist/broker-inbound-persistence.d.ts +11 -0
- package/dist/broker-inbound-persistence.js +22 -0
- package/dist/broker-prompt-loader.d.ts +34 -0
- package/dist/broker-prompt-loader.js +185 -0
- package/dist/broker-runtime-access.d.ts +14 -0
- package/dist/broker-runtime-access.js +20 -0
- package/dist/broker-runtime.d.ts +100 -0
- package/dist/broker-runtime.js +533 -0
- package/dist/broker-thread-owner-hints.d.ts +10 -0
- package/dist/broker-thread-owner-hints.js +14 -0
- package/dist/canvases.d.ts +80 -0
- package/dist/canvases.js +221 -0
- package/dist/command-registration-runtime.d.ts +9 -0
- package/dist/command-registration-runtime.js +9 -0
- package/dist/core-tool-guardrails.d.ts +24 -0
- package/dist/core-tool-guardrails.js +66 -0
- package/dist/deploy-manifest.d.ts +27 -0
- package/dist/deploy-manifest.js +197 -0
- package/dist/follower-delivery.d.ts +16 -0
- package/dist/follower-delivery.js +70 -0
- package/dist/follower-runtime.d.ts +61 -0
- package/dist/follower-runtime.js +350 -0
- package/dist/git-metadata.d.ts +35 -0
- package/dist/git-metadata.js +88 -0
- package/dist/guardrails.d.ts +69 -0
- package/dist/guardrails.js +283 -0
- package/dist/helpers.d.ts +550 -0
- package/dist/helpers.js +2711 -0
- package/dist/home-tab.d.ts +27 -0
- package/dist/home-tab.js +242 -0
- package/dist/imessage-tools.d.ts +33 -0
- package/dist/imessage-tools.js +96 -0
- package/dist/inbox-drain-runtime.d.ts +30 -0
- package/dist/inbox-drain-runtime.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1330 -0
- package/dist/persisted-runtime-state.d.ts +52 -0
- package/dist/persisted-runtime-state.js +98 -0
- package/dist/pinet-activity-formatting.d.ts +20 -0
- package/dist/pinet-activity-formatting.js +43 -0
- package/dist/pinet-agent-status.d.ts +36 -0
- package/dist/pinet-agent-status.js +48 -0
- package/dist/pinet-commands.d.ts +70 -0
- package/dist/pinet-commands.js +402 -0
- package/dist/pinet-confirmation-replies.d.ts +7 -0
- package/dist/pinet-confirmation-replies.js +30 -0
- package/dist/pinet-control-plane-dashboard.d.ts +34 -0
- package/dist/pinet-control-plane-dashboard.js +86 -0
- package/dist/pinet-home-tabs.d.ts +36 -0
- package/dist/pinet-home-tabs.js +101 -0
- package/dist/pinet-maintenance-delivery.d.ts +22 -0
- package/dist/pinet-maintenance-delivery.js +45 -0
- package/dist/pinet-mesh-ops.d.ts +97 -0
- package/dist/pinet-mesh-ops.js +266 -0
- package/dist/pinet-registration-gate.d.ts +14 -0
- package/dist/pinet-registration-gate.js +42 -0
- package/dist/pinet-remote-control-acks.d.ts +16 -0
- package/dist/pinet-remote-control-acks.js +46 -0
- package/dist/pinet-remote-control.d.ts +17 -0
- package/dist/pinet-remote-control.js +74 -0
- package/dist/pinet-runtime-composition.d.ts +27 -0
- package/dist/pinet-runtime-composition.js +20 -0
- package/dist/pinet-tools.d.ts +67 -0
- package/dist/pinet-tools.js +1211 -0
- package/dist/prompts/broker/default.md +41 -0
- package/dist/prompts/broker/tmux.md +49 -0
- package/dist/ralph-loop.d.ts +76 -0
- package/dist/ralph-loop.js +524 -0
- package/dist/reaction-triggers.d.ts +25 -0
- package/dist/reaction-triggers.js +171 -0
- package/dist/repo-tool-guardrails.d.ts +20 -0
- package/dist/repo-tool-guardrails.js +81 -0
- package/dist/runtime-agent-context.d.ts +93 -0
- package/dist/runtime-agent-context.js +302 -0
- package/dist/runtime-mode.d.ts +10 -0
- package/dist/runtime-mode.js +39 -0
- package/dist/scheduled-wakeups.d.ts +1 -0
- package/dist/scheduled-wakeups.js +1 -0
- package/dist/session-ui-runtime.d.ts +20 -0
- package/dist/session-ui-runtime.js +163 -0
- package/dist/single-player-runtime.d.ts +71 -0
- package/dist/single-player-runtime.js +436 -0
- package/dist/skins/cosmere.json +1571 -0
- package/dist/skins/foundation.json +1304 -0
- package/dist/slack-access.d.ts +186 -0
- package/dist/slack-access.js +462 -0
- package/dist/slack-api.d.ts +2 -0
- package/dist/slack-api.js +1 -0
- package/dist/slack-block-kit.d.ts +36 -0
- package/dist/slack-block-kit.js +242 -0
- package/dist/slack-export.d.ts +31 -0
- package/dist/slack-export.js +206 -0
- package/dist/slack-message-context.d.ts +15 -0
- package/dist/slack-message-context.js +207 -0
- package/dist/slack-modals.d.ts +13 -0
- package/dist/slack-modals.js +79 -0
- package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
- package/dist/slack-pinet-runtime-adapter.js +72 -0
- package/dist/slack-presence.d.ts +36 -0
- package/dist/slack-presence.js +89 -0
- package/dist/slack-request-runtime.d.ts +8 -0
- package/dist/slack-request-runtime.js +19 -0
- package/dist/slack-runtime-access.d.ts +28 -0
- package/dist/slack-runtime-access.js +107 -0
- package/dist/slack-scope-diagnostics.d.ts +31 -0
- package/dist/slack-scope-diagnostics.js +235 -0
- package/dist/slack-socket-dedup.d.ts +7 -0
- package/dist/slack-socket-dedup.js +135 -0
- package/dist/slack-thread-status.d.ts +41 -0
- package/dist/slack-thread-status.js +156 -0
- package/dist/slack-tool-policy-runtime.d.ts +36 -0
- package/dist/slack-tool-policy-runtime.js +96 -0
- package/dist/slack-tools.d.ts +52 -0
- package/dist/slack-tools.js +2688 -0
- package/dist/slack-turn-guardrails.d.ts +19 -0
- package/dist/slack-turn-guardrails.js +36 -0
- package/dist/slack-upload.d.ts +49 -0
- package/dist/slack-upload.js +224 -0
- package/dist/task-assignments.d.ts +45 -0
- package/dist/task-assignments.js +527 -0
- package/dist/thread-confirmations.d.ts +22 -0
- package/dist/thread-confirmations.js +139 -0
- package/dist/tool-registration-runtime.d.ts +13 -0
- package/dist/tool-registration-runtime.js +13 -0
- package/dist/ttl-cache.d.ts +47 -0
- package/dist/ttl-cache.js +105 -0
- package/manifest.yaml +57 -0
- package/package.json +60 -0
- package/skills/pinet-skin-creator/SKILL.md +109 -0
- package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
- package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
- package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
- package/skills/slack-bridge/SKILL.md +360 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Will Porcellini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# slack-bridge (Pinet)
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:@pinet/slack-bridge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with npm:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @pinet/slack-bridge
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Publishing
|
|
18
|
+
|
|
19
|
+
This package is included in the full npm publish set tracked in
|
|
20
|
+
[`../plans/npm-publish.md`](../plans/npm-publish.md). Use the GitHub Actions
|
|
21
|
+
workflow's default dry-run/readiness path for validation; do not publish, tag, or
|
|
22
|
+
bump versions without explicit maintainer release approval.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- A Slack workspace where you have permission to install apps
|
|
27
|
+
- Node.js 22+ (uses native `fetch` and `WebSocket`)
|
|
28
|
+
- [pi](https://github.com/badlogic/pi-mono) installed
|
|
29
|
+
|
|
30
|
+
## Slack App Setup
|
|
31
|
+
|
|
32
|
+
### 1. Create the app
|
|
33
|
+
|
|
34
|
+
1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App**
|
|
35
|
+
2. Choose **From a manifest**
|
|
36
|
+
3. Select your workspace
|
|
37
|
+
4. Paste the contents of [`manifest.yaml`](./manifest.yaml) from this directory
|
|
38
|
+
5. Click **Create**
|
|
39
|
+
|
|
40
|
+
The manifest configures Socket Mode, the assistant view, all required bot scopes, and event subscriptions automatically.
|
|
41
|
+
|
|
42
|
+
### 2. Generate tokens
|
|
43
|
+
|
|
44
|
+
You need two tokens:
|
|
45
|
+
|
|
46
|
+
| Token | Where to find it | Looks like |
|
|
47
|
+
| ------------------- | -------------------------------------------------------------------------------- | ------------ |
|
|
48
|
+
| **App-Level Token** | Basic Information → App-Level Tokens → Generate (with `connections:write` scope) | `xapp-1-...` |
|
|
49
|
+
| **Bot Token** | OAuth & Permissions → Install to Workspace → Bot User OAuth Token | `xoxb-...` |
|
|
50
|
+
|
|
51
|
+
### 3. Required bot scopes
|
|
52
|
+
|
|
53
|
+
These are included in the manifest, but for reference:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
app_mentions:read assistant:write bookmarks:read
|
|
57
|
+
bookmarks:write canvases:read canvases:write
|
|
58
|
+
channels:history channels:read chat:write
|
|
59
|
+
files:read files:write groups:history
|
|
60
|
+
groups:read im:history im:read
|
|
61
|
+
im:write pins:read pins:write
|
|
62
|
+
reactions:read reactions:write users:read
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`files:read` is required because Slack exposes canvas comment pagination through `files.info`, even when the target is first validated via canvas-specific APIs.
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Add your tokens to `~/.pi/agent/settings.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"slack-bridge": {
|
|
76
|
+
"botToken": "xoxb-your-bot-token",
|
|
77
|
+
"appToken": "xapp-your-app-token"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That's it for a minimal setup. Start pi and Pinet appears in Slack's sidebar.
|
|
83
|
+
|
|
84
|
+
### Environment variables (alternative)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export SLACK_BOT_TOKEN="xoxb-..."
|
|
88
|
+
export SLACK_APP_TOKEN="xapp-..."
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Settings in `settings.json` take priority over env vars.
|
|
92
|
+
|
|
93
|
+
### Optional Pinet mesh auth
|
|
94
|
+
|
|
95
|
+
Shared-secret mesh auth is **optional**. You can configure it with either settings keys or environment variables:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"slack-bridge": {
|
|
100
|
+
"meshSecret": "shared-secret"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"slack-bridge": {
|
|
108
|
+
"meshSecretPath": "/Users/alice/.config/pi/pinet.secret"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export PINET_MESH_SECRET="shared-secret"
|
|
115
|
+
# or
|
|
116
|
+
export PINET_MESH_SECRET_PATH="$HOME/.config/pi/pinet.secret"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Behavior and precedence:
|
|
120
|
+
|
|
121
|
+
- `slack-bridge.meshSecret` and `slack-bridge.meshSecretPath` override the environment fallbacks.
|
|
122
|
+
- Inline secrets win over secret paths. If `meshSecret` or `PINET_MESH_SECRET` is set, the corresponding `*Path` value is ignored.
|
|
123
|
+
- If all four values are unset, broker/follower mesh auth is disabled.
|
|
124
|
+
- A broker started with `meshSecretPath` creates the secret file if it does not exist yet.
|
|
125
|
+
- 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.
|
|
126
|
+
- 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.
|
|
127
|
+
|
|
128
|
+
### Full settings reference
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"slack-bridge": {
|
|
133
|
+
"botToken": "xoxb-...",
|
|
134
|
+
"appToken": "xapp-...",
|
|
135
|
+
"runtimeMode": "single",
|
|
136
|
+
"allowedUsers": ["U_EXAMPLE_MEMBER_ID"],
|
|
137
|
+
"defaultChannel": "C_EXAMPLE_CHANNEL_ID",
|
|
138
|
+
"logChannel": "#pinet-logs",
|
|
139
|
+
"logLevel": "actions",
|
|
140
|
+
"autoFollow": true,
|
|
141
|
+
"ralphLoopIntervalMs": 300000,
|
|
142
|
+
"ralphSnoozeAfterEmptyCycles": 0,
|
|
143
|
+
"ralphSnoozeDurationMs": 1800000,
|
|
144
|
+
"meshSecretPath": "/Users/alice/.config/pi/pinet.secret",
|
|
145
|
+
"suggestedPrompts": [{ "title": "Status", "message": "What are you working on?" }],
|
|
146
|
+
"security": {
|
|
147
|
+
"readOnly": false,
|
|
148
|
+
"requireConfirmation": ["slack:create_channel"],
|
|
149
|
+
"blockedTools": []
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Slack access is now **default-deny** unless you configure one of these explicitly:
|
|
156
|
+
|
|
157
|
+
- `allowedUsers` / `SLACK_ALLOWED_USERS` — allow only specific Slack user IDs
|
|
158
|
+
- `allowAllWorkspaceUsers: true` / `SLACK_ALLOW_ALL_WORKSPACE_USERS=true` — explicit workspace-wide opt-in
|
|
159
|
+
|
|
160
|
+
| Key | Required | Description |
|
|
161
|
+
| ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| `botToken` | **yes** | Bot User OAuth Token (`xoxb-...`) |
|
|
163
|
+
| `appToken` | **yes** | App-Level Token for Socket Mode (`xapp-...`) |
|
|
164
|
+
| `allowedUsers` | no | Slack user IDs that can interact; when unset, access is denied unless `allowAllWorkspaceUsers` is true |
|
|
165
|
+
| `allowAllWorkspaceUsers` | no | Explicit opt-in for workspace-wide Slack access when you do not want a user allowlist |
|
|
166
|
+
| `defaultChannel` | no | Default channel for the `slack` dispatcher `post_channel` action |
|
|
167
|
+
| `logChannel` | no | Channel for broker activity logs |
|
|
168
|
+
| `logLevel` | no | `"errors"`, `"actions"` (default), or `"verbose"` |
|
|
169
|
+
| `runtimeMode` | no | Explicit startup mode: `"off"`, `"single"`, `"broker"`, or `"follower"` |
|
|
170
|
+
| `autoConnect` | no | Legacy compatibility alias for `runtimeMode: "single"` |
|
|
171
|
+
| `autoFollow` | no | Legacy compatibility alias for follower startup when a broker socket exists |
|
|
172
|
+
| `ralphLoopIntervalMs` | no | Broker RALPH maintenance cadence in milliseconds; defaults to `300000` (5 minutes), valid range `1000`-`2147483647` |
|
|
173
|
+
| `ralphSnoozeAfterEmptyCycles` | no | Broker RALPH auto-snooze trigger after N empty cycles; defaults to `0` (disabled), valid range `0`-`100` |
|
|
174
|
+
| `ralphSnoozeDurationMs` | no | Broker RALPH auto-snooze duration in milliseconds; defaults to `1800000` (30 minutes), valid range `60000`-`86400000` |
|
|
175
|
+
| `skinTheme` | no | Pinet presentation skin selected at broker startup/reload (`default`, `foundation`, `cosmere`, or free-form) |
|
|
176
|
+
| `meshSecret` | no | Optional inline Pinet shared secret; overrides `meshSecretPath` and env fallbacks |
|
|
177
|
+
| `meshSecretPath` | no | Optional path to a shared-secret file; broker creates it if missing, followers require an existing file |
|
|
178
|
+
| `suggestedPrompts` | no | Prompts shown when a user opens a new conversation |
|
|
179
|
+
| `security.readOnly` | no | Runtime-block write-capable tools for Slack-triggered turns, including core tools like `bash`, `edit`, and `write` |
|
|
180
|
+
| `security.requireConfirmation` | no | Runtime-require Slack approval before matching tools execute; core tools need a specific Slack thread context |
|
|
181
|
+
| `security.blockedTools` | no | Runtime-block matching tools for Slack-triggered turns, including core tools |
|
|
182
|
+
|
|
183
|
+
## Scope carrier model (compatibility-first)
|
|
184
|
+
|
|
185
|
+
Slack/Pinet now threads a first-class runtime `scope` carrier through shared message contracts and runtime metadata.
|
|
186
|
+
|
|
187
|
+
For this first slice:
|
|
188
|
+
|
|
189
|
+
- **workspace/install scope** is carried as compatibility-first metadata for Slack
|
|
190
|
+
- **instance scope** is also carried as a first-class compatibility carrier
|
|
191
|
+
- today’s single-workspace deployments use one default compatibility scope
|
|
192
|
+
- a missing or empty Slack `teamId` stays **unknown** — the bridge does not invent a fake workspace ID
|
|
193
|
+
- these carriers are metadata only in this slice; enforcement and multi-install behavior land later in `#547` / `#550`
|
|
194
|
+
|
|
195
|
+
## Usage
|
|
196
|
+
|
|
197
|
+
Once configured, Pinet appears in Slack's sidebar. Users open it, type a message, and the pi agent responds.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
User opens Pinet in Slack sidebar
|
|
201
|
+
└─► types a message
|
|
202
|
+
└─► 👀 reaction appears (thinking)
|
|
203
|
+
└─► message queued for pi agent
|
|
204
|
+
└─► agent responds via slack_send
|
|
205
|
+
└─► 👀 removed, reply appears in thread
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Messages queue while the agent is busy. When the agent finishes, it automatically drains the inbox and responds.
|
|
209
|
+
|
|
210
|
+
### Reaction triggers
|
|
211
|
+
|
|
212
|
+
Configured emoji reactions create structured Pinet requests from the reacted-to Slack message. The default set includes `:arrow_up:` / ⬆️ as `steer`, which marks and redelivers the referenced message as steering so the current thread owner sees an unread operator instruction when relevant and safe. The default `:octagonal_sign:` / 🛑 mapping sends an explicit `interrupt` control to the current thread owner; it aborts the active turn when the owner is busy, but does not reload or exit the process. Pinet adds ✅ when it accepts the reaction-triggered request. If it cannot process the reaction at all, it adds ❌; check broker logs for the underlying Slack/API error. When Slack cannot return the reacted message text, Pinet still routes the reaction with channel/thread/message IDs so steering reactions do not fail solely because message lookup was unavailable.
|
|
213
|
+
|
|
214
|
+
### Available tools
|
|
215
|
+
|
|
216
|
+
Slack-bridge uses progressive disclosure to keep the per-turn tool surface
|
|
217
|
+
small:
|
|
218
|
+
|
|
219
|
+
| Tool | Description |
|
|
220
|
+
| ------------- | --------------------------------------------------------------------------- |
|
|
221
|
+
| `slack_inbox` | Hot-path inbox drain for pending incoming Slack messages |
|
|
222
|
+
| `slack_send` | Hot-path reply tool for Slack assistant threads |
|
|
223
|
+
| `slack` | Dispatcher for all non-hot Slack actions; call `action: "help"` for schemas |
|
|
224
|
+
|
|
225
|
+
Cold Slack actions live behind the `slack` dispatcher:
|
|
226
|
+
|
|
227
|
+
| Dispatcher action | Description |
|
|
228
|
+
| ---------------------- | --------------------------------------------------------------------------------- |
|
|
229
|
+
| `react` | Add an emoji reaction to a message |
|
|
230
|
+
| `read` | Read messages from a thread |
|
|
231
|
+
| `upload` | Upload files, snippets, or diffs into Slack |
|
|
232
|
+
| `schedule` | Schedule a message for later delivery |
|
|
233
|
+
| `post_channel` | Post to a channel (by name or ID) |
|
|
234
|
+
| `delete` | Delete a bot-posted message or an entire thread |
|
|
235
|
+
| `read_channel` | Read channel history or a thread in a channel |
|
|
236
|
+
| `create_channel` | Create a new Slack channel |
|
|
237
|
+
| `project_create` | Create a project channel + RFC canvas + bot invite in one call |
|
|
238
|
+
| `pin` | Pin or unpin a message |
|
|
239
|
+
| `bookmark` | Add, list, or remove channel bookmarks |
|
|
240
|
+
| `export` | Export a thread as markdown, plain text, or JSON |
|
|
241
|
+
| `presence` | Check if users are active, away, or in DND |
|
|
242
|
+
| `canvas_comments_read` | Read comments attached to a verified canvas by canvas ID or channel canvas lookup |
|
|
243
|
+
| `canvas_create` | Create a standalone or channel canvas |
|
|
244
|
+
| `canvas_update` | Append, prepend, or replace canvas content |
|
|
245
|
+
| `modal_open` | Open a modal from a trigger interaction |
|
|
246
|
+
| `modal_push` | Push a new step onto a modal stack |
|
|
247
|
+
| `modal_update` | Update an existing open modal |
|
|
248
|
+
| `confirm_action` | Request user confirmation before a dangerous action |
|
|
249
|
+
|
|
250
|
+
Use `slack` with `action: "help"` for the action catalogue, or
|
|
251
|
+
`action: "help", args: { "topic": "canvas_update" }` for a specific JSON
|
|
252
|
+
schema and example invocations. Dispatcher responses use a consistent
|
|
253
|
+
`{ "status", "data", "errors", "warnings" }` envelope. Guardrails match
|
|
254
|
+
cold Slack actions as `slack:<action>` (for example `slack:upload` or
|
|
255
|
+
`slack:canvas_update`); legacy `slack_<action>` patterns are accepted during
|
|
256
|
+
migration.
|
|
257
|
+
|
|
258
|
+
#### Tool and workflow usage notes
|
|
259
|
+
|
|
260
|
+
- **Reply where the work arrived.** Use `slack_send` for assistant-thread
|
|
261
|
+
replies. If a task was delivered in a Slack thread, acknowledge briefly,
|
|
262
|
+
do the work, report blockers immediately, and finish with the outcome. If
|
|
263
|
+
you know only a channel/thread pair, use dispatcher action `post_channel`
|
|
264
|
+
with `channel` and optional `thread_ts` instead.
|
|
265
|
+
- **Channel posting is explicit.** `post_channel` posts to a named channel or
|
|
266
|
+
channel ID. When `channel` is omitted, it first resolves a provided
|
|
267
|
+
`thread_ts` to a tracked thread channel, then falls back to `defaultChannel`
|
|
268
|
+
from settings. `slack_send` is intentionally narrower and resolves the
|
|
269
|
+
current tracked assistant thread/DM context.
|
|
270
|
+
- **Rich messages use Block Kit JSON.** Pass `blocks` directly to
|
|
271
|
+
`slack_send` or `post_channel`; keep `text` as the notification/fallback.
|
|
272
|
+
Block Kit builder tools are not registered by this package. Load the bundled
|
|
273
|
+
`slack-bridge` skill for copyable status-report, button, code, and diff
|
|
274
|
+
templates. The package also bundles `pinet-skin-creator` for safely drafting
|
|
275
|
+
or reviewing curated Pinet skin descriptors and character/status-vocabulary
|
|
276
|
+
pools before changing runtime skin wiring.
|
|
277
|
+
- **Modal helpers are patterns, not hot tools.** Use dispatcher actions
|
|
278
|
+
`modal_open`, `modal_push`, and `modal_update` with Slack view JSON. Open or
|
|
279
|
+
push immediately after receiving a fresh `trigger_id`; Slack trigger IDs
|
|
280
|
+
expire quickly. Include `thread_ts` when submissions should route back to an
|
|
281
|
+
original assistant thread.
|
|
282
|
+
- **Uploads are for bulky artifacts.** Use `upload` for logs, screenshots,
|
|
283
|
+
long diffs, and generated files instead of large inline messages. Inline
|
|
284
|
+
uploads require `filename`; path uploads are guarded and must stay within the
|
|
285
|
+
current working directory or system temp directory.
|
|
286
|
+
- **Upload host egress note.** The second upload leg goes to Slack file upload
|
|
287
|
+
hosts (`files.slack.com`/`uploads.slack.com`) for the raw payload. In
|
|
288
|
+
environments with restricted egress this can fail with `403` (proxy
|
|
289
|
+
allowlist) or DNS errors after `files.getUploadURLExternal`; verify the proxy
|
|
290
|
+
allowlist first, or route through an environment that can reach those hosts.
|
|
291
|
+
- **Upload metadata note.** Slack snippet uploads attempt to use inferred
|
|
292
|
+
`snippet_type` values for inline content and retry with plain upload metadata
|
|
293
|
+
when Slack returns `invalid_arguments`, preserving syntax highlighting for
|
|
294
|
+
supported types while avoiding hard failures on unsupported snippet types.
|
|
295
|
+
- **Canvases are long-lived docs.** `canvas_create` creates standalone or
|
|
296
|
+
channel canvases. If Slack rejects channel tab creation with
|
|
297
|
+
`canvas_tab_creation_failed`, it falls back to a standalone canvas attached to
|
|
298
|
+
the channel, attempts to bookmark the canvas URL, and returns the fallback
|
|
299
|
+
`canvas_id` for future `canvas_update` calls. `canvas_update` can append,
|
|
300
|
+
prepend, replace the whole canvas, or replace a matched section;
|
|
301
|
+
`canvas_comments_read` is read-only and limited to verified canvas targets.
|
|
302
|
+
- **Scheduling, pins, and bookmarks are durable affordances.** Use `schedule`
|
|
303
|
+
for delayed reminders instead of waiting; use `pin` for important thread
|
|
304
|
+
messages; use `bookmark` for persistent channel-header links to repos,
|
|
305
|
+
dashboards, docs, or runbooks.
|
|
306
|
+
- **Presence helps choose timing.** Use `presence` before pinging humans when
|
|
307
|
+
active/away/DND status affects routing or whether to schedule a follow-up.
|
|
308
|
+
- **Destructive actions stay constrained.** `delete` can remove only messages
|
|
309
|
+
posted by the current bot and every delete call requires `confirm: true`.
|
|
310
|
+
Whole-thread deletion additionally requires `thread: true` and succeeds only
|
|
311
|
+
when every message in the target thread belongs to the current bot. Prefer
|
|
312
|
+
asking for explicit approval before destructive cleanup.
|
|
313
|
+
- **Confirm guarded actions in the same thread.** If guardrails require
|
|
314
|
+
confirmation, call `confirm_action` with the target `thread_ts`, exact tool
|
|
315
|
+
name, and the exact action string required by the guarded tool. The safest
|
|
316
|
+
flow is: attempt the guarded call, copy the `requires confirmation for action
|
|
317
|
+
...` string from the error, request confirmation, wait for the user's approval
|
|
318
|
+
via `slack_inbox`, then retry the guarded call unchanged. Batched
|
|
319
|
+
multi-thread Slack turns cannot satisfy a single-thread confirmation.
|
|
320
|
+
- **Reaction and interaction triggers are explicit tasks.** Reaction-triggered
|
|
321
|
+
requests and Block Kit/modal interaction payloads arrive through
|
|
322
|
+
`slack_inbox` with metadata; treat them as user instructions tied to the
|
|
323
|
+
referenced Slack thread or message.
|
|
324
|
+
|
|
325
|
+
#### Common dispatcher examples
|
|
326
|
+
|
|
327
|
+
Reply in the current Slack assistant thread with Block Kit:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"text": "Deploy complete — branch main, checks passed.",
|
|
332
|
+
"blocks": [
|
|
333
|
+
{
|
|
334
|
+
"type": "section",
|
|
335
|
+
"fields": [
|
|
336
|
+
{ "type": "mrkdwn", "text": "*Branch*\n`main`" },
|
|
337
|
+
{ "type": "mrkdwn", "text": "*Checks*\n✅ lint/typecheck/test" }
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Post a channel/thread update through the dispatcher:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"action": "post_channel",
|
|
349
|
+
"args": {
|
|
350
|
+
"channel": "#pinet-logs",
|
|
351
|
+
"thread_ts": "1712345678.000100",
|
|
352
|
+
"text": "PR #123 is ready for review."
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Upload a generated diff snippet:
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"action": "upload",
|
|
362
|
+
"args": {
|
|
363
|
+
"content": "diff --git a/README.md b/README.md\n...",
|
|
364
|
+
"filename": "docs.diff",
|
|
365
|
+
"filetype": "diff",
|
|
366
|
+
"title": "Docs changes",
|
|
367
|
+
"thread_ts": "1712345678.000100"
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Request confirmation before a guarded destructive action after copying the
|
|
373
|
+
exact action string from the guardrail error:
|
|
374
|
+
|
|
375
|
+
```json
|
|
376
|
+
{
|
|
377
|
+
"action": "confirm_action",
|
|
378
|
+
"args": {
|
|
379
|
+
"thread_ts": "1712345678.000100",
|
|
380
|
+
"tool": "slack:delete",
|
|
381
|
+
"action": "channel=#pinet-logs | thread_ts=1712345678.000100 | ts=1712345678.000200 | thread=false"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### Canvas comment inspection
|
|
387
|
+
|
|
388
|
+
The `canvas_comments_read` dispatcher action is intentionally narrow:
|
|
389
|
+
|
|
390
|
+
- it validates the target with `canvases.sections.lookup` before reading comment pages via `files.info`
|
|
391
|
+
- it needs `files:read` because Slack exposes canvas comments through the file API surface
|
|
392
|
+
- it will **not** inspect generic Slack files, non-canvas file comments, or full canvas body/history
|
|
393
|
+
|
|
394
|
+
### Slash commands
|
|
395
|
+
|
|
396
|
+
| Command | Description |
|
|
397
|
+
| ----------------- | ---------------------------------------------------------- |
|
|
398
|
+
| `/pinet <action>` | Unified Pinet command surface; run `/pinet help` for usage |
|
|
399
|
+
| `/pinet status` | Show connection status, threads, and agent identity |
|
|
400
|
+
| `/pinet rename` | Change the agent's display name |
|
|
401
|
+
| `/pinet logs` | Show recent broker activity log entries |
|
|
402
|
+
|
|
403
|
+
## Runtime modes
|
|
404
|
+
|
|
405
|
+
`slack-bridge` now treats runtime mode as an explicit concept:
|
|
406
|
+
|
|
407
|
+
| Mode | Meaning |
|
|
408
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
409
|
+
| `off` | Slack bridge is loaded, but **no Slack Socket Mode ingress** and no coordination runtime are started. |
|
|
410
|
+
| `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. |
|
|
411
|
+
| `broker` | The session runs the broker coordination runtime. |
|
|
412
|
+
| `follower` | The session connects to an existing broker as a worker runtime. |
|
|
413
|
+
|
|
414
|
+
Startup selection:
|
|
415
|
+
|
|
416
|
+
- `runtimeMode` is the explicit startup selector.
|
|
417
|
+
- `autoConnect` is a legacy compatibility alias for `runtimeMode: "single"`.
|
|
418
|
+
- `autoFollow` is a legacy compatibility alias for `runtimeMode: "follower"` when a broker socket is available.
|
|
419
|
+
- explicit `runtimeMode` wins over the legacy flags.
|
|
420
|
+
- `/pinet start` and `/pinet follow` still switch the live session into broker/follower runtimes explicitly.
|
|
421
|
+
|
|
422
|
+
## Scope carriers (compatibility-first)
|
|
423
|
+
|
|
424
|
+
`slack-bridge` now emits first-class runtime scope carriers in shared transport contracts and agent runtime metadata.
|
|
425
|
+
|
|
426
|
+
- `scope.workspace` models the current Slack install/workspace scope.
|
|
427
|
+
- `scope.instance` models the current broker/runtime instance scope.
|
|
428
|
+
- in the first slice, both stay **compatibility-first**: today’s singleton runtime gets one default compatibility scope
|
|
429
|
+
- if Slack omits `team_id`, the carrier keeps the workspace id **unknown** instead of inventing a fake one
|
|
430
|
+
- this slice is metadata/plumbing only: it does **not** change routing, enforcement, or multi-install orchestration yet
|
|
431
|
+
|
|
432
|
+
## Pinet (Multi-Agent Mode)
|
|
433
|
+
|
|
434
|
+
Pinet supports a broker/follower architecture for coordinating multiple pi agents over Slack.
|
|
435
|
+
|
|
436
|
+
### Runtime composition boundary
|
|
437
|
+
|
|
438
|
+
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.
|
|
439
|
+
|
|
440
|
+
### Quick start
|
|
441
|
+
|
|
442
|
+
**Broker** (one per mesh — coordinates routing and health):
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
/pinet start
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Follower** (workers that connect to the broker):
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
/pinet follow
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Or set `"runtimeMode": "follower"` in settings (or the legacy `"autoFollow": true`) to auto-connect when a broker is running.
|
|
455
|
+
|
|
456
|
+
### Broker prompt MD
|
|
457
|
+
|
|
458
|
+
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:
|
|
459
|
+
|
|
460
|
+
1. workspace override: `.pi/slack-bridge/tmux.md` under the current repo/worktree root
|
|
461
|
+
2. user-local override: `~/.pi/agent/slack-bridge/tmux.md`
|
|
462
|
+
3. packaged default: `dist/prompts/broker/tmux.md`
|
|
463
|
+
|
|
464
|
+
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.
|
|
465
|
+
|
|
466
|
+
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.
|
|
467
|
+
|
|
468
|
+
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.
|
|
469
|
+
|
|
470
|
+
### Multi-agent tools
|
|
471
|
+
|
|
472
|
+
| Tool | Description |
|
|
473
|
+
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
474
|
+
| `pinet` | Pinet dispatcher with token-efficient `action`-based routing (`help`, `send`, `read`, `free`, `snooze`, `schedule`, `agents`, `lanes`, `ports`, `reload`, `exit`) |
|
|
475
|
+
|
|
476
|
+
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=lanes`, `pinet action=ports`, `pinet action=reload`, and `pinet action=exit`. Use slash commands for UI lifecycle transitions: `/pinet start`, `/pinet follow`, and `/pinet unfollow`. 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.
|
|
477
|
+
|
|
478
|
+
Dispatcher content defaults to terse CLI-style confirmations/summaries for noisy reads, sends, and agent lists. In default CLI mode, bulky read/agent payloads are also compacted in `data.details` so tool renderers do not surface full message bodies or agent metadata by accident. Pass `args.format="json"` (or `args.f` / `args["-f"]`) for the dispatcher envelope in content with full structured `data.details`, or `args.full=true` / `args["--full"]=true` for verbose text with full structured `data.details`.
|
|
479
|
+
|
|
480
|
+
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.
|
|
481
|
+
|
|
482
|
+
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.
|
|
483
|
+
|
|
484
|
+
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.
|
|
485
|
+
|
|
486
|
+
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.
|
|
487
|
+
|
|
488
|
+
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.
|
|
489
|
+
|
|
490
|
+
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.
|
|
491
|
+
|
|
492
|
+
### Pinet command surface
|
|
493
|
+
|
|
494
|
+
Use `/pinet <action> [args]` for mesh lifecycle and broker operations.
|
|
495
|
+
|
|
496
|
+
| Command | Description |
|
|
497
|
+
| ------------------------------------- | ----------------------------------------------------------------------------- |
|
|
498
|
+
| `/pinet start` | Start as the mesh broker |
|
|
499
|
+
| `/pinet follow` | Connect as a follower worker |
|
|
500
|
+
| `/pinet unfollow` | Disconnect from the broker |
|
|
501
|
+
| `/pinet reload <agent>` | Ask another agent to reload |
|
|
502
|
+
| `/pinet exit <agent>` | Ask another agent to exit |
|
|
503
|
+
| `/pinet free` | Mark this agent as idle |
|
|
504
|
+
| `/pinet snooze [duration/off/status]` | Quiet empty RALPH cycles while preserving human-triggered wake/route behavior |
|
|
505
|
+
|
|
506
|
+
### Pinet skins
|
|
507
|
+
|
|
508
|
+
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).
|
|
509
|
+
|
|
510
|
+
Built-in skins:
|
|
511
|
+
|
|
512
|
+
- `default` / `classic` — preserves the current whimsical animal names, animal emoji palette, and playful-but-focused persona.
|
|
513
|
+
- `foundation` / `foundation/space` / `space` — JSON descriptor with curated institutional sci-fi characters, full-name aliases, and archive, relay, frontier, and crisis-room flavor.
|
|
514
|
+
- `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.
|
|
515
|
+
|
|
516
|
+
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.
|
|
517
|
+
|
|
518
|
+
### How it works
|
|
519
|
+
|
|
520
|
+
- 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.
|
|
521
|
+
- **Followers** connect to the broker over a local Unix socket, poll for work, and report results
|
|
522
|
+
- Agents can optionally authenticate using a shared local secret (`meshSecret` or `meshSecretPath`); when both are unset, mesh auth is disabled
|
|
523
|
+
- Thread ownership is first-responder-wins — the first agent to reply claims the thread
|
|
524
|
+
|
|
525
|
+
## Security
|
|
526
|
+
|
|
527
|
+
- **User access**: Slack access is default-deny. Set `allowedUsers` for a narrow allowlist, or `allowAllWorkspaceUsers: true` only if you explicitly want workspace-wide access
|
|
528
|
+
- **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`
|
|
529
|
+
- **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.
|
|
530
|
+
- **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.
|
|
531
|
+
|
|
532
|
+
Find Slack user IDs: click a user's profile → **More** → **Copy member ID**.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Development
|
|
537
|
+
|
|
538
|
+
### Build
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
pnpm run build
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Lint / Typecheck / Test
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
pnpm lint
|
|
548
|
+
pnpm typecheck
|
|
549
|
+
pnpm test
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Deploy manifest to Slack
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
pnpm deploy:slack
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Requires `appId` and `appConfigToken` in settings (or `SLACK_APP_ID` / `SLACK_APP_CONFIG_TOKEN` env vars).
|
|
559
|
+
|
|
560
|
+
### Architecture
|
|
561
|
+
|
|
562
|
+
- **Socket Mode** — outbound WebSocket, no public URL needed
|
|
563
|
+
- **Zero runtime npm deps** — native `fetch`, `WebSocket`, `node:sqlite` (Node 22+)
|
|
564
|
+
- **Hybrid inbox** — queue when busy, auto-drain when idle
|
|
565
|
+
- **Reactions** — 👀 as a lightweight "thinking" indicator
|
|
566
|
+
- **Thread persistence** — thread state survives `/reload`
|
|
567
|
+
|
|
568
|
+
## License
|
|
569
|
+
|
|
570
|
+
MIT. See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SlackResult } from "./slack-api.js";
|
|
2
|
+
export type ActivityLogLevel = "errors" | "actions" | "verbose";
|
|
3
|
+
export type ActivityLogTone = "info" | "success" | "warning" | "error";
|
|
4
|
+
export interface ActivityLogField {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number | boolean | null | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface ActivityLogEntry {
|
|
9
|
+
kind: string;
|
|
10
|
+
level: ActivityLogLevel;
|
|
11
|
+
title: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
details?: string[];
|
|
14
|
+
fields?: ActivityLogField[];
|
|
15
|
+
tone?: ActivityLogTone;
|
|
16
|
+
timestamp?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LoggedActivityLogEntry extends ActivityLogEntry {
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SlackActivityLoggerDeps {
|
|
22
|
+
getBotToken: () => string | undefined;
|
|
23
|
+
getLogChannel: () => string | undefined;
|
|
24
|
+
getLogLevel: () => string | undefined;
|
|
25
|
+
getAgentName: () => string;
|
|
26
|
+
getAgentEmoji: () => string;
|
|
27
|
+
resolveChannel: (nameOrId: string) => Promise<string>;
|
|
28
|
+
slack: (method: string, token: string, body?: Record<string, unknown>) => Promise<SlackResult>;
|
|
29
|
+
onError?: (error: unknown) => void;
|
|
30
|
+
now?: () => Date;
|
|
31
|
+
maxRecentEntries?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function normalizeActivityLogLevel(value: string | undefined): ActivityLogLevel;
|
|
34
|
+
export declare function shouldLogActivity(configuredLevel: ActivityLogLevel, eventLevel: ActivityLogLevel): boolean;
|
|
35
|
+
export declare function redactSensitiveText(value: string): string;
|
|
36
|
+
export declare function buildActivityLogText(agentName: string, agentEmoji: string, entry: LoggedActivityLogEntry): string;
|
|
37
|
+
export declare function buildActivityLogBlocks(agentName: string, agentEmoji: string, entry: LoggedActivityLogEntry): Record<string, unknown>[];
|
|
38
|
+
export declare function buildActivityLogThreadHeader(agentName: string, agentEmoji: string, dateKey: string): {
|
|
39
|
+
text: string;
|
|
40
|
+
blocks: Record<string, unknown>[];
|
|
41
|
+
};
|
|
42
|
+
export declare function formatRecentActivityLogEntries(entries: ReadonlyArray<LoggedActivityLogEntry>): string;
|
|
43
|
+
export declare class SlackActivityLogger {
|
|
44
|
+
private readonly deps;
|
|
45
|
+
private readonly queue;
|
|
46
|
+
private readonly recent;
|
|
47
|
+
private readonly maxRecentEntries;
|
|
48
|
+
private flushTimer;
|
|
49
|
+
private flushRunning;
|
|
50
|
+
private resolvedChannelCache;
|
|
51
|
+
private dailyThreadCache;
|
|
52
|
+
constructor(deps: SlackActivityLoggerDeps);
|
|
53
|
+
log(entry: ActivityLogEntry): void;
|
|
54
|
+
getRecentEntries(limit?: number): LoggedActivityLogEntry[];
|
|
55
|
+
clearPending(): void;
|
|
56
|
+
private getNow;
|
|
57
|
+
private scheduleFlush;
|
|
58
|
+
private flushNext;
|
|
59
|
+
private resolveLogChannel;
|
|
60
|
+
private ensureDailyThread;
|
|
61
|
+
private postEntry;
|
|
62
|
+
}
|