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,19 +1,22 @@
1
1
  import type { SQL } from "bun";
2
2
  import type { AgentSeed } from "./agents/agent-store.ts";
3
3
  import type {
4
+ BackgroundTarget,
4
5
  ChannelClaim,
5
6
  ConversationPort,
6
7
  QueuePort,
7
8
  } from "./contract/channels.ts";
8
- import type { InteractionContribution } from "./contract/discord.ts";
9
- import type { Providers } from "./contract/providers.ts";
9
+ import type { Providers, ResolvedProviders } from "./contract/providers.ts";
10
+ import type { ServiceKey, Services } from "./contract/services.ts";
11
+ import type { ChatSurface, SurfacePort } from "./contract/surface.ts";
10
12
  import type { Migration } from "./db/migrations.ts";
11
13
  import type { ToolContribution } from "./define.ts";
14
+ import { PluginError } from "./errors.ts";
12
15
  import type { HoldCheck, HoldRule } from "./holds.ts";
13
16
  import type { HttpRoute } from "./http/listeners.ts";
17
+ import type { Locale } from "./i18n/index.ts";
14
18
  import type { Logger } from "./log.ts";
15
- import type { ComposedInteractions } from "./registry/interactions.ts";
16
- import type { CoreAccess, CoreRegistry } from "./services.ts";
19
+ import type { ConversationTurns } from "./routing/conversation-turns.ts";
17
20
  import type {
18
21
  AgentTurnScope,
19
22
  ChannelKey,
@@ -21,7 +24,7 @@ import type {
21
24
  SessionTool,
22
25
  ToolSelection,
23
26
  } from "./sessions.ts";
24
- import type { Speaker } from "./speakers.ts";
27
+ import type { Speaker, Tier } from "./speakers.ts";
25
28
  import type { ToolTiers } from "./tool-tiers.ts";
26
29
 
27
30
  /**
@@ -31,17 +34,34 @@ import type { ToolTiers } from "./tool-tiers.ts";
31
34
  export interface Service {
32
35
  name: string;
33
36
  start?(): Promise<void> | void;
37
+ /**
38
+ * Starts once every service's `start` and the HTTP listeners are up, without holding up the
39
+ * boot: a failure is logged and heard as `serviceStarted` with `failed`, never fatal, and the
40
+ * other services' background starts still run. `stop` runs for it like for any started service.
41
+ */
42
+ startInBackground?(): Promise<void>;
34
43
  stop?(): Promise<void> | void;
35
44
  /** Work still running or waiting, one entry each; the shutdown drain waits until every list is empty. */
36
45
  busy?(): string[];
37
46
  }
38
47
 
39
- /** How the agent server's startup ended. */
40
- export type AgentServerOutcome = "ready" | "failed";
48
+ /** How a service's background start ended. */
49
+ export type ServiceStartOutcome = "ready" | "failed";
50
+
51
+ /** A service's `startInBackground` ended, as a handler hears of it. */
52
+ export interface ServiceStartedEvent {
53
+ /** The plugin that contributed the service. */
54
+ plugin: string;
55
+ service: string;
56
+ outcome: ServiceStartOutcome;
57
+ }
41
58
 
42
- /** One agent turn, as a handler hears of it. */
59
+ /** One turn, as a handler hears of it: an agent's, or a conversation run through `context.turns`. */
43
60
  export interface TurnEvent {
44
- agent: string;
61
+ /** The agent whose turn it is; absent for a conversation of another kind, such as a study room's. */
62
+ agent?: string;
63
+ /** The conversation's kind: "agent" for an agent's turn, else the kind the turn ran as, such as "owner" or "study". */
64
+ kind: string;
45
65
  /** The channel the turn runs in: the agent's own, or a group's. */
46
66
  channel: ChannelKey;
47
67
  /** Who the turn is for. */
@@ -57,11 +77,15 @@ export interface TurnEndEvent extends TurnEvent {
57
77
 
58
78
  /** What a plugin may react to; a handler that throws is logged and never stops the others. */
59
79
  export interface EventHandlers {
60
- /** Once the agent server has started, or failed to; the rest of the process runs either way. */
61
- agentServer?(outcome: AgentServerOutcome): Promise<void> | void;
62
- /** An agent's turn began. */
80
+ /**
81
+ * A service's background start ended, in the order the services were contributed; the rest
82
+ * of the process runs either way. The agent server's is the `AGENT_TEAM_SERVICE` service of
83
+ * the `AGENT_SERVER_PLUGIN` plugin.
84
+ */
85
+ serviceStarted?(event: ServiceStartedEvent): Promise<void> | void;
86
+ /** An agent's turn, or a turn run through `context.turns`, began. */
63
87
  turnStarted?(turn: TurnEvent): Promise<void> | void;
64
- /** An agent's turn ended, however it ended. */
88
+ /** A turn that began ended, however it ended. */
65
89
  turnEnded?(turn: TurnEndEvent): Promise<void> | void;
66
90
  /** The team changed: an agent or group was created, edited, arranged, archived, or started over. */
67
91
  changed?(): Promise<void> | void;
@@ -91,12 +115,24 @@ export interface PromptSection {
91
115
  build(turn: PromptTurn): string | undefined;
92
116
  }
93
117
 
118
+ /**
119
+ * The system prompt of every non-agent conversation of one kind. A conversation's kind is the
120
+ * string its claim returns from `startFresh` and passes as `kind` to `context.turns.run`.
121
+ */
122
+ export interface Persona {
123
+ /** The conversation kind the prompt is for; "agent" is reserved for the agent server's own. */
124
+ kind: string;
125
+ /**
126
+ * The prompt text, read when a conversation's session is made, so text from the message catalog
127
+ * is in the host's language.
128
+ */
129
+ prompt(): string;
130
+ }
131
+
94
132
  /** What a plugin adds to the process; a plugin that adds nothing is refused. */
95
133
  export interface Contribution {
96
134
  services?: Service[];
97
135
  events?: EventHandlers;
98
- /** Modules that answer Discord interactions, with their subcommands under the root command. */
99
- interactions?: InteractionContribution[];
100
136
  /** Handlers on the host's HTTP listeners. */
101
137
  http?: HttpRoute[];
102
138
  /** Rules deciding which tool calls wait for the owner's approval, asked in contribution order. */
@@ -107,10 +143,39 @@ export interface Contribution {
107
143
  sessionTools?: readonly SessionTool[];
108
144
  /** The channels whose conversations the plugin owns. */
109
145
  channels?: readonly ChannelClaim[];
146
+ /**
147
+ * Chat networks the plugin connects the host to, one per channel-key prefix. Each starts as a
148
+ * service named `surface:<prefix>` before the plugin's own services.
149
+ */
150
+ surfaces?: readonly ChatSurface[];
151
+ /**
152
+ * The system prompts of conversation kinds the plugin owns, one per kind. A plugin that owns the
153
+ * owner's own conversations contributes the `"owner"` kind's prompt; without one those
154
+ * conversations start with an empty system prompt.
155
+ */
156
+ personas?: readonly Persona[];
157
+ /**
158
+ * Tools startup refuses to run without, besides those each session tool requires: the host's
159
+ * preflight builds a session and fails when one of these names is not registered. Merged over
160
+ * every plugin, each name once.
161
+ */
162
+ requiredTools?: readonly string[];
163
+ /**
164
+ * The background targets the plugin's claims answer, one per name across every plugin: a
165
+ * schedule or delegated task names one, and a turn for a target nobody contributes is skipped.
166
+ */
167
+ backgroundTargets?: readonly BackgroundTarget[];
110
168
  /** Lines the agent server's dashboard shows under its title, such as links, in contribution order. */
111
169
  dashboard?: readonly string[];
112
170
  /** Tools the plugin adds, each with the lowest tier that may use it; built with `defineTool`. */
113
171
  tools?: readonly ToolContribution[];
172
+ /**
173
+ * The lowest tier that may use each of the plugin's raw session tools, by tool name: the tools
174
+ * its `sessionTools` extensions register. An operator's `toolTiers` still wins; a tool nobody
175
+ * names needs the owner. Two plugins naming one tool is a PluginError; use `tools` instead
176
+ * when `defineTool` builds the tool.
177
+ */
178
+ toolTiers?: Readonly<Record<string, Tier>>;
114
179
  /** Agents created on the first start; one already stored is never overwritten. */
115
180
  seeds?: readonly AgentSeed[];
116
181
  /** Sections added to every agent turn's system prompt, in contribution order. */
@@ -126,17 +191,21 @@ export interface Contribution {
126
191
  export const CONTRIBUTION_KEYS = [
127
192
  "services",
128
193
  "events",
129
- "interactions",
130
194
  "http",
131
195
  "holdRules",
132
196
  "piPackages",
133
197
  "sessionTools",
134
198
  "channels",
199
+ "surfaces",
200
+ "personas",
201
+ "backgroundTargets",
135
202
  "dashboard",
136
203
  "tools",
204
+ "toolTiers",
137
205
  "seeds",
138
206
  "prompt",
139
207
  "agentSelection",
208
+ "requiredTools",
140
209
  ] as const satisfies readonly (keyof Contribution)[];
141
210
 
142
211
  /** The session parts the host links from every plugin's contributions once all are set up. */
@@ -150,15 +219,30 @@ export interface LinkedSessions {
150
219
  seeds: readonly AgentSeed[];
151
220
  /** Every plugin's prompt sections, in contribution order. */
152
221
  prompt: readonly PromptSection[];
222
+ /** The prompt of a conversation kind, from the plugin that contributes its persona; undefined when none does. */
223
+ persona(kind: string): string | undefined;
224
+ /** Every tool name plugins require at startup, each once. */
225
+ requiredTools: readonly string[];
153
226
  /** The tools plugins defined for agents, by name. */
154
227
  agentTools: readonly string[];
155
228
  /** Every plugin's agent selection merged, read before each agent turn. */
156
229
  agentSelection(): ToolSelection;
157
230
  }
158
231
 
232
+ /** The host's own locale and time zone, as its plugins read them. */
233
+ export interface HostEnv {
234
+ readonly locale: Locale;
235
+ /** The IANA zone the host was configured with. */
236
+ readonly timeZone: string;
237
+ /** The current instant. */
238
+ now(): Date;
239
+ }
240
+
159
241
  /** What the host gives every plugin. */
160
242
  export interface PluginContext {
161
243
  logger: Logger;
244
+ /** The host's locale and time zone, fixed for the run. */
245
+ env: HostEnv;
162
246
  /** The linked session parts; throws NotLinkedError when called during setup. */
163
247
  sessions(): LinkedSessions;
164
248
  /** The one channel queue every conversation and channel operation shares. */
@@ -169,12 +253,19 @@ export interface PluginContext {
169
253
  events: EventSink;
170
254
  /** The claimed channels' conversations; calls during setup throw NotLinkedError. */
171
255
  conversations: ConversationPort;
256
+ /** Every contributed chat surface, chosen by a channel's prefix; calls during setup throw NotLinkedError. */
257
+ surfaces: SurfacePort;
258
+ /** Runs conversation turns of any kind over the runtime and the surfaces; calls during setup throw NotLinkedError. */
259
+ turns: ConversationTurns;
172
260
  /** The host's one connection pool, migrated before any setup; throws PluginError without a database. */
173
261
  database(): SQL;
174
- /** What the built-in plugins provide, for the plugins registered after them; throws PluginError until provided. */
175
- core: CoreAccess & { provide: CoreRegistry["provide"] };
176
- /** Each provider slot, from the plugin that fills it or the core's default. */
177
- providers: Providers;
262
+ /**
263
+ * The services plugins provide to each other, read by key: `services.get(SCHEDULES)`. Reading one
264
+ * before its plugin has set up throws a PluginError naming the plugin to register first.
265
+ */
266
+ services: Services;
267
+ /** Each provider slot, from the plugin that fills it or the core's default; `filled` names the slots a plugin fills. */
268
+ providers: ResolvedProviders;
178
269
  /** Every plugin's dashboard lines, in contribution order; throws NotLinkedError during setup. */
179
270
  dashboard(): readonly string[];
180
271
  }
@@ -185,16 +276,129 @@ export interface RoundtablePlugin {
185
276
  migrations?: readonly Migration[];
186
277
  /** The provider slots the plugin fills, resolved before any setup; two plugins cannot fill one slot. */
187
278
  providers?: Partial<Providers>;
279
+ /**
280
+ * The services this plugin's setup provides with `context.services.provide`; the host reads the
281
+ * list before any setup, and refuses the plugin when setup returns without providing one.
282
+ */
283
+ provides?: readonly ServiceKey<unknown>[];
284
+ /**
285
+ * Services this plugin replaces. The host drops the plugin that provides them and sets this one
286
+ * up where it stood, so it may read what the plugins before that place provide and nothing
287
+ * after. It refuses a key no other plugin provides, a key two plugins replace, a service
288
+ * replaced but not listed in this plugin's `provides`, and a partial replacement, where the
289
+ * dropped plugin provides a service this one does not replace.
290
+ */
291
+ replaces?: readonly ServiceKey<unknown>[];
188
292
  setup(context: PluginContext): Promise<Contribution> | Contribution;
189
293
  /**
190
- * Runs once every plugin is set up and linked, before the commands are handed over or any
191
- * service starts; a failure stops the boot, so a broken setup never reaches Discord.
294
+ * Runs once every plugin is set up and linked, before any service starts; a failure stops the
295
+ * boot, so a broken setup never reaches Discord. The Discord plugin composes the slash commands
296
+ * here, so a plugin adds its own from setup.
192
297
  */
193
298
  preflight?(): Promise<void> | void;
194
- /** Receives the composed slash commands before any service starts, since Discord registers them as it connects. */
195
- useCommands?(composed: ComposedInteractions): void;
196
- /** Starts the agent server, in the background once every service has started; only one plugin has it. */
197
- agentServer?(): Promise<void>;
198
- /** Stops a running turn of a channel the plugin's conversations serve; true when one was running. */
199
- stopTurn?(channel: ChannelKey): boolean;
299
+ }
300
+
301
+ /**
302
+ * The plugin fields of 0.1.0 that are gone, each with what replaces it. The host refuses a
303
+ * plugin that still has one, so a plugin written for 0.1.0 fails at once instead of being ignored.
304
+ */
305
+ const REMOVED_PLUGIN_FIELDS = {
306
+ useCommands:
307
+ "the composed slash commands go to the Discord plugin's surface, not to a plugin; add the commands from setup with context.services.get(DISCORD).commands.add(...), with DISCORD from pi-roundtable/discord",
308
+ agentServer:
309
+ "give the plugin a service with startInBackground, and hear how it ended in a serviceStarted event handler",
310
+ stopTurn:
311
+ "put stop(channel) on the channel claim that owns the channel; the router asks only the owning claim",
312
+ } as const;
313
+
314
+ /** The contribution parts of 0.1.0 that are gone, each with what replaces it. */
315
+ const REMOVED_PARTS = {
316
+ interactions:
317
+ "slash commands belong to the Discord plugin now; add them from setup with context.services.get(DISCORD).commands.add({ module, rootOptions }), with DISCORD from pi-roundtable/discord",
318
+ } as const;
319
+
320
+ /** The chat surface methods of 0.1.0 that are gone, each with what replaces it. */
321
+ const REMOVED_SURFACE_METHODS = {
322
+ useCommands:
323
+ "the host no longer composes slash commands or hands them to a surface; the Discord plugin composes them, and a plugin adds its own with context.services.get(DISCORD).commands.add(...) from pi-roundtable/discord",
324
+ } as const;
325
+
326
+ const REMOVED_EVENT = {
327
+ agentServer:
328
+ "hear serviceStarted instead, which names the plugin and service whose background start ended",
329
+ } as const;
330
+
331
+ /**
332
+ * The context one plugin's setup gets: the host's parts and that plugin's view of the services.
333
+ * `context.core` of 0.1.0 is gone; reading it throws a PluginError naming `context.services`, where
334
+ * a plugin written for 0.1.0 meets it on its first line, instead of getting `undefined`.
335
+ */
336
+ export function pluginContext(
337
+ plugin: RoundtablePlugin,
338
+ base: Omit<PluginContext, "services">,
339
+ services: Services,
340
+ ): PluginContext {
341
+ // Every line a plugin logs carries its name.
342
+ const context = {
343
+ ...base,
344
+ logger: base.logger.child({ plugin: plugin.name }),
345
+ services,
346
+ };
347
+ Object.defineProperty(context, "core", {
348
+ enumerable: false,
349
+ get() {
350
+ throw new PluginError(
351
+ `plugin ${plugin.name}: context.core was removed in 0.2.0; read a service with context.services.get(KEY), for example services.get(SCHEDULES), and provide one with services.provide(KEY, value) after listing KEY in the plugin's provides.`,
352
+ );
353
+ },
354
+ });
355
+ return context;
356
+ }
357
+
358
+ /**
359
+ * Refuses a plugin that still has a field of 0.1.0 that is gone, naming what replaces it, so it
360
+ * fails where it is written or when the host links it instead of being ignored.
361
+ */
362
+ export function refuseRemovedFields(plugin: RoundtablePlugin): void {
363
+ for (const [field, replacement] of Object.entries(REMOVED_PLUGIN_FIELDS))
364
+ if (field in plugin)
365
+ throw new PluginError(
366
+ `plugin ${plugin.name}: "${field}" was removed in 0.2.0; ${replacement}.`,
367
+ );
368
+ }
369
+
370
+ /** Refuses a contribution part of 0.1.0 that is gone, naming what replaces it. */
371
+ export function refuseRemovedParts(
372
+ plugin: string,
373
+ contribution: Contribution,
374
+ ): void {
375
+ for (const [part, replacement] of Object.entries(REMOVED_PARTS))
376
+ if (part in contribution)
377
+ throw new PluginError(
378
+ `plugin ${plugin}: the "${part}" part was removed in 0.2.0; ${replacement}.`,
379
+ );
380
+ }
381
+
382
+ /** Refuses a chat surface that still has a method of 0.1.0 that is gone, naming what replaces it. */
383
+ export function refuseRemovedSurfaceMethods(
384
+ plugin: string,
385
+ surface: ChatSurface,
386
+ ): void {
387
+ for (const [method, replacement] of Object.entries(REMOVED_SURFACE_METHODS))
388
+ if (method in surface)
389
+ throw new PluginError(
390
+ `plugin ${plugin}: surface ${surface.surface} has ${method}, which was removed in 0.2.0; ${replacement}.`,
391
+ );
392
+ }
393
+
394
+ /** Refuses an event handler of 0.1.0 that is gone, naming what replaces it. */
395
+ export function refuseRemovedEvents(
396
+ plugin: string,
397
+ events: EventHandlers,
398
+ ): void {
399
+ for (const [event, replacement] of Object.entries(REMOVED_EVENT))
400
+ if (event in events)
401
+ throw new PluginError(
402
+ `plugin ${plugin}: the "${event}" event was removed in 0.2.0; ${replacement}.`,
403
+ );
200
404
  }