pi-roundtable 0.1.0 → 0.2.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 (238) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +6 -4
  3. package/docs/plugins.md +1448 -66
  4. package/examples/echo-runtime.test.ts +122 -0
  5. package/examples/echo-runtime.ts +107 -0
  6. package/examples/events.test.ts +1 -0
  7. package/examples/events.ts +2 -2
  8. package/examples/fake-surface.test.ts +180 -0
  9. package/examples/fake-surface.ts +109 -0
  10. package/examples/interactions.test.ts +12 -3
  11. package/examples/interactions.ts +19 -19
  12. package/examples/shared-services.test.ts +74 -0
  13. package/examples/shared-services.ts +60 -0
  14. package/examples/study-room.test.ts +69 -0
  15. package/examples/study-room.ts +52 -0
  16. package/examples/support-desk.test.ts +34 -0
  17. package/examples/support-desk.ts +40 -0
  18. package/package.json +9 -2
  19. package/src/cli/checks/database.ts +11 -30
  20. package/src/cli/checks/images.ts +19 -0
  21. package/src/cli/doctor.ts +6 -0
  22. package/src/cli/main.ts +2 -1
  23. package/src/core/agents/agent-claim.ts +42 -16
  24. package/src/core/agents/agent-messages.ts +4 -4
  25. package/src/core/agents/agent-ports.ts +8 -21
  26. package/src/core/agents/agent-prompt.ts +3 -1
  27. package/src/core/agents/agent-store.ts +6 -4
  28. package/src/core/agents/agent-team-fixture.ts +389 -0
  29. package/src/core/agents/agent-team.ts +26 -25
  30. package/src/core/agents/agent-tools.ts +34 -15
  31. package/src/core/agents/avatar-studio.ts +40 -6
  32. package/src/core/agents/fallback-avatar.ts +85 -0
  33. package/src/core/agents/team-editing.ts +49 -10
  34. package/src/core/agents/team-keys.ts +22 -9
  35. package/src/core/agents/team-layout.ts +4 -3
  36. package/src/core/agents/team-lifecycle.ts +28 -2
  37. package/src/core/agents/team-options.ts +9 -5
  38. package/src/core/agents/team-status.ts +4 -4
  39. package/src/core/agents/team-text.ts +8 -6
  40. package/src/core/agents/team-turn-types.ts +4 -4
  41. package/src/core/agents/team-turns.ts +6 -5
  42. package/src/core/builtin/agent-server.ts +187 -121
  43. package/src/core/builtin/discord-admin.ts +41 -0
  44. package/src/core/builtin/discord.ts +65 -40
  45. package/src/core/builtin/modules.ts +67 -58
  46. package/src/core/builtin/session-tool.ts +24 -0
  47. package/src/core/builtin/skills.ts +72 -0
  48. package/src/core/builtin/stores.ts +55 -31
  49. package/src/core/config/config.ts +45 -4
  50. package/src/core/config/schema.ts +6 -0
  51. package/src/core/contract/channels.ts +54 -14
  52. package/src/core/contract/providers.ts +17 -0
  53. package/src/core/contract/runtime.ts +138 -0
  54. package/src/core/contract/services.ts +45 -0
  55. package/src/core/contract/surface.ts +88 -0
  56. package/src/core/db/migrations.ts +118 -3
  57. package/src/core/define-roundtable.ts +70 -46
  58. package/src/core/define.ts +2 -1
  59. package/src/core/discord/agent-commands.ts +39 -14
  60. package/src/core/discord/agent-panel.ts +66 -0
  61. package/src/core/discord/channel-executor.ts +6 -3
  62. package/src/core/discord/channel-operations.ts +14 -11
  63. package/src/core/discord/command-collection.ts +46 -0
  64. package/src/core/{registry/interactions.ts → discord/compose-commands.ts} +5 -5
  65. package/src/core/discord/connection.ts +35 -0
  66. package/src/core/discord/discord-surface.ts +32 -94
  67. package/src/core/discord/dispatch-threads.ts +2 -2
  68. package/src/core/discord/inbound-message.ts +99 -0
  69. package/src/core/discord/interaction-module.ts +57 -1
  70. package/src/core/discord/owner-cards.ts +4 -4
  71. package/src/core/discord/owner-command.ts +44 -11
  72. package/src/core/discord/owner-discord.ts +2 -2
  73. package/src/core/discord/schedule-commands.ts +21 -18
  74. package/src/core/discord/stop-button.ts +44 -1
  75. package/src/core/domain/attachment.ts +12 -8
  76. package/src/core/domain/conversation.ts +3 -4
  77. package/src/core/domain/owner-prompts.ts +5 -5
  78. package/src/core/domain/ports.ts +9 -53
  79. package/src/core/freeze.ts +9 -0
  80. package/src/core/host.ts +255 -82
  81. package/src/core/http/listeners.ts +55 -20
  82. package/src/core/i18n/agent-panel.ts +4 -0
  83. package/src/core/i18n/index.ts +12 -2
  84. package/src/core/i18n/owner.ts +7 -1
  85. package/src/core/i18n/schedules.ts +12 -6
  86. package/src/core/identity.ts +1 -1
  87. package/src/core/judging/effort-judge.ts +25 -3
  88. package/src/core/log.ts +21 -3
  89. package/src/core/models.ts +2 -2
  90. package/src/core/modules/background/background-turns.ts +6 -4
  91. package/src/core/modules/delegation/delegate.ts +3 -2
  92. package/src/core/modules/delegation/delegator.ts +15 -13
  93. package/src/core/modules/delegation/{sol-worker.ts → web-research-worker.ts} +6 -6
  94. package/src/core/modules/discord-admin/discord-admin.ts +6 -6
  95. package/src/core/modules/host-shell/shell-policy.ts +8 -3
  96. package/src/core/modules/memory/owner-memory-store.ts +27 -25
  97. package/src/core/modules/memory/owner-memory.ts +9 -13
  98. package/src/core/modules/schedules/recurrence.ts +5 -3
  99. package/src/core/modules/schedules/schedule-store.ts +11 -11
  100. package/src/core/modules/schedules/schedule-tools.ts +22 -20
  101. package/src/core/modules/schedules/scheduler.ts +7 -2
  102. package/src/core/modules/schedules/schedules.ts +9 -3
  103. package/src/core/modules/skills/skill-registry.ts +3 -2
  104. package/src/core/modules/skills/skill-store.ts +2 -15
  105. package/src/core/modules/skills/skill-tools.ts +10 -4
  106. package/src/core/ops/error-reporter.ts +1 -1
  107. package/src/core/plugin.ts +232 -28
  108. package/src/core/registry/contributions.ts +173 -14
  109. package/src/core/registry/providers.ts +35 -5
  110. package/src/core/registry/services.ts +229 -0
  111. package/src/core/routing/channel-queue.ts +5 -0
  112. package/src/core/routing/channel-router.ts +21 -7
  113. package/src/core/routing/conversation-turns.ts +139 -0
  114. package/src/core/routing/message-text.ts +9 -2
  115. package/src/core/routing/surface-port.ts +39 -0
  116. package/src/core/runtime/conversation-sessions.ts +4 -3
  117. package/src/core/runtime/extensions/confirmation-fixture.ts +11 -0
  118. package/src/core/runtime/extensions/confirmation-gate.ts +11 -9
  119. package/src/core/runtime/mcp.ts +1 -1
  120. package/src/core/runtime/pending-confirmation-store.ts +10 -12
  121. package/src/core/runtime/pi-agent-runtime.ts +9 -6
  122. package/src/core/runtime/prompt-slot.ts +8 -3
  123. package/src/core/runtime/runtime-types.ts +9 -35
  124. package/src/core/runtime/session-factory.ts +21 -5
  125. package/src/core/runtime/text-tools.ts +1 -3
  126. package/src/core/services.ts +238 -90
  127. package/src/core/sessions.ts +3 -2
  128. package/src/core/shared/{profile-tools.ts → active-tools.ts} +2 -2
  129. package/src/core/shared/delegate-tool.ts +4 -3
  130. package/src/core/shared/schedule-tools.ts +22 -13
  131. package/src/core/shared/session-messages.ts +1 -1
  132. package/src/core/speakers.ts +6 -38
  133. package/src/core/testing/database.ts +1 -7
  134. package/src/core/testing/eager-catalog.ts +30 -0
  135. package/src/core/testing/locale.ts +23 -5
  136. package/src/core/testing/modules.ts +105 -41
  137. package/src/core/testing/test-host.ts +279 -0
  138. package/src/core/testing/tool-set.ts +64 -0
  139. package/src/core/time.ts +6 -1
  140. package/src/core/tool-set.snapshot.json +288 -0
  141. package/src/core/tool-tiers.ts +17 -57
  142. package/src/discord/index.ts +64 -0
  143. package/src/index.ts +181 -10
  144. package/src/kit/channels.ts +14 -0
  145. package/src/kit/domain.ts +18 -0
  146. package/src/kit/holds.ts +4 -0
  147. package/src/kit/index.ts +129 -0
  148. package/src/kit/judging.ts +10 -0
  149. package/src/kit/memory.ts +3 -0
  150. package/src/kit/mirror.ts +19 -0
  151. package/src/kit/presentation.ts +7 -0
  152. package/src/kit/shell.ts +7 -0
  153. package/src/kit/skills.ts +8 -0
  154. package/src/kit/support.ts +21 -0
  155. package/src/kit/threads.ts +7 -0
  156. package/src/kit/tools.ts +14 -0
  157. package/src/kit/worker.ts +16 -0
  158. package/src/testing.ts +394 -34
  159. package/examples/guide.test.ts +0 -52
  160. package/src/cli/add-plugin.test.ts +0 -107
  161. package/src/cli/checks/basic.test.ts +0 -276
  162. package/src/cli/checks/database.test.ts +0 -117
  163. package/src/cli/checks/discord.test.ts +0 -200
  164. package/src/cli/cli.test.ts +0 -122
  165. package/src/cli/config-edit.test.ts +0 -130
  166. package/src/cli/doctor.test.ts +0 -184
  167. package/src/cli/init.test.ts +0 -127
  168. package/src/cli/size.test.ts +0 -9
  169. package/src/cli/templates.test.ts +0 -129
  170. package/src/cli/testing/fixtures.ts +0 -106
  171. package/src/core/agents/agent-claim.test.ts +0 -142
  172. package/src/core/agents/agent-dashboard.test.ts +0 -120
  173. package/src/core/agents/agent-guild.test.ts +0 -124
  174. package/src/core/agents/agent-store.test.ts +0 -205
  175. package/src/core/agents/avatar-studio.test.ts +0 -88
  176. package/src/core/agents/group-round.test.ts +0 -143
  177. package/src/core/agents/owner-identity.test.ts +0 -172
  178. package/src/core/agents/team-turns.test.ts +0 -278
  179. package/src/core/attachments/attachments.test.ts +0 -85
  180. package/src/core/boundary.test.ts +0 -45
  181. package/src/core/builtin/modules.test.ts +0 -127
  182. package/src/core/config/config.test.ts +0 -135
  183. package/src/core/contract/discord.ts +0 -33
  184. package/src/core/db/migrations.test.ts +0 -237
  185. package/src/core/define-roundtable.test.ts +0 -134
  186. package/src/core/define.test.ts +0 -144
  187. package/src/core/discord/agent-commands.test.ts +0 -58
  188. package/src/core/discord/agent-discord.test.ts +0 -70
  189. package/src/core/discord/dispatch-thread-host.test.ts +0 -114
  190. package/src/core/discord/dispatch-threads.test.ts +0 -94
  191. package/src/core/discord/owner-cards.test.ts +0 -435
  192. package/src/core/discord/owner-discord.test.ts +0 -325
  193. package/src/core/domain/expression.ts +0 -21
  194. package/src/core/domain/profile.ts +0 -32
  195. package/src/core/drain.test.ts +0 -46
  196. package/src/core/events.test.ts +0 -81
  197. package/src/core/holds.test.ts +0 -54
  198. package/src/core/host.test.ts +0 -536
  199. package/src/core/http/listeners.test.ts +0 -164
  200. package/src/core/i18n/i18n.test.ts +0 -139
  201. package/src/core/identity.test.ts +0 -18
  202. package/src/core/judging/effort-judge.test.ts +0 -112
  203. package/src/core/judging/model-judge.test.ts +0 -126
  204. package/src/core/modules/delegation/delegator.test.ts +0 -170
  205. package/src/core/modules/memory/owner-memory-store.test.ts +0 -244
  206. package/src/core/modules/schedules/schedule.test.ts +0 -358
  207. package/src/core/modules/skills/skill-kind.test.ts +0 -55
  208. package/src/core/modules/skills/skill-registry.test.ts +0 -376
  209. package/src/core/ops/error-reporter.test.ts +0 -283
  210. package/src/core/presentation/card-cadence.ts +0 -41
  211. package/src/core/presentation/presentation.test.ts +0 -166
  212. package/src/core/public-entry.test.ts +0 -59
  213. package/src/core/registry/contributions.test.ts +0 -215
  214. package/src/core/registry/interactions.test.ts +0 -87
  215. package/src/core/registry/providers.test.ts +0 -103
  216. package/src/core/routing/channel-queue.test.ts +0 -31
  217. package/src/core/routing/channel-router.test.ts +0 -326
  218. package/src/core/routing/conversation-kind.ts +0 -14
  219. package/src/core/routing/settle-turn.test.ts +0 -21
  220. package/src/core/runtime/compaction-tiers.test.ts +0 -227
  221. package/src/core/runtime/extensions/ask-user.test.ts +0 -114
  222. package/src/core/runtime/extensions/self-compact-guard.test.ts +0 -50
  223. package/src/core/runtime/pending-confirmation-store.test.ts +0 -52
  224. package/src/core/runtime/session-archive.test.ts +0 -20
  225. package/src/core/runtime/steerable-run.test.ts +0 -264
  226. package/src/core/runtime/text-tools.test.ts +0 -110
  227. package/src/core/runtime/turn-answer.test.ts +0 -72
  228. package/src/core/runtime/worker-task.test.ts +0 -82
  229. package/src/core/services.test.ts +0 -28
  230. package/src/core/sessions.test.ts +0 -85
  231. package/src/core/shared/unix-server.ts +0 -18
  232. package/src/core/size.test.ts +0 -9
  233. package/src/core/speakers.test.ts +0 -78
  234. package/src/core/testing/file-size.ts +0 -34
  235. package/src/core/time.test.ts +0 -166
  236. package/src/core/tool-tiers.test.ts +0 -117
  237. package/src/entries.test.ts +0 -112
  238. package/src/testing.test.ts +0 -156
@@ -1,325 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { ChannelType, type Client, PermissionFlagsBits } from "discord.js";
3
- import { DiscordOwnerOps } from "./owner-discord.ts";
4
-
5
- const OWNER = "100";
6
- const BOT = "200";
7
- const {
8
- KickMembers,
9
- ManageNicknames,
10
- ManageRoles,
11
- Administrator,
12
- ViewChannel,
13
- ManageThreads,
14
- CreatePrivateThreads,
15
- SendMessagesInThreads,
16
- } = PermissionFlagsBits;
17
-
18
- class FakeRole {
19
- constructor(
20
- readonly id: string,
21
- readonly position: number,
22
- ) {}
23
- comparePositionTo(other: FakeRole): number {
24
- return this.position - other.position;
25
- }
26
- }
27
-
28
- /** A member with a fixed permission set, a highest role, and recorded actions. */
29
- class FakeMember {
30
- actions: string[] = [];
31
- readonly roles: {
32
- highest: FakeRole;
33
- add: (role: FakeRole) => Promise<void>;
34
- };
35
- readonly permissions: { has: (needed: bigint | bigint[]) => boolean };
36
- constructor(
37
- readonly id: string,
38
- readonly guild: FakeGuild,
39
- rank: number,
40
- granted: bigint[],
41
- ) {
42
- this.roles = {
43
- highest: new FakeRole(`r${id}`, rank),
44
- add: async (role) => {
45
- this.actions.push(`add ${role.id}`);
46
- },
47
- };
48
- this.permissions = {
49
- has: (needed) =>
50
- granted.includes(Administrator) ||
51
- [needed].flat().every((p) => granted.includes(p)),
52
- };
53
- }
54
- async kick(): Promise<void> {
55
- this.actions.push("kick");
56
- }
57
- async setNickname(nickname: string | null): Promise<void> {
58
- this.actions.push(`nick ${nickname}`);
59
- }
60
- }
61
-
62
- class FakeGuild {
63
- readonly id = "1";
64
- ownerId = "999";
65
- readonly people = new Map<string, FakeMember>();
66
- readonly roleList = new Map<string, FakeRole>();
67
- readonly members = {
68
- fetch: async ({ user }: { user: string }) => {
69
- const member = this.people.get(user);
70
- if (!member) throw new Error("Unknown Member");
71
- return member;
72
- },
73
- fetchMe: async () => this.people.get(BOT),
74
- };
75
- readonly roles = {
76
- fetch: async (id: string) => this.roleList.get(id) ?? null,
77
- };
78
- readonly threadList = new Map<string, FakeThread>();
79
- readonly channels = {
80
- fetchActiveThreads: async () => ({ threads: this.threadList }),
81
- };
82
- add(id: string, rank: number, granted: bigint[]): FakeMember {
83
- const member = new FakeMember(id, this, rank, granted);
84
- this.people.set(id, member);
85
- return member;
86
- }
87
- }
88
-
89
- /** A thread whose permissions are its members' server permissions. */
90
- class FakeThread {
91
- edits: object[] = [];
92
- added: string[] = [];
93
- readonly parentId = "50";
94
- readonly guildId: string;
95
- archived = false;
96
- locked = false;
97
- readonly messageCount = 0;
98
- readonly createdAt = new Date(0);
99
- readonly members = {
100
- add: async (id: string) => {
101
- this.added.push(id);
102
- },
103
- };
104
- constructor(
105
- readonly id: string,
106
- readonly guild: FakeGuild,
107
- readonly type: ChannelType,
108
- readonly name = "t",
109
- ) {
110
- this.guildId = guild.id;
111
- }
112
- isThread() {
113
- return true;
114
- }
115
- permissionsFor(member: FakeMember) {
116
- return member.permissions;
117
- }
118
- async edit(options: object) {
119
- this.edits.push(options);
120
- }
121
- }
122
-
123
- /** A text channel whose new threads are recorded. */
124
- class FakeTextChannel {
125
- readonly id = "50";
126
- readonly type = ChannelType.GuildText;
127
- readonly guildId: string;
128
- created: object[] = [];
129
- readonly threads = {
130
- create: async (options: { name: string; type?: ChannelType }) => {
131
- this.created.push(options);
132
- return new FakeThread(
133
- "60",
134
- this.guild,
135
- options.type ?? ChannelType.PublicThread,
136
- options.name,
137
- );
138
- },
139
- };
140
- constructor(readonly guild: FakeGuild) {
141
- this.guildId = guild.id;
142
- }
143
- isThread() {
144
- return false;
145
- }
146
- permissionsFor(member: FakeMember) {
147
- return member.permissions;
148
- }
149
- }
150
-
151
- function setup(ownerGrants: bigint[], botGrants: bigint[]) {
152
- const guild = new FakeGuild();
153
- const owner = guild.add(OWNER, 10, ownerGrants);
154
- const bot = guild.add(BOT, 20, botGrants);
155
- const guest = guild.add("300", 5, []);
156
- const text = new FakeTextChannel(guild);
157
- const channels = new Map<string, unknown>([["50", text]]);
158
- const client = {
159
- isReady: () => true,
160
- channels: { fetch: async (id: string) => channels.get(id) ?? null },
161
- guilds: {
162
- fetch: async (id: string) => {
163
- if (id !== guild.id) throw new Error("Unknown Guild");
164
- return guild;
165
- },
166
- },
167
- } as unknown as Client;
168
- return {
169
- ops: new DiscordOwnerOps(client, OWNER),
170
- guild,
171
- owner,
172
- bot,
173
- guest,
174
- text,
175
- channels,
176
- };
177
- }
178
-
179
- const kick = (userId: string) => ({ guildId: "1", userId });
180
-
181
- describe("DiscordOwnerOps", () => {
182
- test("a kick runs when both hold the permission and the owner outranks the member", async () => {
183
- const { ops, guest } = setup([KickMembers], [KickMembers]);
184
- expect(await ops.run("discord_kick_member", kick("300"))).toEqual({
185
- userId: "300",
186
- kicked: true,
187
- });
188
- expect(guest.actions).toEqual(["kick"]);
189
- });
190
-
191
- test.each([
192
- ["the owner lacks it", [], [KickMembers], "OWNER_PERMISSION_MISSING"],
193
- ["the bot lacks it", [KickMembers], [], "BOT_PERMISSION_MISSING"],
194
- ])(
195
- "a kick is refused when %s",
196
- async (_name, ownerGrants, botGrants, code) => {
197
- const { ops, guest } = setup(ownerGrants, botGrants);
198
- await expect(ops.run("discord_kick_member", kick("300"))).rejects.toThrow(
199
- code,
200
- );
201
- expect(guest.actions).toEqual([]);
202
- },
203
- );
204
-
205
- test("a member at or above the owner's highest role is refused unless the owner owns the server", async () => {
206
- const { ops, guild, guest } = setup([KickMembers], [KickMembers]);
207
- guest.roles.highest = new FakeRole("rtop", 10);
208
- await expect(ops.run("discord_kick_member", kick("300"))).rejects.toThrow(
209
- "HIERARCHY",
210
- );
211
- guild.ownerId = OWNER;
212
- await ops.run("discord_kick_member", kick("300"));
213
- expect(guest.actions).toEqual(["kick"]);
214
- });
215
-
216
- test("the owner must be in the server", async () => {
217
- const { ops, guild } = setup([KickMembers], [KickMembers]);
218
- guild.people.delete(OWNER);
219
- await expect(ops.run("discord_kick_member", kick("300"))).rejects.toThrow(
220
- "OWNER_NOT_IN_SERVER",
221
- );
222
- });
223
-
224
- test("a role above the owner cannot be given", async () => {
225
- const { ops, guild, guest } = setup([ManageRoles], [ManageRoles]);
226
- guild.roleList.set("low", new FakeRole("low", 3));
227
- guild.roleList.set("high", new FakeRole("high", 15));
228
- const give = (roleId: string) =>
229
- ops.run("discord_add_member_role", {
230
- guildId: "1",
231
- userId: "300",
232
- roleId,
233
- });
234
- await give("low");
235
- await expect(give("high")).rejects.toThrow("HIERARCHY");
236
- expect(guest.actions).toEqual(["add low"]);
237
- });
238
-
239
- test("the owner may rename the assistant even though it ranks higher", async () => {
240
- const { ops, bot } = setup([ManageNicknames], [ManageNicknames]);
241
- await ops.run("discord_set_nickname", {
242
- guildId: "1",
243
- userId: BOT,
244
- nickname: "Pika the Crow",
245
- });
246
- expect(bot.actions).toEqual(["nick Pika the Crow"]);
247
- });
248
- });
249
-
250
- describe("threads", () => {
251
- const threadMaker = [
252
- ViewChannel,
253
- CreatePrivateThreads,
254
- SendMessagesInThreads,
255
- ];
256
-
257
- test("a private thread starts on its own and has the owner added", async () => {
258
- const { ops, text } = setup(threadMaker, threadMaker);
259
- expect(
260
- await ops.run("discord_create_thread", {
261
- channelId: "50",
262
- name: "plans",
263
- private: true,
264
- }),
265
- ).toEqual({ id: "60", name: "plans", parentId: "50", private: true });
266
- expect(text.created).toMatchObject([
267
- { name: "plans", type: ChannelType.PrivateThread },
268
- ]);
269
- });
270
-
271
- test("a private thread from a message is refused before anything is made", async () => {
272
- const { ops, text } = setup(threadMaker, threadMaker);
273
- await expect(
274
- ops.run("discord_create_thread", {
275
- channelId: "50",
276
- name: "x",
277
- messageId: "70",
278
- private: true,
279
- }),
280
- ).rejects.toThrow("INVALID_ARGUMENT");
281
- expect(text.created).toEqual([]);
282
- });
283
-
284
- test("editing a thread needs Manage Threads from both", async () => {
285
- const { ops, guild, channels } = setup([ViewChannel], [Administrator]);
286
- const thread = new FakeThread("61", guild, ChannelType.PublicThread);
287
- channels.set("61", thread);
288
- await expect(
289
- ops.run("discord_edit_thread", { threadId: "61", archived: true }),
290
- ).rejects.toThrow("OWNER_PERMISSION_MISSING");
291
- expect(thread.edits).toEqual([]);
292
-
293
- const both = setup([ViewChannel, ManageThreads], [Administrator]);
294
- const shared = new FakeThread("62", both.guild, ChannelType.PublicThread);
295
- both.channels.set("62", shared);
296
- await both.ops.run("discord_edit_thread", {
297
- threadId: "62",
298
- archived: true,
299
- });
300
- expect(shared.edits).toMatchObject([{ archived: true }]);
301
- });
302
-
303
- test("private threads are listed only when the owner may manage threads", async () => {
304
- const list = async (ownerGrants: bigint[]) => {
305
- const { ops, guild } = setup(ownerGrants, [Administrator]);
306
- guild.threadList.set(
307
- "63",
308
- new FakeThread("63", guild, ChannelType.PublicThread, "open"),
309
- );
310
- guild.threadList.set(
311
- "64",
312
- new FakeThread("64", guild, ChannelType.PrivateThread, "secret"),
313
- );
314
- const threads = (await ops.run("discord_list_threads", {
315
- guildId: "1",
316
- })) as { name: string }[];
317
- return threads.map((t) => t.name);
318
- };
319
- expect(await list([ViewChannel])).toEqual(["open"]);
320
- expect(await list([ViewChannel, ManageThreads])).toEqual([
321
- "open",
322
- "secret",
323
- ]);
324
- });
325
- });
@@ -1,21 +0,0 @@
1
- export const EXPRESSIONS = [
2
- "neutral",
3
- "happy",
4
- "thinking",
5
- "concerned",
6
- "apologetic",
7
- "surprised",
8
- "proud",
9
- "amused",
10
- "curious",
11
- "sympathetic",
12
- "playful",
13
- "determined",
14
- "sleepy",
15
- ] as const;
16
-
17
- export type Expression = (typeof EXPRESSIONS)[number];
18
-
19
- export function isExpression(value: string): value is Expression {
20
- return (EXPRESSIONS as readonly string[]).includes(value);
21
- }
@@ -1,32 +0,0 @@
1
- /** The built-in coordinator profiles; each owner connector adds one more at runtime. */
2
- export const STATIC_PROFILE_IDS = [
3
- "general",
4
- "research",
5
- "workspace",
6
- "discord",
7
- "coding",
8
- ] as const;
9
-
10
- export type StaticProfileId = (typeof STATIC_PROFILE_IDS)[number];
11
-
12
- /** A built-in profile id, or `connector:<name>` for an owner connector. */
13
- export type ProfileId = string;
14
-
15
- export const DEFAULT_PROFILE: StaticProfileId = "general";
16
-
17
- /** A coordinator profile: the same persona with a different set of active tools. */
18
- export interface Profile {
19
- id: ProfileId;
20
- /** What the judge reads to decide whether a message belongs to this profile. */
21
- criterion: string;
22
- /** Exact names of the assistant's own and Pi package tools active while this profile handles a turn. */
23
- tools: readonly string[];
24
- /** MCP gateway virtual servers whose every tool is also active. */
25
- mcpServers: readonly string[];
26
- }
27
-
28
- /** The profiles a turn can be routed to right now. */
29
- export interface ProfileCatalog {
30
- list(): Profile[];
31
- get(id: ProfileId): Profile | undefined;
32
- }
@@ -1,46 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { waitUntilIdle } from "./drain.ts";
3
- import { ChannelQueue } from "./routing/channel-queue.ts";
4
-
5
- describe("waitUntilIdle", () => {
6
- test("returns at once when nothing runs", async () => {
7
- expect(await waitUntilIdle({ busy: () => [] })).toEqual([]);
8
- });
9
-
10
- test("waits for queued work, including work that arrives while waiting", async () => {
11
- const queue = new ChannelQueue();
12
- const done: string[] = [];
13
- let release = () => {};
14
- void queue.run("discord:1", async () => {
15
- await new Promise<void>((r) => {
16
- release = r;
17
- });
18
- done.push("first");
19
- });
20
- const waiting = waitUntilIdle({ busy: () => queue.busy(), intervalMs: 5 });
21
- await Bun.sleep(20);
22
- void queue.run("discord:2", async () => {
23
- await Bun.sleep(30);
24
- done.push("second");
25
- });
26
- release();
27
- expect(await waiting).toEqual([]);
28
- expect(done).toEqual(["first", "second"]);
29
- expect(queue.busy()).toEqual([]);
30
- });
31
-
32
- test("gives up at the limit and says what is still busy", async () => {
33
- let clock = 0;
34
- const left = await waitUntilIdle({
35
- busy: () => ["discord:1", "discord:1"],
36
- limitMs: 3_000,
37
- intervalMs: 1_000,
38
- now: () => clock,
39
- sleep: async (ms) => {
40
- clock += ms;
41
- },
42
- });
43
- expect(left).toEqual(["discord:1", "discord:1"]);
44
- expect(clock).toBe(3_000);
45
- });
46
- });
@@ -1,81 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { EventBus } from "./events.ts";
3
- import { silentLogger } from "./log.ts";
4
- import type { EventHandlers } from "./plugin.ts";
5
-
6
- const turn = {
7
- agent: "infra",
8
- channel: "discord:1" as const,
9
- speaker: undefined,
10
- };
11
-
12
- describe("EventBus", () => {
13
- test("delivers to every plugin's handler in registration order", async () => {
14
- const log: string[] = [];
15
- const bus = new EventBus(silentLogger());
16
- const handlers = (name: string): EventHandlers => ({
17
- turnStarted: (event) => {
18
- log.push(`${name} ${event.agent}`);
19
- },
20
- });
21
- bus.link([
22
- { plugin: "a", events: handlers("a") },
23
- { plugin: "b", events: handlers("b") },
24
- ]);
25
- await bus.deliver("turnStarted", turn);
26
- expect(log).toEqual(["a infra", "b infra"]);
27
- });
28
-
29
- test("a handler that throws is logged with its plugin and never stops the others", async () => {
30
- const logged: { plugin: string; event: string }[] = [];
31
- const bus = new EventBus({
32
- ...silentLogger(),
33
- error: (fields: unknown) => {
34
- logged.push(fields as { plugin: string; event: string });
35
- },
36
- });
37
- const reached: string[] = [];
38
- bus.link([
39
- {
40
- plugin: "bad",
41
- events: {
42
- changed: () => {
43
- throw new Error("boom");
44
- },
45
- },
46
- },
47
- {
48
- plugin: "good",
49
- events: {
50
- changed: () => {
51
- reached.push("good");
52
- },
53
- },
54
- },
55
- ]);
56
- await bus.deliver("changed");
57
- expect(reached).toEqual(["good"]);
58
- expect(logged).toMatchObject([{ plugin: "bad", event: "changed" }]);
59
- });
60
-
61
- test("reports to the sink before the handlers are linked are dropped", async () => {
62
- const log: string[] = [];
63
- const bus = new EventBus(silentLogger());
64
- bus.sink.changed();
65
- bus.link([
66
- {
67
- plugin: "a",
68
- events: {
69
- changed: () => {
70
- log.push("changed");
71
- },
72
- },
73
- },
74
- ]);
75
- await Bun.sleep(1);
76
- expect(log).toEqual([]);
77
- bus.sink.changed();
78
- await Bun.sleep(1);
79
- expect(log).toEqual(["changed"]);
80
- });
81
- });
@@ -1,54 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { PluginError } from "./errors.ts";
3
- import { type HoldRule, holdChain } from "./holds.ts";
4
-
5
- /** A rule that holds every call to `tool`, saying `said`. */
6
- const rule = (name: string, tool: string, said: string): HoldRule => ({
7
- name,
8
- describe: (called) => (called === tool ? said : undefined),
9
- });
10
-
11
- describe("holdChain", () => {
12
- test("lets a call run when no rule holds it", () => {
13
- const holds = holdChain([rule("a", "x", "do x")]);
14
- expect(holds("y", {}, {})).toBeUndefined();
15
- expect(holdChain([])("x", {}, {})).toBeUndefined();
16
- });
17
-
18
- test("the first rule that describes a call holds it", () => {
19
- const holds = holdChain([
20
- rule("first", "x", "first says x"),
21
- rule("second", "x", "second says x"),
22
- rule("third", "y", "third says y"),
23
- ]);
24
- expect(holds("x", {}, {})).toBe("first says x");
25
- expect(holds("y", {}, {})).toBe("third says y");
26
- });
27
-
28
- test("hands each rule the call's input and the session's workspace", () => {
29
- const seen: unknown[] = [];
30
- const holds = holdChain([
31
- {
32
- name: "spy",
33
- describe: (tool, input, context) => {
34
- seen.push({ tool, input, context });
35
- return undefined;
36
- },
37
- },
38
- ]);
39
- holds("bash", { command: "ls" }, { workspace: "/work" });
40
- expect(seen).toEqual([
41
- {
42
- tool: "bash",
43
- input: { command: "ls" },
44
- context: { workspace: "/work" },
45
- },
46
- ]);
47
- });
48
-
49
- test("refuses two rules with one name", () => {
50
- expect(() =>
51
- holdChain([rule("same", "x", "a"), rule("same", "y", "b")]),
52
- ).toThrow(PluginError);
53
- });
54
- });