pi-roundtable 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.
Files changed (315) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +140 -0
  4. package/docs/plugins.md +924 -0
  5. package/examples/channels.test.ts +13 -0
  6. package/examples/channels.ts +27 -0
  7. package/examples/dashboard.test.ts +11 -0
  8. package/examples/dashboard.ts +9 -0
  9. package/examples/events.test.ts +26 -0
  10. package/examples/events.ts +25 -0
  11. package/examples/guide.test.ts +52 -0
  12. package/examples/holds.test.ts +16 -0
  13. package/examples/holds.ts +30 -0
  14. package/examples/http.test.ts +12 -0
  15. package/examples/http.ts +17 -0
  16. package/examples/interactions.test.ts +25 -0
  17. package/examples/interactions.ts +28 -0
  18. package/examples/migrations.test.ts +31 -0
  19. package/examples/migrations.ts +42 -0
  20. package/examples/packages.test.ts +9 -0
  21. package/examples/packages.ts +9 -0
  22. package/examples/preflight.test.ts +11 -0
  23. package/examples/preflight.ts +19 -0
  24. package/examples/prompt.test.ts +25 -0
  25. package/examples/prompt.ts +20 -0
  26. package/examples/providers.test.ts +10 -0
  27. package/examples/providers.ts +17 -0
  28. package/examples/seeds.test.ts +12 -0
  29. package/examples/seeds.ts +18 -0
  30. package/examples/selection.test.ts +13 -0
  31. package/examples/selection.ts +14 -0
  32. package/examples/services.test.ts +20 -0
  33. package/examples/services.ts +30 -0
  34. package/examples/session-tools.test.ts +24 -0
  35. package/examples/session-tools.ts +34 -0
  36. package/examples/tools.test.ts +18 -0
  37. package/examples/tools.ts +28 -0
  38. package/package.json +55 -0
  39. package/src/cli/add-plugin.test.ts +107 -0
  40. package/src/cli/add-plugin.ts +75 -0
  41. package/src/cli/checks/basic.test.ts +276 -0
  42. package/src/cli/checks/bun.ts +26 -0
  43. package/src/cli/checks/configuration.ts +28 -0
  44. package/src/cli/checks/database.test.ts +117 -0
  45. package/src/cli/checks/database.ts +100 -0
  46. package/src/cli/checks/discord.test.ts +200 -0
  47. package/src/cli/checks/discord.ts +218 -0
  48. package/src/cli/checks/environment.ts +27 -0
  49. package/src/cli/checks/model.ts +28 -0
  50. package/src/cli/checks/public-url.ts +40 -0
  51. package/src/cli/cli.test.ts +122 -0
  52. package/src/cli/cli.ts +166 -0
  53. package/src/cli/config-edit.test.ts +130 -0
  54. package/src/cli/config-edit.ts +199 -0
  55. package/src/cli/discord-api.ts +96 -0
  56. package/src/cli/doctor.test.ts +184 -0
  57. package/src/cli/doctor.ts +92 -0
  58. package/src/cli/http.ts +23 -0
  59. package/src/cli/init.test.ts +127 -0
  60. package/src/cli/init.ts +82 -0
  61. package/src/cli/main.ts +13 -0
  62. package/src/cli/project.ts +123 -0
  63. package/src/cli/report.ts +68 -0
  64. package/src/cli/roundtable.mjs +22 -0
  65. package/src/cli/runtime.ts +53 -0
  66. package/src/cli/size.test.ts +9 -0
  67. package/src/cli/start.ts +33 -0
  68. package/src/cli/templates.test.ts +129 -0
  69. package/src/cli/templates.ts +119 -0
  70. package/src/cli/testing/fixtures.ts +106 -0
  71. package/src/core/agents/agent-claim.test.ts +142 -0
  72. package/src/core/agents/agent-claim.ts +170 -0
  73. package/src/core/agents/agent-dashboard.test.ts +120 -0
  74. package/src/core/agents/agent-dashboard.ts +185 -0
  75. package/src/core/agents/agent-guild.test.ts +124 -0
  76. package/src/core/agents/agent-messages.ts +189 -0
  77. package/src/core/agents/agent-ports.ts +103 -0
  78. package/src/core/agents/agent-prompt.ts +163 -0
  79. package/src/core/agents/agent-rules.ts +106 -0
  80. package/src/core/agents/agent-schema.ts +115 -0
  81. package/src/core/agents/agent-settings.ts +49 -0
  82. package/src/core/agents/agent-store.test.ts +205 -0
  83. package/src/core/agents/agent-store.ts +384 -0
  84. package/src/core/agents/agent-team.ts +445 -0
  85. package/src/core/agents/agent-tools.ts +386 -0
  86. package/src/core/agents/avatar-studio.test.ts +88 -0
  87. package/src/core/agents/avatar-studio.ts +147 -0
  88. package/src/core/agents/group-messages.ts +99 -0
  89. package/src/core/agents/group-round.test.ts +143 -0
  90. package/src/core/agents/group-round.ts +117 -0
  91. package/src/core/agents/group-turns.ts +132 -0
  92. package/src/core/agents/owner-identity.test.ts +172 -0
  93. package/src/core/agents/team-editing.ts +205 -0
  94. package/src/core/agents/team-keys.ts +40 -0
  95. package/src/core/agents/team-layout.ts +135 -0
  96. package/src/core/agents/team-lifecycle.ts +188 -0
  97. package/src/core/agents/team-options.ts +52 -0
  98. package/src/core/agents/team-status.ts +80 -0
  99. package/src/core/agents/team-text.ts +60 -0
  100. package/src/core/agents/team-turn-types.ts +107 -0
  101. package/src/core/agents/team-turns.test.ts +278 -0
  102. package/src/core/agents/team-turns.ts +323 -0
  103. package/src/core/assets/neutral.png +0 -0
  104. package/src/core/assets/prompts/shared-guest.md +11 -0
  105. package/src/core/assets/prompts/shared.md +11 -0
  106. package/src/core/assets/skills/writing-skills/SKILL.md +21 -0
  107. package/src/core/attachments/attachment-dir.ts +15 -0
  108. package/src/core/attachments/attachment-fetcher.ts +83 -0
  109. package/src/core/attachments/attachments.test.ts +85 -0
  110. package/src/core/attachments/image-prep.ts +50 -0
  111. package/src/core/attachments/prompt-block.ts +37 -0
  112. package/src/core/attachments/turn-attachments.ts +79 -0
  113. package/src/core/boundary.test.ts +45 -0
  114. package/src/core/builtin/agent-server.ts +275 -0
  115. package/src/core/builtin/discord.ts +95 -0
  116. package/src/core/builtin/modules.test.ts +127 -0
  117. package/src/core/builtin/modules.ts +176 -0
  118. package/src/core/builtin/seeds.ts +10 -0
  119. package/src/core/builtin/stores.ts +42 -0
  120. package/src/core/config/config.test.ts +135 -0
  121. package/src/core/config/config.ts +285 -0
  122. package/src/core/config/schema.ts +181 -0
  123. package/src/core/contract/channels.ts +152 -0
  124. package/src/core/contract/discord.ts +33 -0
  125. package/src/core/contract/providers.ts +67 -0
  126. package/src/core/db/guild-scope.ts +53 -0
  127. package/src/core/db/migrations.test.ts +237 -0
  128. package/src/core/db/migrations.ts +37 -0
  129. package/src/core/define-roundtable.test.ts +134 -0
  130. package/src/core/define-roundtable.ts +195 -0
  131. package/src/core/define.test.ts +144 -0
  132. package/src/core/define.ts +154 -0
  133. package/src/core/discord/agent-commands.test.ts +58 -0
  134. package/src/core/discord/agent-commands.ts +385 -0
  135. package/src/core/discord/agent-discord.test.ts +70 -0
  136. package/src/core/discord/agent-discord.ts +367 -0
  137. package/src/core/discord/channel-executor.ts +404 -0
  138. package/src/core/discord/channel-operations.ts +329 -0
  139. package/src/core/discord/discord-surface.ts +431 -0
  140. package/src/core/discord/dispatch-thread-host.test.ts +114 -0
  141. package/src/core/discord/dispatch-thread-host.ts +72 -0
  142. package/src/core/discord/dispatch-threads.test.ts +94 -0
  143. package/src/core/discord/dispatch-threads.ts +180 -0
  144. package/src/core/discord/interaction-module.ts +1 -0
  145. package/src/core/discord/owner-cards.test.ts +435 -0
  146. package/src/core/discord/owner-cards.ts +463 -0
  147. package/src/core/discord/owner-command.ts +126 -0
  148. package/src/core/discord/owner-discord-access.ts +183 -0
  149. package/src/core/discord/owner-discord-threads.ts +160 -0
  150. package/src/core/discord/owner-discord.test.ts +325 -0
  151. package/src/core/discord/owner-discord.ts +282 -0
  152. package/src/core/discord/owner-panel.ts +103 -0
  153. package/src/core/discord/schedule-commands.ts +149 -0
  154. package/src/core/discord/stop-button.ts +20 -0
  155. package/src/core/domain/attachment.ts +39 -0
  156. package/src/core/domain/conversation.ts +42 -0
  157. package/src/core/domain/errors.ts +31 -0
  158. package/src/core/domain/expression.ts +21 -0
  159. package/src/core/domain/owner-prompts.ts +45 -0
  160. package/src/core/domain/ports.ts +91 -0
  161. package/src/core/domain/profile.ts +32 -0
  162. package/src/core/drain.test.ts +46 -0
  163. package/src/core/drain.ts +33 -0
  164. package/src/core/errors.ts +29 -0
  165. package/src/core/events.test.ts +81 -0
  166. package/src/core/events.ts +55 -0
  167. package/src/core/holds.test.ts +54 -0
  168. package/src/core/holds.ts +46 -0
  169. package/src/core/host.test.ts +536 -0
  170. package/src/core/host.ts +297 -0
  171. package/src/core/http/listeners.test.ts +164 -0
  172. package/src/core/http/listeners.ts +151 -0
  173. package/src/core/i18n/agent-panel.ts +96 -0
  174. package/src/core/i18n/cards.ts +64 -0
  175. package/src/core/i18n/channels.ts +28 -0
  176. package/src/core/i18n/conversation.ts +49 -0
  177. package/src/core/i18n/dashboard.ts +72 -0
  178. package/src/core/i18n/discord.ts +23 -0
  179. package/src/core/i18n/en.ts +27 -0
  180. package/src/core/i18n/i18n.test.ts +139 -0
  181. package/src/core/i18n/index.ts +58 -0
  182. package/src/core/i18n/owner.ts +35 -0
  183. package/src/core/i18n/schedules.ts +92 -0
  184. package/src/core/i18n/time-zones.ts +27 -0
  185. package/src/core/i18n/types.ts +7 -0
  186. package/src/core/i18n/zh-tw.ts +26 -0
  187. package/src/core/identity.test.ts +18 -0
  188. package/src/core/identity.ts +31 -0
  189. package/src/core/judging/confirmation-judge.ts +68 -0
  190. package/src/core/judging/effort-judge.test.ts +112 -0
  191. package/src/core/judging/effort-judge.ts +158 -0
  192. package/src/core/judging/model-judge.test.ts +126 -0
  193. package/src/core/judging/model-judge.ts +193 -0
  194. package/src/core/log.ts +45 -0
  195. package/src/core/models.ts +52 -0
  196. package/src/core/modules/background/background-turns.ts +78 -0
  197. package/src/core/modules/delegation/delegate.ts +54 -0
  198. package/src/core/modules/delegation/delegator.test.ts +170 -0
  199. package/src/core/modules/delegation/delegator.ts +191 -0
  200. package/src/core/modules/delegation/sol-worker.ts +78 -0
  201. package/src/core/modules/discord-admin/discord-admin.ts +288 -0
  202. package/src/core/modules/host-shell/shell-policy.ts +386 -0
  203. package/src/core/modules/memory/owner-memory-store.test.ts +244 -0
  204. package/src/core/modules/memory/owner-memory-store.ts +228 -0
  205. package/src/core/modules/memory/owner-memory.ts +153 -0
  206. package/src/core/modules/notify/notify.ts +26 -0
  207. package/src/core/modules/schedules/recurrence.ts +206 -0
  208. package/src/core/modules/schedules/schedule-store.ts +213 -0
  209. package/src/core/modules/schedules/schedule-tools.ts +196 -0
  210. package/src/core/modules/schedules/schedule.test.ts +358 -0
  211. package/src/core/modules/schedules/scheduler.ts +121 -0
  212. package/src/core/modules/schedules/schedules.ts +87 -0
  213. package/src/core/modules/skills/repo-name.ts +11 -0
  214. package/src/core/modules/skills/skill-kind.test.ts +55 -0
  215. package/src/core/modules/skills/skill-link.ts +69 -0
  216. package/src/core/modules/skills/skill-listing.ts +128 -0
  217. package/src/core/modules/skills/skill-registry.test.ts +376 -0
  218. package/src/core/modules/skills/skill-registry.ts +323 -0
  219. package/src/core/modules/skills/skill-rules.ts +120 -0
  220. package/src/core/modules/skills/skill-store.ts +285 -0
  221. package/src/core/modules/skills/skill-tools.ts +207 -0
  222. package/src/core/ops/error-reporter.test.ts +283 -0
  223. package/src/core/ops/error-reporter.ts +271 -0
  224. package/src/core/plugin.ts +200 -0
  225. package/src/core/presentation/card-cadence.ts +41 -0
  226. package/src/core/presentation/headline.ts +34 -0
  227. package/src/core/presentation/presentation.test.ts +166 -0
  228. package/src/core/presentation/quiet-links.ts +36 -0
  229. package/src/core/presentation/reply-splitter.ts +110 -0
  230. package/src/core/presentation/thinking-line.ts +24 -0
  231. package/src/core/public-entry.test.ts +59 -0
  232. package/src/core/registry/contributions.test.ts +215 -0
  233. package/src/core/registry/contributions.ts +249 -0
  234. package/src/core/registry/interactions.test.ts +87 -0
  235. package/src/core/registry/interactions.ts +55 -0
  236. package/src/core/registry/providers.test.ts +103 -0
  237. package/src/core/registry/providers.ts +55 -0
  238. package/src/core/routing/channel-queue.test.ts +31 -0
  239. package/src/core/routing/channel-queue.ts +44 -0
  240. package/src/core/routing/channel-router.test.ts +326 -0
  241. package/src/core/routing/channel-router.ts +157 -0
  242. package/src/core/routing/conversation-kind.ts +14 -0
  243. package/src/core/routing/forward-join.ts +65 -0
  244. package/src/core/routing/message-text.ts +16 -0
  245. package/src/core/routing/settle-turn.test.ts +21 -0
  246. package/src/core/routing/settle-turn.ts +26 -0
  247. package/src/core/runtime/compaction-tiers.test.ts +227 -0
  248. package/src/core/runtime/compaction-tiers.ts +203 -0
  249. package/src/core/runtime/conversation-sessions.ts +162 -0
  250. package/src/core/runtime/extensions/agent-prompt.ts +16 -0
  251. package/src/core/runtime/extensions/ask-user.test.ts +114 -0
  252. package/src/core/runtime/extensions/ask-user.ts +98 -0
  253. package/src/core/runtime/extensions/confirmation-gate.ts +247 -0
  254. package/src/core/runtime/extensions/self-compact-guard.test.ts +50 -0
  255. package/src/core/runtime/extensions/self-compact-guard.ts +23 -0
  256. package/src/core/runtime/mcp.ts +23 -0
  257. package/src/core/runtime/pending-confirmation-store.test.ts +52 -0
  258. package/src/core/runtime/pending-confirmation-store.ts +75 -0
  259. package/src/core/runtime/pi-agent-runtime.ts +477 -0
  260. package/src/core/runtime/prompt-slot.ts +81 -0
  261. package/src/core/runtime/runtime-types.ts +150 -0
  262. package/src/core/runtime/session-archive.test.ts +20 -0
  263. package/src/core/runtime/session-archive.ts +17 -0
  264. package/src/core/runtime/session-factory.ts +291 -0
  265. package/src/core/runtime/steerable-run.test.ts +264 -0
  266. package/src/core/runtime/steerable-run.ts +109 -0
  267. package/src/core/runtime/text-tools.test.ts +110 -0
  268. package/src/core/runtime/text-tools.ts +66 -0
  269. package/src/core/runtime/turn-answer.test.ts +72 -0
  270. package/src/core/runtime/turn-answer.ts +79 -0
  271. package/src/core/runtime/worker-task.test.ts +82 -0
  272. package/src/core/runtime/worker-task.ts +65 -0
  273. package/src/core/services.test.ts +28 -0
  274. package/src/core/services.ts +107 -0
  275. package/src/core/sessions.test.ts +85 -0
  276. package/src/core/sessions.ts +149 -0
  277. package/src/core/shared/attachment-reader.ts +91 -0
  278. package/src/core/shared/delegate-tool.ts +15 -0
  279. package/src/core/shared/mcp-adapter.ts +58 -0
  280. package/src/core/shared/package-dir.ts +9 -0
  281. package/src/core/shared/profile-tools.ts +16 -0
  282. package/src/core/shared/read-attachment-tool.ts +43 -0
  283. package/src/core/shared/schedule-tools.ts +118 -0
  284. package/src/core/shared/session-messages.ts +50 -0
  285. package/src/core/shared/tool-result.ts +9 -0
  286. package/src/core/shared/unix-server.ts +18 -0
  287. package/src/core/size.test.ts +9 -0
  288. package/src/core/speakers.test.ts +78 -0
  289. package/src/core/speakers.ts +143 -0
  290. package/src/core/testing/database.ts +52 -0
  291. package/src/core/testing/file-size.ts +34 -0
  292. package/src/core/testing/locale.ts +8 -0
  293. package/src/core/testing/modules.ts +97 -0
  294. package/src/core/testing/owner.ts +15 -0
  295. package/src/core/testing/thread-host.ts +60 -0
  296. package/src/core/time.test.ts +166 -0
  297. package/src/core/time.ts +67 -0
  298. package/src/core/tool-tiers.test.ts +117 -0
  299. package/src/core/tool-tiers.ts +127 -0
  300. package/src/entries.test.ts +112 -0
  301. package/src/index.ts +28 -0
  302. package/src/testing.test.ts +156 -0
  303. package/src/testing.ts +182 -0
  304. package/templates/.env.example +22 -0
  305. package/templates/README.md +11 -0
  306. package/templates/_gitignore +3 -0
  307. package/templates/agents.ts +12 -0
  308. package/templates/biome.json.tmpl +7 -0
  309. package/templates/docker-compose.yml +16 -0
  310. package/templates/package.json.tmpl +24 -0
  311. package/templates/persona/shared.md +2 -0
  312. package/templates/plugin/plugin.test.ts.tmpl +13 -0
  313. package/templates/plugin/plugin.ts +18 -0
  314. package/templates/roundtable.config.ts +24 -0
  315. package/templates/tsconfig.json.tmpl +16 -0
@@ -0,0 +1,329 @@
1
+ import { type TObject, Type } from "typebox";
2
+ import Value from "typebox/value";
3
+ import { type Messages, messages } from "../i18n/index.ts";
4
+
5
+ /** What a call may do in one channel, with the owner-facing label; grants and permission checks name these. */
6
+ export const CHANNEL_OPERATIONS = [
7
+ "read",
8
+ "send",
9
+ "edit",
10
+ "pin",
11
+ "delete",
12
+ "channel",
13
+ "permissions",
14
+ ] as const;
15
+ export type ChannelOperation = (typeof CHANNEL_OPERATIONS)[number];
16
+
17
+ export const isChannelOperation = (value: string): value is ChannelOperation =>
18
+ (CHANNEL_OPERATIONS as readonly string[]).includes(value);
19
+
20
+ const LABEL_KEYS = {
21
+ read: "opRead",
22
+ send: "opSend",
23
+ edit: "opEdit",
24
+ pin: "opPin",
25
+ delete: "opDelete",
26
+ channel: "opChannel",
27
+ permissions: "opPermissions",
28
+ } as const satisfies Record<ChannelOperation, keyof Messages>;
29
+
30
+ /** The owner-facing label of an operation, in the active language. */
31
+ export const operationLabel = (operation: ChannelOperation): string =>
32
+ messages()[LABEL_KEYS[operation]];
33
+
34
+ const id = Type.String({ pattern: "^\\d{1,20}$" });
35
+ const channel = { channelId: id };
36
+ const message = { channelId: id, messageId: id };
37
+ const strict = { additionalProperties: false } as const;
38
+
39
+ /** One upload may carry at most this much, in all files together. */
40
+ export const CHANNEL_UPLOAD_MAX_BYTES = 8 * 1024 * 1024;
41
+
42
+ const file = Type.Object(
43
+ {
44
+ filename: Type.String({
45
+ minLength: 1,
46
+ maxLength: 200,
47
+ // No path separators or control characters.
48
+ pattern: "^[^/\\\\\\u0000-\\u001f]+$",
49
+ description: "The attachment file name, without a path",
50
+ }),
51
+ dataBase64: Type.String({
52
+ minLength: 4,
53
+ maxLength: Math.ceil(CHANNEL_UPLOAD_MAX_BYTES / 3) * 4,
54
+ description:
55
+ "The file content as standard base64, without a data URL prefix",
56
+ }),
57
+ description: Type.Optional(
58
+ Type.String({ maxLength: 1024, description: "Alt text for an image" }),
59
+ ),
60
+ },
61
+ strict,
62
+ );
63
+ const files = Type.Optional(
64
+ Type.Array(file, {
65
+ minItems: 1,
66
+ maxItems: 10,
67
+ description:
68
+ "Images or files to add; at most 8 MiB in total for the whole call",
69
+ }),
70
+ );
71
+
72
+ interface ChannelTool {
73
+ operation: ChannelOperation;
74
+ schema: TObject;
75
+ description: string;
76
+ }
77
+
78
+ /** The Discord tools a bundle can expose, each gated by one operation. */
79
+ export const CHANNEL_TOOLS: Record<string, ChannelTool> = {
80
+ discord_get_channel_info: {
81
+ operation: "read",
82
+ schema: Type.Object(channel, strict),
83
+ description: "Read the channel's server, name, and topic.",
84
+ },
85
+ discord_get_messages: {
86
+ operation: "read",
87
+ schema: Type.Object(
88
+ {
89
+ ...channel,
90
+ limit: Type.Optional(
91
+ Type.Integer({ minimum: 1, maximum: 100, default: 20 }),
92
+ ),
93
+ before: Type.Optional(id),
94
+ after: Type.Optional(id),
95
+ around: Type.Optional(id),
96
+ },
97
+ strict,
98
+ ),
99
+ description:
100
+ "Read the channel's recent messages; position with before, after, or around.",
101
+ },
102
+ discord_search_messages: {
103
+ operation: "read",
104
+ schema: Type.Object(
105
+ {
106
+ ...channel,
107
+ query: Type.Optional(
108
+ Type.String({
109
+ minLength: 1,
110
+ maxLength: 1024,
111
+ description:
112
+ "Discord full-text search keywords; omit to browse by the other filters",
113
+ }),
114
+ ),
115
+ authorId: Type.Optional(id),
116
+ afterDate: Type.Optional(
117
+ Type.String({
118
+ format: "date-time",
119
+ description: "ISO 8601 start time, inclusive; needs a time zone",
120
+ }),
121
+ ),
122
+ beforeDate: Type.Optional(
123
+ Type.String({
124
+ format: "date-time",
125
+ description: "ISO 8601 end time, exclusive; needs a time zone",
126
+ }),
127
+ ),
128
+ has: Type.Optional(
129
+ Type.Union(
130
+ ["image", "sound", "video", "file", "link", "embed"].map((v) =>
131
+ Type.Literal(v),
132
+ ),
133
+ ),
134
+ ),
135
+ sort: Type.Optional(
136
+ Type.Union(
137
+ ["newest", "oldest", "relevance"].map((v) => Type.Literal(v)),
138
+ { default: "newest" },
139
+ ),
140
+ ),
141
+ limit: Type.Optional(
142
+ Type.Integer({ minimum: 1, maximum: 25, default: 25 }),
143
+ ),
144
+ offset: Type.Optional(
145
+ Type.Integer({
146
+ minimum: 0,
147
+ maximum: 9975,
148
+ default: 0,
149
+ description:
150
+ "For the next page keep the same filters and pass back nextOffset",
151
+ }),
152
+ ),
153
+ },
154
+ strict,
155
+ ),
156
+ description:
157
+ "Search one channel's message history by keyword, author, date, attachment, and sort order. Page with nextOffset; the total is approximate, results are incomplete while indexing, and when limitReached narrow the date range.",
158
+ },
159
+ discord_get_pinned_messages: {
160
+ operation: "read",
161
+ schema: Type.Object(channel, strict),
162
+ description: "Read the channel's pinned messages.",
163
+ },
164
+ discord_send_message: {
165
+ operation: "send",
166
+ schema: Type.Object(
167
+ {
168
+ ...channel,
169
+ content: Type.Optional(Type.String({ maxLength: 2000 })),
170
+ files,
171
+ },
172
+ strict,
173
+ ),
174
+ description:
175
+ "Send a message, optionally with images or files; it triggers no @ mentions.",
176
+ },
177
+ discord_edit_message: {
178
+ operation: "edit",
179
+ schema: Type.Object(
180
+ {
181
+ ...message,
182
+ content: Type.Optional(Type.String({ maxLength: 2000 })),
183
+ files,
184
+ keepAttachmentIds: Type.Optional(
185
+ Type.Array(id, {
186
+ maxItems: 10,
187
+ description:
188
+ "Omit to keep every old attachment; an empty array removes them all; listed ids keep only those attachments",
189
+ }),
190
+ ),
191
+ },
192
+ strict,
193
+ ),
194
+ description: "Edit a message the bot itself sent.",
195
+ },
196
+ discord_delete_message: {
197
+ operation: "delete",
198
+ schema: Type.Object(message, strict),
199
+ description: "Delete one message.",
200
+ },
201
+ discord_pin_message: {
202
+ operation: "pin",
203
+ schema: Type.Object(message, strict),
204
+ description: "Pin one message.",
205
+ },
206
+ discord_unpin_message: {
207
+ operation: "pin",
208
+ schema: Type.Object(message, strict),
209
+ description: "Unpin one message.",
210
+ },
211
+ discord_edit_channel: {
212
+ operation: "channel",
213
+ schema: Type.Object(
214
+ {
215
+ ...channel,
216
+ name: Type.Optional(Type.String({ minLength: 1, maxLength: 100 })),
217
+ topic: Type.Optional(Type.String({ maxLength: 1024 })),
218
+ nsfw: Type.Optional(Type.Boolean()),
219
+ rateLimitPerUser: Type.Optional(
220
+ Type.Integer({ minimum: 0, maximum: 21600 }),
221
+ ),
222
+ position: Type.Optional(Type.Integer({ minimum: 0 })),
223
+ },
224
+ strict,
225
+ ),
226
+ description:
227
+ "Change the channel's name, topic, slowmode, and similar settings; it cannot move the category.",
228
+ },
229
+ discord_set_channel_permissions: {
230
+ operation: "permissions",
231
+ schema: Type.Object(
232
+ {
233
+ ...channel,
234
+ targetId: id,
235
+ allow: Type.Optional(Type.Array(Type.String(), { maxItems: 60 })),
236
+ deny: Type.Optional(Type.Array(Type.String(), { maxItems: 60 })),
237
+ },
238
+ strict,
239
+ ),
240
+ description:
241
+ "Set a role's or member's permission overwrite in this channel; permission names are discord.js PermissionFlagsBits.",
242
+ },
243
+ discord_delete_channel_permissions: {
244
+ operation: "permissions",
245
+ schema: Type.Object({ ...channel, targetId: id }, strict),
246
+ description:
247
+ "Remove a role's or member's permission overwrite in this channel.",
248
+ },
249
+ };
250
+
251
+ /** A tool call that cannot be run as asked; the code is safe to show the caller. */
252
+ export class ChannelToolError extends Error {
253
+ constructor(readonly code: string) {
254
+ super(code);
255
+ }
256
+ }
257
+
258
+ const DISCORD_EPOCH = 1420070400000;
259
+ const BASE64 =
260
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
261
+
262
+ /** Checks and fills defaults into a tool's arguments; throws ChannelToolError otherwise. */
263
+ export function parseChannelTool(
264
+ tool: string,
265
+ args: unknown,
266
+ ): Record<string, unknown> {
267
+ const spec = CHANNEL_TOOLS[tool];
268
+ if (!spec) throw new ChannelToolError("INVALID_CHANNEL_OPERATION");
269
+ const value = Value.Default(spec.schema, structuredClone(args ?? {}));
270
+ if (!Value.Check(spec.schema, value))
271
+ throw new ChannelToolError("INVALID_CHANNEL_ARGUMENTS");
272
+ const parsed = value as Record<string, unknown>;
273
+ if (tool === "discord_search_messages") {
274
+ const after = parsed.afterDate && Date.parse(String(parsed.afterDate));
275
+ const before = parsed.beforeDate && Date.parse(String(parsed.beforeDate));
276
+ for (const date of [after, before]) {
277
+ if (typeof date === "number" && date < DISCORD_EPOCH)
278
+ throw new ChannelToolError("INVALID_CHANNEL_DATE_RANGE");
279
+ }
280
+ if (
281
+ typeof after === "number" &&
282
+ typeof before === "number" &&
283
+ after >= before
284
+ )
285
+ throw new ChannelToolError("INVALID_CHANNEL_DATE_RANGE");
286
+ }
287
+ if (tool === "discord_send_message" || tool === "discord_edit_message") {
288
+ const uploads = (parsed.files ?? []) as { dataBase64: string }[];
289
+ if (
290
+ tool === "discord_send_message" &&
291
+ !parsed.content &&
292
+ uploads.length === 0
293
+ )
294
+ throw new ChannelToolError("INVALID_CHANNEL_EMPTY_MESSAGE");
295
+ if (
296
+ tool === "discord_edit_message" &&
297
+ parsed.content === undefined &&
298
+ parsed.files === undefined &&
299
+ parsed.keepAttachmentIds === undefined
300
+ )
301
+ throw new ChannelToolError("INVALID_CHANNEL_EMPTY_EDIT");
302
+ let bytes = 0;
303
+ for (const upload of uploads) {
304
+ if (!BASE64.test(upload.dataBase64))
305
+ throw new ChannelToolError("INVALID_CHANNEL_BASE64");
306
+ const data = upload.dataBase64;
307
+ bytes +=
308
+ (data.length / 4) * 3 -
309
+ (data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0);
310
+ if (bytes > CHANNEL_UPLOAD_MAX_BYTES)
311
+ throw new ChannelToolError("INVALID_CHANNEL_UPLOAD_SIZE");
312
+ }
313
+ }
314
+ return parsed;
315
+ }
316
+
317
+ /** Runs Discord operations for granted calls; implemented over discord.js by the surface. */
318
+ export interface ChannelExecutor {
319
+ /** The channel's guild, after checking the bot holds the operation's permissions. */
320
+ inspect(
321
+ channelId: string,
322
+ operation: ChannelOperation,
323
+ ): Promise<{ guildId: string }>;
324
+ execute(tool: string, args: Record<string, unknown>): Promise<unknown>;
325
+ /** Current guild and channel names, or undefined when the bot cannot see the channel. */
326
+ names(
327
+ channelId: string,
328
+ ): Promise<{ guildName: string; channelName: string } | undefined>;
329
+ }