pi-roundtable 0.8.0 → 0.9.1

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 (158) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +35 -11
  3. package/README.zh-TW.md +27 -10
  4. package/docs/migration-0.9.md +247 -0
  5. package/docs/plugins.md +618 -86
  6. package/examples/channel-context.test.ts +88 -0
  7. package/examples/channel-context.ts +62 -0
  8. package/examples/echo-runtime.test.ts +1 -0
  9. package/examples/fake-surface.test.ts +69 -6
  10. package/examples/fake-surface.ts +73 -7
  11. package/examples/headless.ts +5 -1
  12. package/examples/prechecks.test.ts +1 -0
  13. package/examples/principals.test.ts +60 -0
  14. package/examples/principals.ts +48 -0
  15. package/examples/prompt.test.ts +1 -1
  16. package/examples/study-room.test.ts +29 -1
  17. package/examples/study-room.ts +16 -19
  18. package/examples/support-desk.test.ts +3 -1
  19. package/examples/tools.test.ts +6 -1
  20. package/package.json +5 -2
  21. package/src/cli/checks/database.ts +11 -0
  22. package/src/cli/checks/identity.ts +225 -0
  23. package/src/cli/checks/model.ts +21 -3
  24. package/src/cli/cli.ts +115 -1
  25. package/src/cli/config-edit.ts +83 -13
  26. package/src/cli/doctor.ts +7 -0
  27. package/src/cli/line-diff.ts +72 -0
  28. package/src/cli/principal.ts +412 -0
  29. package/src/cli/project.ts +5 -0
  30. package/src/cli/report.ts +12 -3
  31. package/src/cli/upgrade-rewrite.ts +102 -0
  32. package/src/cli/upgrade-source.ts +430 -0
  33. package/src/cli/upgrade.ts +176 -0
  34. package/src/core/agents/agent-claim.ts +56 -34
  35. package/src/core/agents/team-turns.ts +8 -4
  36. package/src/core/builtin/agent-server.ts +9 -7
  37. package/src/core/builtin/discord-admin.ts +4 -1
  38. package/src/core/builtin/discord.ts +71 -13
  39. package/src/core/builtin/modules.ts +150 -86
  40. package/src/core/builtin/stores.ts +40 -21
  41. package/src/core/config/access.ts +229 -0
  42. package/src/core/config/adapters.ts +64 -1
  43. package/src/core/config/config.ts +87 -41
  44. package/src/core/contract/channel-context.ts +53 -0
  45. package/src/core/contract/channels.ts +82 -9
  46. package/src/core/contract/runtime.ts +23 -7
  47. package/src/core/contract/surface.ts +22 -6
  48. package/src/core/conversations/conversation-registry.ts +14 -0
  49. package/src/core/conversations/conversation-store.ts +13 -0
  50. package/src/core/define-roundtable.ts +42 -11
  51. package/src/core/discord/agent-panel.ts +2 -2
  52. package/src/core/discord/card-audience.ts +148 -0
  53. package/src/core/discord/channel-context.ts +348 -0
  54. package/src/core/discord/connection.ts +15 -3
  55. package/src/core/discord/direct-channel.ts +54 -0
  56. package/src/core/discord/discord-owners.ts +133 -0
  57. package/src/core/discord/discord-surface.ts +69 -14
  58. package/src/core/discord/fresh-marker.ts +24 -0
  59. package/src/core/discord/inbound-message.ts +21 -7
  60. package/src/core/discord/interaction-module.ts +11 -1
  61. package/src/core/discord/owner-cards-fixture.ts +80 -0
  62. package/src/core/discord/owner-cards.ts +58 -97
  63. package/src/core/discord/owner-command.ts +27 -13
  64. package/src/core/discord/stop-button.ts +4 -4
  65. package/src/core/domain/conversation.ts +7 -2
  66. package/src/core/domain/errors.ts +5 -0
  67. package/src/core/domain/ports.ts +27 -2
  68. package/src/core/drain.ts +56 -5
  69. package/src/core/errors.ts +13 -0
  70. package/src/core/host-background.ts +56 -0
  71. package/src/core/host.ts +52 -68
  72. package/src/core/i18n/cards.ts +8 -4
  73. package/src/core/i18n/conversation.ts +2 -0
  74. package/src/core/identity/access-policy.ts +227 -0
  75. package/src/core/identity/actor-facts.ts +65 -0
  76. package/src/core/identity/caching-principal-store.ts +111 -0
  77. package/src/core/identity/config-owner.ts +145 -0
  78. package/src/core/identity/contact.ts +27 -0
  79. package/src/core/identity/identity-plugin.ts +56 -0
  80. package/src/core/identity/identity-schema.ts +282 -0
  81. package/src/core/identity/identity-service.ts +489 -0
  82. package/src/core/identity/identity-view.ts +77 -0
  83. package/src/core/identity/plugin-identities.ts +109 -0
  84. package/src/core/identity/principal-reads.ts +38 -0
  85. package/src/core/identity/principal-store.ts +478 -0
  86. package/src/core/identity/principal-tables.ts +53 -0
  87. package/src/core/identity/seen-throttle.ts +57 -0
  88. package/src/core/identity/ulid.ts +36 -0
  89. package/src/core/identity.ts +34 -5
  90. package/src/core/interactions/prompts.ts +127 -0
  91. package/src/core/modules/background/background-turns.ts +56 -17
  92. package/src/core/modules/background/personal-target.ts +50 -0
  93. package/src/core/modules/delegation/delegate.ts +28 -14
  94. package/src/core/modules/delegation/delegator.ts +23 -7
  95. package/src/core/modules/memory/owner-memory.ts +123 -50
  96. package/src/core/modules/notify/notify.ts +53 -13
  97. package/src/core/modules/notify/session-notify.ts +110 -0
  98. package/src/core/modules/schedules/prechecks.ts +8 -2
  99. package/src/core/modules/schedules/schedule-prechecks.ts +4 -1
  100. package/src/core/modules/schedules/schedule-store.ts +51 -20
  101. package/src/core/modules/schedules/schedule-tools.ts +122 -12
  102. package/src/core/modules/schedules/scheduler.ts +56 -4
  103. package/src/core/modules/schedules/schedules.ts +43 -14
  104. package/src/core/plugin.ts +48 -0
  105. package/src/core/presence/direct-channels.ts +104 -0
  106. package/src/core/registry/contributions.ts +63 -3
  107. package/src/core/routing/channel-queue.ts +18 -1
  108. package/src/core/routing/channel-router.ts +274 -21
  109. package/src/core/routing/conversation-turns.ts +45 -10
  110. package/src/core/routing/surface-port.ts +32 -2
  111. package/src/core/routing/system-turns.ts +29 -0
  112. package/src/core/runtime/abort-guard.ts +58 -0
  113. package/src/core/runtime/bridge-guard.ts +225 -0
  114. package/src/core/runtime/compaction-tiers.ts +5 -1
  115. package/src/core/runtime/conversation-sessions.ts +139 -12
  116. package/src/core/runtime/extensions/ask-user.ts +1 -1
  117. package/src/core/runtime/extensions/confirmation-gate.ts +28 -14
  118. package/src/core/runtime/extensions/private-memory.ts +447 -0
  119. package/src/core/runtime/pending-confirmation-store.ts +30 -5
  120. package/src/core/runtime/pi-agent-runtime.ts +174 -190
  121. package/src/core/runtime/preflight-tools.ts +47 -0
  122. package/src/core/runtime/prompt-slot.ts +4 -4
  123. package/src/core/runtime/reader-history.ts +129 -0
  124. package/src/core/runtime/runtime-plugin.ts +51 -5
  125. package/src/core/runtime/runtime-types.ts +48 -12
  126. package/src/core/runtime/session-conversation.ts +143 -0
  127. package/src/core/runtime/session-factory.ts +115 -15
  128. package/src/core/runtime/session-scope.ts +78 -0
  129. package/src/core/runtime/steerable-run.ts +21 -3
  130. package/src/core/runtime/thinking-level.ts +47 -0
  131. package/src/core/runtime/tool-call-scope.ts +43 -0
  132. package/src/core/runtime/turn-answer.ts +30 -1
  133. package/src/core/runtime/turn-wrapup.ts +63 -0
  134. package/src/core/services.ts +27 -1
  135. package/src/core/sessions.ts +30 -0
  136. package/src/core/speakers.ts +21 -19
  137. package/src/core/testing/background.ts +31 -0
  138. package/src/core/testing/fixture-database.ts +55 -0
  139. package/src/core/testing/map-identity.ts +76 -0
  140. package/src/core/testing/module-sessions.ts +134 -0
  141. package/src/core/testing/modules.ts +108 -21
  142. package/src/core/testing/owner.ts +17 -0
  143. package/src/core/testing/surface-contract.ts +118 -41
  144. package/src/core/testing/test-host.ts +79 -16
  145. package/src/core/testing/tool-set.ts +32 -0
  146. package/src/core/tool-set.snapshot.json +4 -4
  147. package/src/core/tool-tiers.ts +57 -3
  148. package/src/discord/index.ts +8 -0
  149. package/src/index.ts +48 -8
  150. package/src/kit/compaction.ts +13 -0
  151. package/src/kit/index.ts +10 -0
  152. package/src/kit/support.ts +1 -1
  153. package/src/testing.ts +55 -16
  154. package/templates/.env.example +1 -1
  155. package/templates/adapters/web/README.md +7 -0
  156. package/templates/adapters/web/roundtable.config.ts +14 -10
  157. package/templates/roundtable.config.ts +12 -1
  158. package/src/core/domain/owner-prompts.ts +0 -45
package/src/index.ts CHANGED
@@ -20,8 +20,14 @@ export {
20
20
  AGENT_SERVER_PLUGIN,
21
21
  AGENT_TEAM_SERVICE,
22
22
  } from "./core/builtin/agent-server.ts";
23
+ export type {
24
+ AccessConfig,
25
+ AccessOwnerConfig,
26
+ AccessTierConfig,
27
+ } from "./core/config/access.ts";
23
28
  export type {
24
29
  AdapterConfig,
30
+ BackgroundConfig,
25
31
  DiscordConfig,
26
32
  Pronouns,
27
33
  RoundtableConfig,
@@ -30,6 +36,7 @@ export type {
30
36
  export type {
31
37
  Admission,
32
38
  AttachmentRef,
39
+ BackgroundRunsAs,
33
40
  BackgroundTarget,
34
41
  BackgroundTurn,
35
42
  ChannelClaim,
@@ -93,6 +100,7 @@ export {
93
100
  AgentRunError,
94
101
  ConfigError,
95
102
  DelegationError,
103
+ IdentityError,
96
104
  MemoryError,
97
105
  ScheduleError,
98
106
  } from "./core/domain/errors.ts";
@@ -101,17 +109,11 @@ export type {
101
109
  InterimPosts,
102
110
  InterimTextMode,
103
111
  } from "./core/domain/interim.ts";
104
- export type {
105
- Approval,
106
- AskOption,
107
- OwnerAnswer,
108
- OwnerPrompts,
109
- OwnerQuestion,
110
- } from "./core/domain/owner-prompts.ts";
111
- export type { TurnRequest } from "./core/domain/ports.ts";
112
+ export type { TurnConversation, TurnRequest } from "./core/domain/ports.ts";
112
113
  export type { TurnProgress } from "./core/domain/progress.ts";
113
114
  export type { DrainOptions } from "./core/drain.ts";
114
115
  export {
116
+ HostStoppingError,
115
117
  JudgeError,
116
118
  MigrationError,
117
119
  NotLinkedError,
@@ -133,9 +135,39 @@ export type {
133
135
  WebSocketSendResult,
134
136
  } from "./core/http/websocket.ts";
135
137
  export type { Locale } from "./core/i18n/index.ts";
138
+ export type {
139
+ AccessOwner,
140
+ AccessRules,
141
+ AccessTier,
142
+ } from "./core/identity/access-policy.ts";
143
+ export type { ActorFacts } from "./core/identity/actor-facts.ts";
144
+ export type { IdentityService } from "./core/identity/identity-service.ts";
145
+ export type {
146
+ IdentityLink,
147
+ IdentityRef,
148
+ LinkSource,
149
+ Principal,
150
+ RoleGrant,
151
+ RoleSource,
152
+ } from "./core/identity/principal-store.ts";
153
+ export { SYSTEM_PRINCIPAL } from "./core/identity/principal-store.ts";
154
+ export type {
155
+ Approval,
156
+ AskOption,
157
+ OwnerAnswer,
158
+ OwnerPrompts,
159
+ OwnerQuestion,
160
+ PromptScope,
161
+ Prompts,
162
+ } from "./core/interactions/prompts.ts";
163
+ export { promptScope } from "./core/interactions/prompts.ts";
136
164
  export type { JudgeModel } from "./core/judging/model-judge.ts";
137
165
  export type { LogEntry, LogFn, Logger } from "./core/log.ts";
138
166
  export type { ThinkingLevel, ThinkingSetting } from "./core/models.ts";
167
+ export {
168
+ PERSONAL_TARGET,
169
+ type PerPrincipalLimits,
170
+ } from "./core/modules/background/personal-target.ts";
139
171
  export type {
140
172
  DelegationJob,
141
173
  DelegationOutcome,
@@ -184,6 +216,7 @@ export type {
184
216
  LinkedSessions,
185
217
  Persona,
186
218
  PluginContext,
219
+ PluginIdentity,
187
220
  PromptSection,
188
221
  PromptTurn,
189
222
  RoundtablePlugin,
@@ -194,6 +227,11 @@ export type {
194
227
  TurnEvent,
195
228
  TurnProgressEvent,
196
229
  } from "./core/plugin.ts";
230
+ export type {
231
+ DirectChannelProvider,
232
+ DirectChannels,
233
+ DirectReach,
234
+ } from "./core/presence/direct-channels.ts";
197
235
  export {
198
236
  attachReplyFile,
199
237
  REPLY_FILE_LIMITS,
@@ -223,6 +261,7 @@ export {
223
261
  BACKGROUND_TURNS,
224
262
  CONVERSATIONS,
225
263
  DELEGATION,
264
+ IDENTITY,
226
265
  MEMORY,
227
266
  PRECHECKS,
228
267
  RUNTIME,
@@ -233,6 +272,7 @@ export type {
233
272
  AgentTurnScope,
234
273
  ChannelKey,
235
274
  SessionContext,
275
+ SessionConversation,
236
276
  SessionPlan,
237
277
  SessionTool,
238
278
  SessionToolSnapshot,
@@ -12,3 +12,16 @@ export {
12
12
  HARD_COMPACT_TOKENS,
13
13
  SOFT_COMPACT_TOKENS,
14
14
  } from "../core/runtime/compaction-tiers.ts";
15
+ export type { MemoryView } from "../core/runtime/extensions/private-memory.ts";
16
+ export {
17
+ hidesPrivateExchange,
18
+ memoryProjection,
19
+ privateCompaction,
20
+ summaryProjection,
21
+ } from "../core/runtime/extensions/private-memory.ts";
22
+ export {
23
+ bridgeHistoryHidesMemory,
24
+ carriesMemory,
25
+ MEMORY_TURN_ENTRY,
26
+ recordMemoryTurn,
27
+ } from "../core/runtime/reader-history.ts";
package/src/kit/index.ts CHANGED
@@ -18,13 +18,22 @@ export type {
18
18
  CompactionEngine,
19
19
  CompactionHistory,
20
20
  LatestCompaction,
21
+ MemoryView,
21
22
  } from "./compaction.ts";
22
23
  export {
24
+ bridgeHistoryHidesMemory,
23
25
  COMPACT_HEADROOM_TOKENS,
24
26
  CompactionTiers,
27
+ carriesMemory,
25
28
  compactionEngine,
26
29
  HARD_COMPACT_TOKENS,
30
+ hidesPrivateExchange,
31
+ MEMORY_TURN_ENTRY,
32
+ memoryProjection,
33
+ privateCompaction,
34
+ recordMemoryTurn,
27
35
  SOFT_COMPACT_TOKENS,
36
+ summaryProjection,
28
37
  } from "./compaction.ts";
29
38
  export { scrubDiagnostic } from "./diagnostics.ts";
30
39
  export type {
@@ -122,6 +131,7 @@ export type {
122
131
  DashboardBoard,
123
132
  DelegationWorker,
124
133
  GroupMessage,
134
+ Notifier,
125
135
  OwnerNotifier,
126
136
  SpeakerFacts,
127
137
  SpeakerPolicy,
@@ -13,7 +13,7 @@ export type {
13
13
  export type { Backlog, GroupMessage } from "../core/agents/agent-rules.ts";
14
14
  export type { AgentOps } from "../core/agents/agent-tools.ts";
15
15
  export type { ThreadHost } from "../core/discord/dispatch-threads.ts";
16
- export type { OwnerNotifier } from "../core/domain/ports.ts";
16
+ export type { Notifier, OwnerNotifier } from "../core/domain/ports.ts";
17
17
  export type { ThinkingPicker } from "../core/models.ts";
18
18
  export type { DelegationWorker } from "../core/modules/delegation/delegator.ts";
19
19
  export type { AgentChannelLookup } from "../core/modules/schedules/schedules.ts";
package/src/testing.ts CHANGED
@@ -3,8 +3,8 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
  import type { SQL } from "bun";
6
- import { agentClaim, OWNER_TARGET } from "./core/agents/agent-claim.ts";
7
- import { DISCORD } from "./core/builtin/discord.ts";
6
+ import { agentClaim } from "./core/agents/agent-claim.ts";
7
+ import { DISCORD, type DiscordServices } from "./core/builtin/discord.ts";
8
8
  import type {
9
9
  ConversationPort,
10
10
  InboundMessage,
@@ -24,7 +24,10 @@ import type {
24
24
  CommandRegistrar,
25
25
  InteractionContribution,
26
26
  } from "./core/discord/interaction-module.ts";
27
- import { OwnerGuard, ownerRootCommand } from "./core/discord/owner-command.ts";
27
+ import {
28
+ commandGuard,
29
+ ownerRootCommand,
30
+ } from "./core/discord/owner-command.ts";
28
31
  import type {
29
32
  PendingConfirmation,
30
33
  ReplyFile,
@@ -32,10 +35,12 @@ import type {
32
35
  import { NotLinkedError, PluginError } from "./core/errors.ts";
33
36
  import { EventBus } from "./core/events.ts";
34
37
  import type { HoldCheck } from "./core/holds.ts";
38
+ import { contactsOf } from "./core/identity/identity-view.ts";
35
39
  import type { OwnerIdentity } from "./core/identity.ts";
36
40
  import { ConfirmationJudge } from "./core/judging/confirmation-judge.ts";
37
41
  import { silentLogger } from "./core/log.ts";
38
42
  import { ConversationBackgroundTurns } from "./core/modules/background/background-turns.ts";
43
+ import { PERSONAL_TARGET } from "./core/modules/background/personal-target.ts";
39
44
  import type {
40
45
  Contribution,
41
46
  HostEnv,
@@ -69,10 +74,12 @@ import {
69
74
  type AgentServer,
70
75
  BACKGROUND_TURNS,
71
76
  CONVERSATIONS,
77
+ IDENTITY,
72
78
  RUNTIME,
73
79
  } from "./core/services.ts";
74
80
  import type { ChannelKey, SessionContext } from "./core/sessions.ts";
75
- import { type Speaker, speakerPolicy } from "./core/speakers.ts";
81
+ import { type Speaker, THE_SPEAKER } from "./core/speakers.ts";
82
+ import { mapIdentity } from "./core/testing/map-identity.ts";
76
83
  import { type ToolTierTable, toolTiers } from "./core/tool-tiers.ts";
77
84
 
78
85
  // Fixtures for plugin tests.
@@ -107,6 +114,7 @@ export type {
107
114
  } from "./core/testing/recording-logger.ts";
108
115
  export { recordingLogger } from "./core/testing/recording-logger.ts";
109
116
  export type {
117
+ SurfaceContractAnswerer,
110
118
  SurfaceContractFailure,
111
119
  SurfaceContractSubject,
112
120
  SurfaceObservation,
@@ -155,20 +163,30 @@ export interface FakeDiscord {
155
163
  /**
156
164
  * A `DISCORD` service for a plugin that adds slash commands, without a Discord connection: the
157
165
  * plugin's `commands.add` calls are recorded, and `compose()` shows the tree Discord would get.
158
- * `rootCommand` is the root command's name, `roundtable` by default. Only `commands` and `guard` are given; reading another member throws.
166
+ * `rootCommand` is the root command's name, `roundtable` by default. `channelContext` is what
167
+ * `DISCORD.channelContext` answers, by default no context, as on a host with it off. Only
168
+ * `commands`, `guard`, and `channelContext` are given; reading another member throws.
159
169
  */
160
170
  export function fakeDiscord(
161
- options: { ownerId?: string; rootCommand?: string } = {},
171
+ options: {
172
+ ownerId?: string;
173
+ rootCommand?: string;
174
+ channelContext?: DiscordServices["channelContext"];
175
+ } = {},
162
176
  ): FakeDiscord {
163
177
  const collection = new CommandCollection();
164
178
  const { rootCommand = "roundtable" } = options;
165
- const guard = new OwnerGuard(
166
- options.ownerId ?? "owner",
167
- silentLogger(),
168
- rootCommand,
169
- );
179
+ const guard = commandGuard({
180
+ ownerId: options.ownerId ?? "owner",
181
+ root: rootCommand,
182
+ logger: silentLogger(),
183
+ });
170
184
  return {
171
- service: servicePair(DISCORD, { commands: collection.registrar, guard }),
185
+ service: servicePair(DISCORD, {
186
+ commands: collection.registrar,
187
+ guard,
188
+ channelContext: options.channelContext ?? (async () => undefined),
189
+ }),
172
190
  commands: collection.registrar,
173
191
  guard,
174
192
  added: () => collection.added(),
@@ -201,7 +219,9 @@ export interface TestPluginOptions {
201
219
  * host supplies anyway: `BACKGROUND_TURNS`, the real background turns over the test's router,
202
220
  * and, once `AGENTS` is given, its `approvals` (the real confirmation judge over
203
221
  * `providers.judge`, when the test gives a judge). Giving `AGENTS` a `team` adds the agent
204
- * server's own claim to the router, answering as the host's would for the `owner`.
222
+ * server's own claim to the router, answering as the host's would for the `owner`. The router
223
+ * resolves who wrote each message through a given `IDENTITY`; without one only the `owner` is
224
+ * anyone, at the owner tier, their principal their id.
205
225
  */
206
226
  services?: readonly ServicePair[];
207
227
  /**
@@ -333,6 +353,7 @@ export async function testPlugin(
333
353
  (router ?? unlinked("conversations")).handle(message),
334
354
  background: async (turn) =>
335
355
  (router ?? unlinked("conversations")).background(turn),
356
+ runsAs: async (turn) => (router ?? unlinked("conversations")).runsAs(turn),
336
357
  target: (name) => (router ?? unlinked("conversations")).target(name),
337
358
  startFresh: async (channel) =>
338
359
  (router ?? unlinked("conversations")).startFresh(channel),
@@ -344,6 +365,8 @@ export async function testPlugin(
344
365
  owns: (channel) => (router ?? unlinked("conversations")).owns(channel),
345
366
  takesBackground: (channel) =>
346
367
  (router ?? unlinked("conversations")).takesBackground(channel),
368
+ takesSystemReports: (channel) =>
369
+ (router ?? unlinked("conversations")).takesSystemReports(channel),
347
370
  ...options.conversations,
348
371
  };
349
372
  const surfaces = surfacePort(() =>
@@ -463,7 +486,7 @@ export async function testPlugin(
463
486
  }),
464
487
  );
465
488
  services.checkRequires();
466
- const context: Omit<PluginContext, "services"> = {
489
+ const context: Omit<PluginContext, "services" | "directChannels"> = {
467
490
  logger,
468
491
  env,
469
492
  sessions: () => linked ?? unlinked("sessions"),
@@ -496,13 +519,17 @@ export async function testPlugin(
496
519
  const team = given.get(AGENTS.id)?.given;
497
520
  const agentServer = team && "team" in team ? services.get(AGENTS) : undefined;
498
521
  const attachmentDir = mkdtempSync(join(tmpdir(), "roundtable-test-plugin-"));
522
+ const identity = services.find(IDENTITY);
523
+ // Who wrote a message, and whom a background turn runs as: by the given IDENTITY, else the owner alone, at the owner tier.
524
+ const people = mapIdentity({ owners: [owner.id] });
499
525
  router = new ChannelRouter({
526
+ contacts: identity ? contactsOf(identity) : people,
527
+ principals: identity ?? people,
500
528
  claims: [
501
529
  ...(agentServer
502
530
  ? [
503
531
  agentClaim({
504
532
  owner,
505
- speakers: speakerPolicy({ owners: [owner.id] }),
506
533
  // SAFETY: the claim reads the concrete team's members (guildId, owns, answerOwner, answerGroup, answerBackground, startFresh), which a test that gives a team for it supplies.
507
534
  team: agentServer.team as unknown as Parameters<
508
535
  typeof agentClaim
@@ -514,6 +541,13 @@ export async function testPlugin(
514
541
  surface: surfaces,
515
542
  attachmentDir: () => attachmentDir,
516
543
  logger,
544
+ // As on a host: the given DISCORD's channel context, when the test gives one.
545
+ channelContext: async (message) =>
546
+ (
547
+ given.get(DISCORD.id)?.given as
548
+ | Partial<DiscordServices>
549
+ | undefined
550
+ )?.channelContext?.(message),
517
551
  }),
518
552
  ]
519
553
  : []),
@@ -521,7 +555,9 @@ export async function testPlugin(
521
555
  ],
522
556
  targets: (name) =>
523
557
  registry.backgroundTargets.find((t) => t.name === name) ??
524
- (agentServer && name === OWNER_TARGET.name ? OWNER_TARGET : undefined),
558
+ (agentServer && name === PERSONAL_TARGET.name
559
+ ? PERSONAL_TARGET
560
+ : undefined),
525
561
  queue,
526
562
  logger,
527
563
  ...(options.forwardJoinMs === undefined
@@ -586,6 +622,9 @@ export async function testPlugin(
586
622
  homeChannel: channel,
587
623
  turnChannel: channel,
588
624
  compaction: { wrap: (factory) => factory },
625
+ conversation: { visibility: "shared" },
626
+ addressee: THE_SPEAKER,
627
+ memory: "speaker",
589
628
  speaker: () => runOptions?.speaker,
590
629
  runTask: async () => {
591
630
  throw new Error("test session cannot run tasks");
@@ -7,7 +7,7 @@ DISCORD_TOKEN=
7
7
  DISCORD_GUILD_ID=
8
8
  DISCORD_ENTRY_CHANNEL_ID=
9
9
 
10
- # You: the one owner who can change everything (right-click your name, Copy User ID).
10
+ # You: the owner, who can change everything; OWNER_ID is your Discord user id (right-click your name, Copy User ID).
11
11
  OWNER_ID=
12
12
  OWNER_NAME=
13
13
 
@@ -9,6 +9,13 @@ People your OpenID Connect provider signs in chat with the assistant in private
9
9
 
10
10
  The chat is under `/chat`: a web page gets a one-time ticket from `POST /chat/tickets` with the person's token, and opens `/chat/socket` with it.
11
11
  The protocol is in `node_modules/pi-roundtable-webchat/README.md`.
12
+ Core `access` in `roundtable.config.ts` decides admission; token roles are prefixed with `web:role:` there.
13
+ New admitted users get principals, and linked Discord/web identities share private memory and quotas.
14
+ Link identities with `bunx roundtable principal link <principal> <identity>`; use `principal list` to inspect them.
15
+
16
+ The private inbox is `GET /chat/notices`, with `POST /chat/notices/<id>/read` to acknowledge an entry.
17
+ Online connections also receive additive `notice` frames.
18
+ Schedules and delegated reports can answer in their creator's private conversation; enable their tools in the persona selection only where intended and allowed by tier.
12
19
 
13
20
  `roundtable.config.ts` holds the settings and lists the plugins; `plugins/` holds yours, and `persona/assistant.md` is the assistant's prompt.
14
21
  Add another plugin with `bunx roundtable add plugin <name>`, and test it with `bun test`.
@@ -14,8 +14,17 @@ const list = (name: string): string[] =>
14
14
 
15
15
  export default {
16
16
  name: "Roundtable",
17
- // The operator who runs this host. People sign in through your OpenID Connect provider instead.
18
- owner: { id: "operator", name: env("OWNER_NAME") },
17
+ // The operator who runs this host, as the host's owner. People sign in through your OpenID
18
+ // Connect provider instead; core access maps their verified, surface-prefixed roles to tiers.
19
+ access: {
20
+ owners: [{ name: env("OWNER_NAME"), principal: "operator" }],
21
+ members: {
22
+ roles: list("CHAT_MEMBER_ROLES").map((role) => `web:role:${role}`),
23
+ },
24
+ admins: {
25
+ roles: list("CHAT_ADMIN_ROLES").map((role) => `web:role:${role}`),
26
+ },
27
+ },
19
28
  database: { url: env("DATABASE_URL") },
20
29
  // Paths are relative to the project directory, where the roundtable command runs.
21
30
  dataDir: "./data",
@@ -33,20 +42,15 @@ export default {
33
42
  issuers: [env("OIDC_ISSUER")],
34
43
  audiences: [env("OIDC_AUDIENCE")],
35
44
  }),
36
- // Members and admins come from the token's roles; owners only from speaker ids you list.
37
- access: {
38
- members: { roles: list("CHAT_MEMBER_ROLES") },
39
- admins: { roles: list("CHAT_ADMIN_ROLES") },
40
- },
41
45
  origins: list("CHAT_ORIGINS"),
42
46
  personas: [
43
47
  {
44
48
  kind: "assistant",
45
49
  label: "Assistant",
46
50
  prompt: () => readFileSync("./persona/assistant.md", "utf8"),
47
- // The tools of its turns, by name. Leave out schedule_* and delegate_task, which a web
48
- // conversation cannot hear back from, and web_search and fetch_content unless people
49
- // may make this server fetch any address, internal ones included.
51
+ // The tools of its turns, by name. Schedules, delegated reports and notify can reach
52
+ // this private conversation; add those tools only where intended and permitted by tier.
53
+ // Leave out web_search and fetch_content unless people may make the server fetch any address.
50
54
  selection: { tools: ["hello_greet"], groups: [] },
51
55
  },
52
56
  ],
@@ -8,7 +8,18 @@ const env = (name: string): string => process.env[name] ?? "";
8
8
 
9
9
  export default {
10
10
  name: "Roundtable",
11
- owner: { id: env("OWNER_ID"), name: env("OWNER_NAME") },
11
+ // Who the host serves. You are the owner: your principal and your Discord identity are your
12
+ // Discord user id. Add admins and members by Discord user or role, such as
13
+ // members: { roles: ["discord:role:<role id>"] }.
14
+ access: {
15
+ owners: [
16
+ {
17
+ name: env("OWNER_NAME"),
18
+ principal: env("OWNER_ID"),
19
+ identities: [`discord:${env("OWNER_ID")}`],
20
+ },
21
+ ],
22
+ },
12
23
  discord: {
13
24
  token: env("DISCORD_TOKEN"),
14
25
  guild: env("DISCORD_GUILD_ID"),
@@ -1,45 +0,0 @@
1
- import type { Tier } from "../speakers.ts";
2
- /**
3
- * The owner answering inside a running turn: a card in the turn's channel that only they may
4
- * answer. Unanswered cards expire; a stopped turn cancels its open cards.
5
- */
6
- export type Approval = "approved" | "declined" | "expired" | "cancelled";
7
-
8
- export interface AskOption {
9
- label: string;
10
- description?: string;
11
- }
12
-
13
- export interface OwnerQuestion {
14
- question: string;
15
- /** Up to 25; none means a free-text answer. */
16
- options: readonly AskOption[];
17
- /** More than one option may be chosen. */
18
- multi: boolean;
19
- /** They may write an answer of their own besides the options. */
20
- allowOther: boolean;
21
- }
22
-
23
- export interface OwnerAnswer {
24
- /** The chosen options' labels, in the order they were offered. */
25
- choices: string[];
26
- /** What they wrote himself, if anything. */
27
- text?: string;
28
- }
29
-
30
- export interface OwnerPrompts {
31
- /** Asks them to approve an action; `signal` cancels the card when the turn stops. */
32
- confirm(
33
- title: string,
34
- message: string,
35
- signal?: AbortSignal,
36
- /** The tier the turn's speaker needs to approve it themselves; the owner may always approve, and only the owner when absent. */
37
- minTier?: Tier,
38
- ): Promise<Approval>;
39
- /** Asks them a question; undefined when it expired or was cancelled. */
40
- ask(
41
- title: string,
42
- question: OwnerQuestion,
43
- signal?: AbortSignal,
44
- ): Promise<OwnerAnswer | undefined>;
45
- }