@vellumai/assistant 0.4.29 → 0.4.30

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 (174) hide show
  1. package/ARCHITECTURE.md +39 -37
  2. package/README.md +5 -6
  3. package/docs/runbook-trusted-contacts.md +79 -43
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
  6. package/scripts/test.sh +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
  8. package/src/__tests__/actor-token-service.test.ts +4 -3
  9. package/src/__tests__/app-executors.test.ts +7 -17
  10. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
  11. package/src/__tests__/browser-skill-endstate.test.ts +10 -1
  12. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
  13. package/src/__tests__/channel-approval-routes.test.ts +44 -44
  14. package/src/__tests__/channel-approval.test.ts +8 -0
  15. package/src/__tests__/channel-approvals.test.ts +39 -1
  16. package/src/__tests__/channel-guardian.test.ts +15 -5
  17. package/src/__tests__/channel-reply-delivery.test.ts +31 -0
  18. package/src/__tests__/commit-message-enrichment-service.test.ts +4 -0
  19. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
  20. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  21. package/src/__tests__/gemini-image-service.test.ts +2 -2
  22. package/src/__tests__/guardian-grant-minting.test.ts +6 -6
  23. package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
  24. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
  25. package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
  26. package/src/__tests__/integrations-cli.test.ts +3 -27
  27. package/src/__tests__/intent-routing.test.ts +3 -0
  28. package/src/__tests__/invite-redemption-service.test.ts +1 -1
  29. package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
  30. package/src/__tests__/ipc-snapshot.test.ts +4 -31
  31. package/src/__tests__/nl-approval-parser.test.ts +305 -0
  32. package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
  33. package/src/__tests__/provider-error-scenarios.test.ts +68 -0
  34. package/src/__tests__/relay-server.test.ts +1 -1
  35. package/src/__tests__/retry-after-extraction.test.ts +111 -0
  36. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
  37. package/src/__tests__/session-media-retry.test.ts +147 -0
  38. package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
  39. package/src/__tests__/skill-feature-flags.test.ts +18 -12
  40. package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
  41. package/src/__tests__/slack-block-formatting.test.ts +100 -0
  42. package/src/__tests__/slack-inbound-verification.test.ts +346 -0
  43. package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
  44. package/src/__tests__/slack-skill.test.ts +3 -2
  45. package/src/__tests__/starter-task-flow.test.ts +0 -1
  46. package/src/__tests__/trusted-contact-verification.test.ts +3 -1
  47. package/src/__tests__/voice-invite-redemption.test.ts +1 -1
  48. package/src/amazon/client.ts +7 -24
  49. package/src/calls/relay-server.ts +39 -11
  50. package/src/channels/config.ts +1 -1
  51. package/src/cli/integrations.ts +10 -66
  52. package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
  53. package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
  54. package/src/config/bundled-skills/browser/TOOLS.json +59 -2
  55. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
  56. package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
  57. package/src/config/bundled-skills/contacts/SKILL.md +42 -35
  58. package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
  59. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
  60. package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
  61. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
  62. package/src/config/bundled-skills/document/TOOLS.json +8 -0
  63. package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
  64. package/src/config/bundled-skills/followups/TOOLS.json +12 -0
  65. package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
  66. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
  67. package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
  68. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
  69. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
  70. package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
  71. package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
  72. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
  73. package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
  74. package/src/config/bundled-skills/notifications/SKILL.md +3 -2
  75. package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
  76. package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
  77. package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
  78. package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
  79. package/src/config/bundled-skills/schedule/SKILL.md +33 -15
  80. package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
  81. package/src/config/bundled-skills/slack/SKILL.md +30 -1
  82. package/src/config/bundled-skills/slack/TOOLS.json +89 -2
  83. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
  84. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
  85. package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
  86. package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
  87. package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
  88. package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
  89. package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
  90. package/src/config/bundled-skills/weather/TOOLS.json +4 -0
  91. package/src/config/bundled-tool-registry.ts +2 -0
  92. package/src/config/channel-permission-profiles.ts +155 -0
  93. package/src/config/env.ts +4 -1
  94. package/src/contacts/contact-store.ts +195 -4
  95. package/src/contacts/types.ts +26 -0
  96. package/src/daemon/assistant-attachments.ts +23 -3
  97. package/src/daemon/guardian-verification-intent.ts +7 -4
  98. package/src/daemon/handlers/apps.ts +1 -2
  99. package/src/daemon/handlers/config-inbox.ts +16 -134
  100. package/src/daemon/handlers/guardian-actions.ts +20 -87
  101. package/src/daemon/handlers/sessions.ts +0 -1
  102. package/src/daemon/ipc-contract/apps.ts +0 -1
  103. package/src/daemon/ipc-contract/inbox.ts +7 -66
  104. package/src/daemon/ipc-contract/sessions.ts +1 -0
  105. package/src/daemon/ipc-contract/surfaces.ts +0 -1
  106. package/src/daemon/ipc-contract-inventory.json +2 -4
  107. package/src/daemon/lifecycle.ts +14 -2
  108. package/src/daemon/session-agent-loop-handlers.ts +9 -0
  109. package/src/daemon/session-agent-loop.ts +1 -0
  110. package/src/daemon/session-attachments.ts +5 -1
  111. package/src/daemon/session-error.ts +18 -0
  112. package/src/daemon/session-lifecycle.ts +4 -5
  113. package/src/daemon/session-media-retry.ts +15 -1
  114. package/src/daemon/session-surfaces.ts +0 -1
  115. package/src/daemon/session-tool-setup.ts +7 -4
  116. package/src/events/domain-events.ts +2 -1
  117. package/src/home-base/prebuilt/seed.ts +0 -1
  118. package/src/influencer/client.ts +7 -24
  119. package/src/media/gemini-image-service.ts +48 -3
  120. package/src/memory/app-store.ts +0 -4
  121. package/src/memory/conversation-attention-store.ts +3 -1
  122. package/src/memory/db-init.ts +4 -0
  123. package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
  124. package/src/memory/migrations/index.ts +1 -0
  125. package/src/memory/schema.ts +12 -0
  126. package/src/memory/slack-thread-store.ts +187 -0
  127. package/src/messaging/providers/slack/client.ts +84 -26
  128. package/src/messaging/providers/slack/types.ts +4 -0
  129. package/src/notifications/adapters/slack.ts +90 -0
  130. package/src/notifications/destination-resolver.ts +42 -1
  131. package/src/notifications/emit-signal.ts +17 -1
  132. package/src/oauth/provider-profiles.ts +22 -0
  133. package/src/providers/anthropic/client.ts +3 -0
  134. package/src/providers/openai/client.ts +3 -0
  135. package/src/providers/retry.ts +9 -1
  136. package/src/runtime/actor-trust-resolver.ts +8 -0
  137. package/src/runtime/auth/require-bound-guardian.ts +44 -0
  138. package/src/runtime/auth/route-policy.ts +4 -8
  139. package/src/runtime/channel-approval-types.ts +18 -0
  140. package/src/runtime/channel-approvals.ts +8 -0
  141. package/src/runtime/channel-invite-transport.ts +1 -1
  142. package/src/runtime/channel-reply-delivery.ts +62 -3
  143. package/src/runtime/gateway-client.ts +36 -2
  144. package/src/runtime/gateway-internal-client.ts +86 -0
  145. package/src/runtime/guardian-action-service.ts +127 -0
  146. package/src/runtime/guardian-verification-templates.ts +16 -1
  147. package/src/runtime/http-server.ts +20 -49
  148. package/src/runtime/invite-redemption-service.ts +1 -1
  149. package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
  150. package/src/runtime/nl-approval-parser.ts +138 -0
  151. package/src/runtime/routes/approval-routes.ts +1 -40
  152. package/src/runtime/routes/channel-route-shared.ts +35 -1
  153. package/src/runtime/routes/contact-routes.ts +196 -28
  154. package/src/runtime/routes/guardian-action-routes.ts +19 -111
  155. package/src/runtime/routes/guardian-approval-interception.ts +76 -0
  156. package/src/runtime/routes/inbound-message-handler.ts +40 -12
  157. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
  158. package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
  159. package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
  160. package/src/runtime/slack-block-formatting.ts +176 -0
  161. package/src/schedule/scheduler.ts +11 -2
  162. package/src/tools/apps/executors.ts +16 -15
  163. package/src/tools/calls/call-end.ts +1 -1
  164. package/src/tools/computer-use/definitions.ts +16 -0
  165. package/src/tools/credentials/vault.ts +86 -2
  166. package/src/tools/network/script-proxy/session-manager.ts +28 -3
  167. package/src/tools/permission-checker.ts +18 -0
  168. package/src/tools/terminal/shell.ts +15 -5
  169. package/src/tools/tool-approval-handler.ts +48 -4
  170. package/src/tools/types.ts +38 -1
  171. package/src/util/errors.ts +5 -1
  172. package/src/util/retry.ts +21 -0
  173. package/src/watcher/providers/slack.ts +33 -3
  174. /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "task_save",
6
- "description": "Save the current conversation as a reusable task template (definition). This is NOT for adding items to the user's task queue use task_list_add for that. task_save extracts the conversation pattern into a reusable definition with placeholders that can be run later with different inputs.",
6
+ "description": "Save the current conversation as a reusable task template (definition). This is NOT for adding items to the user's task queue \u2014 use task_list_add for that. task_save extracts the conversation pattern into a reusable definition with placeholders that can be run later with different inputs.",
7
7
  "category": "tasks",
8
8
  "risk": "low",
9
9
  "input_schema": {
@@ -16,6 +16,10 @@
16
16
  "title": {
17
17
  "type": "string",
18
18
  "description": "Optional override for the auto-generated task title"
19
+ },
20
+ "reason": {
21
+ "type": "string",
22
+ "description": "Brief non-technical explanation of why this tool is being called"
19
23
  }
20
24
  },
21
25
  "required": []
@@ -42,7 +46,13 @@
42
46
  "inputs": {
43
47
  "type": "object",
44
48
  "description": "Values for template placeholders (e.g. {\"file_path\": \"/tmp/foo.txt\", \"url\": \"https://example.com\"})",
45
- "additionalProperties": { "type": "string" }
49
+ "additionalProperties": {
50
+ "type": "string"
51
+ }
52
+ },
53
+ "reason": {
54
+ "type": "string",
55
+ "description": "Brief non-technical explanation of why this tool is being called"
46
56
  }
47
57
  }
48
58
  },
@@ -56,7 +66,12 @@
56
66
  "risk": "low",
57
67
  "input_schema": {
58
68
  "type": "object",
59
- "properties": {}
69
+ "properties": {
70
+ "reason": {
71
+ "type": "string",
72
+ "description": "Brief non-technical explanation of why this tool is being called"
73
+ }
74
+ }
60
75
  },
61
76
  "executor": "tools/task-list.ts",
62
77
  "execution_target": "host"
@@ -71,8 +86,14 @@
71
86
  "properties": {
72
87
  "task_ids": {
73
88
  "type": "array",
74
- "items": { "type": "string" },
89
+ "items": {
90
+ "type": "string"
91
+ },
75
92
  "description": "One or more task IDs to delete."
93
+ },
94
+ "reason": {
95
+ "type": "string",
96
+ "description": "Brief non-technical explanation of why this tool is being called"
76
97
  }
77
98
  },
78
99
  "required": ["task_ids"]
@@ -90,10 +111,21 @@
90
111
  "properties": {
91
112
  "status": {
92
113
  "oneOf": [
93
- { "type": "string" },
94
- { "type": "array", "items": { "type": "string" } }
114
+ {
115
+ "type": "string"
116
+ },
117
+ {
118
+ "type": "array",
119
+ "items": {
120
+ "type": "string"
121
+ }
122
+ }
95
123
  ],
96
124
  "description": "Optional status filter. A single status string (e.g. \"queued\") or an array of statuses to include."
125
+ },
126
+ "reason": {
127
+ "type": "string",
128
+ "description": "Brief non-technical explanation of why this tool is being called"
97
129
  }
98
130
  }
99
131
  },
@@ -102,7 +134,7 @@
102
134
  },
103
135
  {
104
136
  "name": "task_list_add",
105
- "description": "Add a task to the user's Task Queue. Use this when the user says \"add to my tasks\", \"add to my queue\", \"put this on my task list\", \"track this task\", or any variation of adding a one-off item they want to remember or work on. You can provide just a title for ad-hoc items, or reference an existing task definition by name or ID. Do NOT use schedule_create or reminder for simple \"add to tasks\" requests those are for timed/recurring automation only.",
137
+ "description": "Add a task to the user's Task Queue. Use this when the user says \"add to my tasks\", \"add to my queue\", \"put this on my task list\", \"track this task\", or any variation of adding a one-off item they want to remember or work on. You can provide just a title for ad-hoc items, or reference an existing task definition by name or ID. Do NOT use schedule_create or reminder for simple \"add to tasks\" requests \u2014 those are for timed/recurring automation only.",
106
138
  "category": "tasks",
107
139
  "risk": "low",
108
140
  "input_schema": {
@@ -143,8 +175,14 @@
143
175
  },
144
176
  "required_tools": {
145
177
  "type": "array",
146
- "items": { "type": "string" },
147
- "description": "Tools the task will need at execution time. Always specify this — think about what tools are needed to accomplish the task (e.g. [\"host_bash\"] for shell commands, [\"host_file_read\", \"host_file_write\"] for file operations, [\"web_search\"] for web lookups). The user must approve these before the task runs. Pass [] if no tools are needed."
178
+ "items": {
179
+ "type": "string"
180
+ },
181
+ "description": "Tools the task will need at execution time. Always specify this \u2014 think about what tools are needed to accomplish the task (e.g. [\"host_bash\"] for shell commands, [\"host_file_read\", \"host_file_write\"] for file operations, [\"web_search\"] for web lookups). The user must approve these before the task runs. Pass [] if no tools are needed."
182
+ },
183
+ "reason": {
184
+ "type": "string",
185
+ "description": "Brief non-technical explanation of why this tool is being called"
148
186
  }
149
187
  }
150
188
  },
@@ -185,7 +223,15 @@
185
223
  },
186
224
  "status": {
187
225
  "type": "string",
188
- "enum": ["queued", "running", "awaiting_review", "done", "failed", "cancelled", "archived"],
226
+ "enum": [
227
+ "queued",
228
+ "running",
229
+ "awaiting_review",
230
+ "done",
231
+ "failed",
232
+ "cancelled",
233
+ "archived"
234
+ ],
189
235
  "description": "New status for the work item. To mark a task as done, it must currently be in 'awaiting_review' status."
190
236
  },
191
237
  "sort_index": {
@@ -194,16 +240,28 @@
194
240
  },
195
241
  "filter_priority_tier": {
196
242
  "type": "number",
197
- "description": "Disambiguation filter: narrow by current priority tier (0=high, 1=medium, 2=low) when multiple items share the same title/task_id. This identifies WHICH item to update it is NOT the new priority value."
243
+ "description": "Disambiguation filter: narrow by current priority tier (0=high, 1=medium, 2=low) when multiple items share the same title/task_id. This identifies WHICH item to update \u2014 it is NOT the new priority value."
198
244
  },
199
245
  "filter_status": {
200
246
  "type": "string",
201
- "enum": ["queued", "running", "awaiting_review", "failed", "cancelled", "done", "archived"],
247
+ "enum": [
248
+ "queued",
249
+ "running",
250
+ "awaiting_review",
251
+ "failed",
252
+ "cancelled",
253
+ "done",
254
+ "archived"
255
+ ],
202
256
  "description": "Disambiguation filter: narrow by current status when multiple items share the same title/task_id."
203
257
  },
204
258
  "created_order": {
205
259
  "type": "number",
206
260
  "description": "Disambiguation filter: pick the Nth oldest match (1 = oldest, 2 = second oldest, etc.) when multiple items share the same title/task_id."
261
+ },
262
+ "reason": {
263
+ "type": "string",
264
+ "description": "Brief non-technical explanation of why this tool is being called"
207
265
  }
208
266
  }
209
267
  },
@@ -240,12 +298,22 @@
240
298
  },
241
299
  "status": {
242
300
  "type": "string",
243
- "enum": ["queued", "running", "awaiting_review", "failed", "cancelled"],
301
+ "enum": [
302
+ "queued",
303
+ "running",
304
+ "awaiting_review",
305
+ "failed",
306
+ "cancelled"
307
+ ],
244
308
  "description": "Disambiguator: filter by work item status"
245
309
  },
246
310
  "created_order": {
247
311
  "type": "number",
248
312
  "description": "Disambiguator: 1-indexed creation order among matches (1 = oldest, 2 = second oldest, etc.)"
313
+ },
314
+ "reason": {
315
+ "type": "string",
316
+ "description": "Brief non-technical explanation of why this tool is being called"
249
317
  }
250
318
  }
251
319
  },
@@ -254,7 +322,7 @@
254
322
  },
255
323
  {
256
324
  "name": "task_queue_run",
257
- "description": "Run a task from the Task Queue in the background. Use this when the user says \"run this task\", \"execute this task\", \"start this task\", or wants to kick off a queued work item. The task runs asynchronously the user can continue chatting while it executes. Required tool permissions are auto-approved since the user is explicitly requesting execution.",
325
+ "description": "Run a task from the Task Queue in the background. Use this when the user says \"run this task\", \"execute this task\", \"start this task\", or wants to kick off a queued work item. The task runs asynchronously \u2014 the user can continue chatting while it executes. Required tool permissions are auto-approved since the user is explicitly requesting execution.",
258
326
  "category": "tasks",
259
327
  "risk": "medium",
260
328
  "input_schema": {
@@ -271,6 +339,10 @@
271
339
  "title": {
272
340
  "type": "string",
273
341
  "description": "Work item title to search for (case-insensitive substring match)"
342
+ },
343
+ "reason": {
344
+ "type": "string",
345
+ "description": "Brief non-technical explanation of why this tool is being called"
274
346
  }
275
347
  }
276
348
  },
@@ -21,6 +21,10 @@
21
21
  "type": "string",
22
22
  "enum": ["api", "local"],
23
23
  "description": "Transcription backend: 'api' for OpenAI Whisper API (cloud), 'local' for whisper.cpp (on-device)"
24
+ },
25
+ "reason": {
26
+ "type": "string",
27
+ "description": "Brief non-technical explanation of why this tool is being called"
24
28
  }
25
29
  },
26
30
  "required": ["mode"]
@@ -32,6 +32,10 @@
32
32
  "config": {
33
33
  "type": "object",
34
34
  "description": "Provider-specific configuration (e.g. filter criteria)"
35
+ },
36
+ "reason": {
37
+ "type": "string",
38
+ "description": "Brief non-technical explanation of why this tool is being called"
35
39
  }
36
40
  },
37
41
  "required": ["name", "provider", "action_prompt"]
@@ -54,6 +58,10 @@
54
58
  "enabled_only": {
55
59
  "type": "boolean",
56
60
  "description": "When true, only show enabled watchers. Defaults to false."
61
+ },
62
+ "reason": {
63
+ "type": "string",
64
+ "description": "Brief non-technical explanation of why this tool is being called"
57
65
  }
58
66
  },
59
67
  "required": []
@@ -92,6 +100,10 @@
92
100
  "config": {
93
101
  "type": "object",
94
102
  "description": "New provider-specific configuration"
103
+ },
104
+ "reason": {
105
+ "type": "string",
106
+ "description": "Brief non-technical explanation of why this tool is being called"
95
107
  }
96
108
  },
97
109
  "required": ["watcher_id"]
@@ -110,6 +122,10 @@
110
122
  "watcher_id": {
111
123
  "type": "string",
112
124
  "description": "The ID of the watcher to delete"
125
+ },
126
+ "reason": {
127
+ "type": "string",
128
+ "description": "Brief non-technical explanation of why this tool is being called"
113
129
  }
114
130
  },
115
131
  "required": ["watcher_id"]
@@ -136,6 +152,10 @@
136
152
  "limit": {
137
153
  "type": "number",
138
154
  "description": "Maximum number of events to return. Defaults to 50."
155
+ },
156
+ "reason": {
157
+ "type": "string",
158
+ "description": "Brief non-technical explanation of why this tool is being called"
139
159
  }
140
160
  },
141
161
  "required": []
@@ -21,6 +21,10 @@
21
21
  "days": {
22
22
  "type": "number",
23
23
  "description": "Number of forecast days to return (1-16, default: 10)"
24
+ },
25
+ "reason": {
26
+ "type": "string",
27
+ "description": "Brief non-technical explanation of why this tool is being called"
24
28
  }
25
29
  },
26
30
  "required": ["location"]
@@ -146,6 +146,7 @@ import * as scheduleUpdate from "./bundled-skills/schedule/tools/schedule-update
146
146
  // ── slack ────────────────────────────────────────────────────────────────────
147
147
  import * as slackAddReaction from "./bundled-skills/slack/tools/slack-add-reaction.js";
148
148
  import * as slackChannelDetails from "./bundled-skills/slack/tools/slack-channel-details.js";
149
+ import * as slackChannelPermissions from "./bundled-skills/slack/tools/slack-channel-permissions.js";
149
150
  import * as slackConfigureChannels from "./bundled-skills/slack/tools/slack-configure-channels.js";
150
151
  import * as slackDeleteMessage from "./bundled-skills/slack/tools/slack-delete-message.js";
151
152
  import * as slackLeaveChannel from "./bundled-skills/slack/tools/slack-leave-channel.js";
@@ -345,6 +346,7 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
345
346
  ["slack:tools/slack-add-reaction.ts", slackAddReaction],
346
347
  ["slack:tools/slack-delete-message.ts", slackDeleteMessage],
347
348
  ["slack:tools/slack-leave-channel.ts", slackLeaveChannel],
349
+ ["slack:tools/slack-channel-permissions.ts", slackChannelPermissions],
348
350
 
349
351
  // subagent
350
352
  ["subagent:tools/subagent-spawn.ts", subagentSpawn],
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Channel-scoped permission profiles.
3
+ *
4
+ * Maps Slack channel IDs to permission/trust overrides. When processing
5
+ * an inbound message from a channel, the permission profile is looked up
6
+ * to determine which tools are available, what trust level applies, etc.
7
+ *
8
+ * Permission profiles are stored in the Slack skill config section:
9
+ * skills.entries.slack.config.channelPermissions
10
+ *
11
+ * Each entry maps a channel ID to a ChannelPermissionProfile.
12
+ */
13
+
14
+ import { getConfig, saveConfig } from "./loader.js";
15
+
16
+ // ── Types ───────────────────────────────────────────────────────────
17
+
18
+ export interface ChannelPermissionProfile {
19
+ /** Human-readable label for this channel's permission set. */
20
+ label?: string;
21
+ /** Tool categories allowed in this channel. When set, only tools in these
22
+ * categories can be invoked. Empty array means no tool restrictions. */
23
+ allowedToolCategories?: string[];
24
+ /** Specific tool names blocked in this channel, regardless of category. */
25
+ blockedTools?: string[];
26
+ /** Trust level override for messages from this channel.
27
+ * "restricted" limits tool access; "standard" uses defaults. */
28
+ trustLevel?: "restricted" | "standard";
29
+ }
30
+
31
+ export type ChannelPermissionMap = Record<string, ChannelPermissionProfile>;
32
+
33
+ // ── Config accessors ────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Get all channel permission mappings from config.
37
+ */
38
+ export function getChannelPermissions(): ChannelPermissionMap {
39
+ const config = getConfig();
40
+ const perms = config.skills?.entries?.slack?.config?.channelPermissions;
41
+ if (perms && typeof perms === "object" && !Array.isArray(perms)) {
42
+ return perms as ChannelPermissionMap;
43
+ }
44
+ return {};
45
+ }
46
+
47
+ /**
48
+ * Get the permission profile for a specific channel.
49
+ * Returns null if no profile is configured for the channel.
50
+ */
51
+ export function getChannelPermissionProfile(
52
+ channelId: string,
53
+ ): ChannelPermissionProfile | null {
54
+ const perms = getChannelPermissions();
55
+ return perms[channelId] ?? null;
56
+ }
57
+
58
+ /**
59
+ * Set the permission profile for a specific channel.
60
+ */
61
+ export function setChannelPermissionProfile(
62
+ channelId: string,
63
+ profile: ChannelPermissionProfile,
64
+ ): void {
65
+ const config = getConfig();
66
+ if (!config.skills) config.skills = {} as typeof config.skills;
67
+ if (!config.skills.entries) config.skills.entries = {};
68
+ if (!config.skills.entries.slack)
69
+ config.skills.entries.slack = { enabled: true };
70
+ if (!config.skills.entries.slack.config)
71
+ config.skills.entries.slack.config = {};
72
+
73
+ const existing =
74
+ (config.skills.entries.slack.config.channelPermissions as
75
+ | ChannelPermissionMap
76
+ | undefined) ?? {};
77
+ existing[channelId] = profile;
78
+ config.skills.entries.slack.config.channelPermissions = existing;
79
+ saveConfig(config);
80
+ }
81
+
82
+ /**
83
+ * Remove the permission profile for a specific channel.
84
+ * Returns true if a profile was removed, false if none existed.
85
+ */
86
+ export function removeChannelPermissionProfile(channelId: string): boolean {
87
+ const config = getConfig();
88
+ const perms = config.skills?.entries?.slack?.config?.channelPermissions as
89
+ | ChannelPermissionMap
90
+ | undefined;
91
+ if (!perms || !(channelId in perms)) return false;
92
+
93
+ delete perms[channelId];
94
+ config.skills!.entries!.slack!.config!.channelPermissions = perms;
95
+ saveConfig(config);
96
+ return true;
97
+ }
98
+
99
+ /**
100
+ * Replace all channel permission profiles at once.
101
+ */
102
+ export function setAllChannelPermissions(
103
+ permissions: ChannelPermissionMap,
104
+ ): void {
105
+ const config = getConfig();
106
+ if (!config.skills) config.skills = {} as typeof config.skills;
107
+ if (!config.skills.entries) config.skills.entries = {};
108
+ if (!config.skills.entries.slack)
109
+ config.skills.entries.slack = { enabled: true };
110
+ if (!config.skills.entries.slack.config)
111
+ config.skills.entries.slack.config = {};
112
+
113
+ config.skills.entries.slack.config.channelPermissions = permissions;
114
+ saveConfig(config);
115
+ }
116
+
117
+ // ── Permission resolution ───────────────────────────────────────────
118
+
119
+ /**
120
+ * Check whether a specific tool is allowed in a channel.
121
+ * If no permission profile exists for the channel, all tools are allowed.
122
+ */
123
+ export function isToolAllowedInChannel(
124
+ channelId: string,
125
+ toolName: string,
126
+ toolCategory?: string,
127
+ ): boolean {
128
+ const profile = getChannelPermissionProfile(channelId);
129
+ if (!profile) return true;
130
+
131
+ // Check explicit block list first
132
+ if (profile.blockedTools?.includes(toolName)) return false;
133
+
134
+ // Check allowed categories (if specified)
135
+ if (
136
+ profile.allowedToolCategories &&
137
+ profile.allowedToolCategories.length > 0
138
+ ) {
139
+ if (!toolCategory) return false;
140
+ return profile.allowedToolCategories.includes(toolCategory);
141
+ }
142
+
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Get the effective trust level for a channel.
148
+ * Returns "standard" if no profile or no override is configured.
149
+ */
150
+ export function getChannelTrustLevel(
151
+ channelId: string,
152
+ ): "restricted" | "standard" {
153
+ const profile = getChannelPermissionProfile(channelId);
154
+ return profile?.trustLevel ?? "standard";
155
+ }
package/src/config/env.ts CHANGED
@@ -57,11 +57,14 @@ export function getGatewayPort(): number {
57
57
 
58
58
  /**
59
59
  * Resolve the gateway base URL for internal service-to-service calls.
60
- * Prefers GATEWAY_INTERNAL_BASE_URL if set, otherwise derives from port.
60
+ * Prefers GATEWAY_INTERNAL_BASE_URL if set, then INTERNAL_GATEWAY_BASE_URL
61
+ * (used by skill subprocesses), otherwise derives from port.
61
62
  */
62
63
  export function getGatewayInternalBaseUrl(): string {
63
64
  const explicit = str("GATEWAY_INTERNAL_BASE_URL");
64
65
  if (explicit) return explicit.replace(/\/+$/, "");
66
+ const skillInjected = str("INTERNAL_GATEWAY_BASE_URL");
67
+ if (skillInjected) return skillInjected.replace(/\/+$/, "");
65
68
  return `http://127.0.0.1:${getGatewayPort()}`;
66
69
  }
67
70