pi-roundtable 0.7.19 → 0.9.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.
- package/CHANGELOG.md +140 -0
- package/README.md +72 -15
- package/README.zh-TW.md +65 -13
- package/docs/migrating-0.8.md +132 -0
- package/docs/migration-0.9.md +247 -0
- package/docs/plugins.md +853 -81
- package/examples/channel-context.test.ts +88 -0
- package/examples/channel-context.ts +62 -0
- package/examples/discord-adapter.test.ts +42 -0
- package/examples/discord-adapter.ts +26 -0
- package/examples/echo-runtime.test.ts +1 -0
- package/examples/echo-runtime.ts +1 -1
- package/examples/fake-surface.test.ts +69 -6
- package/examples/fake-surface.ts +73 -7
- package/examples/headless.test.ts +72 -0
- package/examples/headless.ts +35 -0
- package/examples/http.test.ts +31 -1
- package/examples/http.ts +39 -0
- package/examples/prechecks.test.ts +1 -0
- package/examples/principals.test.ts +60 -0
- package/examples/principals.ts +48 -0
- package/examples/prompt.test.ts +1 -1
- package/examples/study-room.test.ts +29 -1
- package/examples/study-room.ts +16 -19
- package/examples/support-desk.test.ts +3 -1
- package/examples/tools.test.ts +6 -1
- package/package.json +7 -3
- package/src/cli/checks/database.ts +11 -0
- package/src/cli/checks/identity.ts +225 -0
- package/src/cli/checks/model.ts +21 -3
- package/src/cli/cli.ts +130 -5
- package/src/cli/config-edit.ts +83 -13
- package/src/cli/doctor.ts +28 -5
- package/src/cli/init.ts +9 -1
- package/src/cli/line-diff.ts +72 -0
- package/src/cli/principal.ts +412 -0
- package/src/cli/project.ts +33 -2
- package/src/cli/report.ts +12 -3
- package/src/cli/templates.ts +46 -14
- package/src/cli/upgrade-rewrite.ts +102 -0
- package/src/cli/upgrade-source.ts +430 -0
- package/src/cli/upgrade.ts +176 -0
- package/src/core/agents/agent-claim.ts +56 -34
- package/src/core/agents/agent-team-fixture.ts +6 -0
- package/src/core/agents/team-turns.ts +10 -1
- package/src/core/builtin/agent-server.ts +38 -101
- package/src/core/builtin/discord-admin.ts +4 -1
- package/src/core/builtin/discord.ts +71 -13
- package/src/core/builtin/modules.ts +197 -43
- package/src/core/builtin/stores.ts +40 -21
- package/src/core/config/access.ts +229 -0
- package/src/core/config/adapters.ts +120 -0
- package/src/core/config/config.ts +228 -94
- package/src/core/contract/channel-context.ts +53 -0
- package/src/core/contract/channels.ts +81 -9
- package/src/core/contract/providers.ts +3 -2
- package/src/core/contract/runtime.ts +31 -11
- package/src/core/contract/surface.ts +35 -6
- package/src/core/conversations/conversation-registry.ts +56 -0
- package/src/core/conversations/conversation-store.ts +126 -0
- package/src/core/conversations/conversations-plugin.ts +29 -0
- package/src/core/define-roundtable.ts +157 -57
- package/src/core/discord/agent-panel.ts +2 -2
- package/src/core/discord/card-audience.ts +148 -0
- package/src/core/discord/channel-context.ts +348 -0
- package/src/core/discord/connection.ts +15 -3
- package/src/core/discord/direct-channel.ts +54 -0
- package/src/core/discord/discord-adapter.ts +9 -0
- package/src/core/discord/discord-owners.ts +133 -0
- package/src/core/discord/discord-surface.ts +69 -14
- package/src/core/discord/fresh-marker.ts +24 -0
- package/src/core/discord/inbound-message.ts +21 -7
- package/src/core/discord/interaction-module.ts +11 -1
- package/src/core/discord/owner-cards-fixture.ts +80 -0
- package/src/core/discord/owner-cards.ts +58 -86
- package/src/core/discord/owner-command.ts +27 -13
- package/src/core/discord/stop-button.ts +4 -4
- package/src/core/domain/conversation.ts +10 -0
- package/src/core/domain/errors.ts +5 -0
- package/src/core/domain/ports.ts +33 -2
- package/src/core/domain/progress.ts +10 -0
- package/src/core/events.ts +1 -0
- package/src/core/host.ts +32 -10
- package/src/core/http/listeners.ts +174 -21
- package/src/core/http/websocket.ts +316 -0
- package/src/core/i18n/cards.ts +11 -9
- package/src/core/identity/access-policy.ts +227 -0
- package/src/core/identity/actor-facts.ts +65 -0
- package/src/core/identity/caching-principal-store.ts +111 -0
- package/src/core/identity/config-owner.ts +145 -0
- package/src/core/identity/contact.ts +27 -0
- package/src/core/identity/identity-plugin.ts +56 -0
- package/src/core/identity/identity-schema.ts +282 -0
- package/src/core/identity/identity-service.ts +489 -0
- package/src/core/identity/identity-view.ts +77 -0
- package/src/core/identity/plugin-identities.ts +109 -0
- package/src/core/identity/principal-reads.ts +38 -0
- package/src/core/identity/principal-store.ts +478 -0
- package/src/core/identity/principal-tables.ts +53 -0
- package/src/core/identity/seen-throttle.ts +57 -0
- package/src/core/identity/ulid.ts +36 -0
- package/src/core/identity.ts +34 -5
- package/src/core/interactions/prompts.ts +127 -0
- package/src/core/modules/background/background-turns.ts +56 -17
- package/src/core/modules/background/personal-target.ts +50 -0
- package/src/core/modules/delegation/delegate.ts +28 -14
- package/src/core/modules/delegation/delegator.ts +23 -7
- package/src/core/modules/memory/owner-memory.ts +123 -50
- package/src/core/modules/notify/notify.ts +53 -13
- package/src/core/modules/notify/session-notify.ts +110 -0
- package/src/core/modules/schedules/prechecks.ts +8 -2
- package/src/core/modules/schedules/schedule-prechecks.ts +4 -1
- package/src/core/modules/schedules/schedule-store.ts +51 -20
- package/src/core/modules/schedules/schedule-tools.ts +122 -12
- package/src/core/modules/schedules/scheduler.ts +43 -2
- package/src/core/modules/schedules/schedules.ts +43 -14
- package/src/core/ops/error-reporter.ts +24 -12
- package/src/core/plugin.ts +62 -0
- package/src/core/presence/direct-channels.ts +104 -0
- package/src/core/registry/contributions.ts +63 -3
- package/src/core/registry/providers.ts +1 -1
- package/src/core/routing/channel-router.ts +226 -17
- package/src/core/routing/conversation-turns.ts +85 -1
- package/src/core/routing/surface-port.ts +35 -2
- package/src/core/routing/system-turns.ts +29 -0
- package/src/core/runtime/bridge-guard.ts +225 -0
- package/src/core/runtime/compaction-tiers.ts +5 -1
- package/src/core/runtime/conversation-sessions.ts +124 -12
- package/src/core/runtime/extensions/ask-user.ts +1 -1
- package/src/core/runtime/extensions/confirmation-gate.ts +32 -10
- package/src/core/runtime/extensions/private-memory.ts +447 -0
- package/src/core/runtime/extensions/self-compact-guard.ts +11 -0
- package/src/core/runtime/pending-confirmation-store.ts +67 -4
- package/src/core/runtime/pi-agent-runtime.ts +143 -135
- package/src/core/runtime/preflight-tools.ts +47 -0
- package/src/core/runtime/prompt-slot.ts +6 -6
- package/src/core/runtime/reader-history.ts +129 -0
- package/src/core/runtime/runtime-plugin.ts +205 -0
- package/src/core/runtime/runtime-types.ts +40 -12
- package/src/core/runtime/session-conversation.ts +143 -0
- package/src/core/runtime/session-factory.ts +115 -15
- package/src/core/runtime/session-scope.ts +78 -0
- package/src/core/runtime/steerable-run.ts +9 -0
- package/src/core/runtime/tool-call-scope.ts +43 -0
- package/src/core/runtime/turn-answer.ts +30 -1
- package/src/core/runtime/turn-progress.ts +116 -0
- package/src/core/services.ts +42 -2
- package/src/core/sessions.ts +30 -0
- package/src/core/shared/unix-server.ts +35 -5
- package/src/core/speakers.ts +21 -19
- package/src/core/testing/background.ts +31 -0
- package/src/core/testing/fixture-database.ts +55 -0
- package/src/core/testing/map-identity.ts +76 -0
- package/src/core/testing/module-sessions.ts +134 -0
- package/src/core/testing/modules.ts +149 -38
- package/src/core/testing/owner.ts +17 -0
- package/src/core/testing/surface-contract.ts +460 -0
- package/src/core/testing/test-host.ts +100 -19
- package/src/core/testing/tool-set.ts +32 -0
- package/src/core/tool-set.snapshot.json +4 -4
- package/src/core/tool-tiers.ts +57 -3
- package/src/discord/index.ts +10 -0
- package/src/index.ts +65 -8
- package/src/kit/compaction.ts +13 -0
- package/src/kit/index.ts +10 -0
- package/src/kit/support.ts +1 -1
- package/src/testing.ts +96 -21
- package/templates/.env.example +1 -1
- package/templates/adapters/web/.env.example +24 -0
- package/templates/adapters/web/README.md +22 -0
- package/templates/adapters/web/package.json.tmpl +26 -0
- package/templates/adapters/web/persona/assistant.md +2 -0
- package/templates/adapters/web/roundtable.config.ts +59 -0
- package/templates/package.json.tmpl +1 -0
- package/templates/plugins/self-compact.ts +11 -0
- package/templates/roundtable.config.ts +14 -2
- package/src/core/domain/owner-prompts.ts +0 -45
|
@@ -13,17 +13,34 @@ import {
|
|
|
13
13
|
precheckPlugin,
|
|
14
14
|
scheduleStorePlugin,
|
|
15
15
|
} from "./builtin/stores.ts";
|
|
16
|
-
import {
|
|
16
|
+
import { warnDeprecations } from "./config/access.ts";
|
|
17
|
+
import {
|
|
18
|
+
type ResolvedConfig,
|
|
19
|
+
type RoundtableConfig,
|
|
20
|
+
resolveConfig,
|
|
21
|
+
} from "./config/config.ts";
|
|
22
|
+
import { conversationsPlugin } from "./conversations/conversations-plugin.ts";
|
|
17
23
|
import { ConfigError } from "./domain/errors.ts";
|
|
18
24
|
import { JudgeError } from "./errors.ts";
|
|
19
25
|
import type { RoundtableOptions } from "./host.ts";
|
|
20
26
|
import type { ListenerConfig } from "./http/listeners.ts";
|
|
27
|
+
import { identityPlugin } from "./identity/identity-plugin.ts";
|
|
21
28
|
import { type JudgeModel, piJudgeModel } from "./judging/model-judge.ts";
|
|
22
29
|
import { createLogger, type LogEntry, type Logger } from "./log.ts";
|
|
23
30
|
import { formatModelRef, type ModelRef } from "./models.ts";
|
|
24
31
|
import { ErrorReporter } from "./ops/error-reporter.ts";
|
|
25
32
|
import type { RoundtablePlugin } from "./plugin.ts";
|
|
26
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
BRIDGE_MEMORY_FIX,
|
|
35
|
+
bridgeMemoryProblem,
|
|
36
|
+
configuredCrowd,
|
|
37
|
+
onClaudeBridge,
|
|
38
|
+
} from "./runtime/bridge-guard.ts";
|
|
39
|
+
import {
|
|
40
|
+
type AgentSessionsSlot,
|
|
41
|
+
agentSessionsSlot,
|
|
42
|
+
runtimePlugin,
|
|
43
|
+
} from "./runtime/runtime-plugin.ts";
|
|
27
44
|
import { toolTiers } from "./tool-tiers.ts";
|
|
28
45
|
|
|
29
46
|
/** What `defineRoundtable` returns: give both to `new Roundtable(options, plugins)`. */
|
|
@@ -86,7 +103,7 @@ function judgeThrough(modelRuntime: ModelRuntime, ref: ModelRef): JudgeModel {
|
|
|
86
103
|
|
|
87
104
|
/** The listener `config.http` names: its unix socket when it has one, else its TCP port. */
|
|
88
105
|
function publicListener(
|
|
89
|
-
http:
|
|
106
|
+
http: NonNullable<ResolvedConfig["http"]>,
|
|
90
107
|
): ListenerConfig {
|
|
91
108
|
if (http.socketPath)
|
|
92
109
|
return {
|
|
@@ -101,11 +118,107 @@ function publicListener(
|
|
|
101
118
|
};
|
|
102
119
|
}
|
|
103
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The owner the Discord parts act for: the primary owner, by the Discord user id among their
|
|
123
|
+
* identities, until those parts follow each turn's principal.
|
|
124
|
+
*/
|
|
125
|
+
export function discordOwnerOf(
|
|
126
|
+
config: ResolvedConfig,
|
|
127
|
+
): ResolvedConfig["owner"] {
|
|
128
|
+
return {
|
|
129
|
+
...config.owner,
|
|
130
|
+
id: config.primaryOwner.discordId ?? config.owner.id,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The Discord bot and the agent server that lives there, each in its place in the plugin list. */
|
|
135
|
+
interface DiscordAssembly {
|
|
136
|
+
discord: RoundtablePlugin;
|
|
137
|
+
admin: RoundtablePlugin[];
|
|
138
|
+
skills: RoundtablePlugin[];
|
|
139
|
+
agentServer: RoundtablePlugin;
|
|
140
|
+
seeds: RoundtablePlugin;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function discordAssembly(
|
|
144
|
+
config: ResolvedConfig,
|
|
145
|
+
discord: NonNullable<ResolvedConfig["discord"]>,
|
|
146
|
+
shared: {
|
|
147
|
+
modelRuntime: ModelRuntime;
|
|
148
|
+
agentSessions: AgentSessionsSlot;
|
|
149
|
+
errorReporter: ErrorReporter | undefined;
|
|
150
|
+
},
|
|
151
|
+
): DiscordAssembly {
|
|
152
|
+
const { name } = config;
|
|
153
|
+
const owner = discordOwnerOf(config);
|
|
154
|
+
const sharedPrompt = config.prompts.shared
|
|
155
|
+
? readPrompt(config.prompts.shared, "prompts.shared")
|
|
156
|
+
: readPrompt(join(ASSETS, "prompts", "shared.md"), "prompts.shared");
|
|
157
|
+
const guest = config.prompts.guest
|
|
158
|
+
? readPrompt(config.prompts.guest, "prompts.guest")
|
|
159
|
+
: readPrompt(join(ASSETS, "prompts", "shared-guest.md"), "prompts.guest");
|
|
160
|
+
const { errorReporter } = shared;
|
|
161
|
+
return {
|
|
162
|
+
discord: discordPlugin({
|
|
163
|
+
token: discord.token,
|
|
164
|
+
ownerId: owner.id,
|
|
165
|
+
ownerName: owner.name,
|
|
166
|
+
rootCommand: discord.rootCommand,
|
|
167
|
+
dataDir: config.dataDir,
|
|
168
|
+
...(discord.refusalHint === undefined
|
|
169
|
+
? {}
|
|
170
|
+
: { refusalHint: discord.refusalHint }),
|
|
171
|
+
channelContext: discord.channelContext,
|
|
172
|
+
...(discord.freshMarker === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: { freshMarker: discord.freshMarker }),
|
|
175
|
+
}),
|
|
176
|
+
admin: discord.admin ? [discordAdminPlugin({ owner })] : [],
|
|
177
|
+
skills: config.skills
|
|
178
|
+
? [
|
|
179
|
+
skillsPlugin({
|
|
180
|
+
guildId: discord.guild,
|
|
181
|
+
reposDir: config.skills.reposDir ?? join(config.dataDir, "repos"),
|
|
182
|
+
writtenDir: join(config.dataDir, "skills"),
|
|
183
|
+
builtinDir: config.skills.builtinDir ?? join(ASSETS, "skills"),
|
|
184
|
+
}),
|
|
185
|
+
]
|
|
186
|
+
: [],
|
|
187
|
+
agentServer: agentServerPlugin({
|
|
188
|
+
guildId: discord.guild,
|
|
189
|
+
entryChannelId: discord.entryChannel,
|
|
190
|
+
owner,
|
|
191
|
+
assistant: name,
|
|
192
|
+
modelRuntime: shared.modelRuntime,
|
|
193
|
+
dataDir: config.dataDir,
|
|
194
|
+
model: config.model,
|
|
195
|
+
judgeThreshold: config.judge.threshold,
|
|
196
|
+
agents: shared.agentSessions,
|
|
197
|
+
workDir: config.workDir,
|
|
198
|
+
scratchDir: config.scratchDir,
|
|
199
|
+
shellUser: userInfo().username,
|
|
200
|
+
prompts: { shared: sharedPrompt, guest },
|
|
201
|
+
...(discord.agentMemory ? { memory: discord.agentMemory } : {}),
|
|
202
|
+
avatarListener: "public",
|
|
203
|
+
// resolveConfig refuses Discord without a public address.
|
|
204
|
+
avatarUrl: config.http?.publicUrl ?? "",
|
|
205
|
+
avatarReference: config.avatar ?? join(ASSETS, "neutral.png"),
|
|
206
|
+
// An ops agent's reports are the agent server's to deliver.
|
|
207
|
+
...(errorReporter && "agent" in errorReporter.destination
|
|
208
|
+
? { errorReporter }
|
|
209
|
+
: {}),
|
|
210
|
+
}),
|
|
211
|
+
seeds: seedsPlugin(config.agents),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
104
215
|
/**
|
|
105
216
|
* The host's options and the plugin list of a configured bot: the built-in plugins in their
|
|
106
217
|
* fixed order (each service one provides is read by the ones after it), then the operator's, then the scheduler, so a due schedule fires only once
|
|
107
218
|
* everything it reaches runs. The memory, Discord administration, and skills addons are left out
|
|
108
|
-
* when the configuration switches them off.
|
|
219
|
+
* when the configuration switches them off. Without `discord` the Discord plugins, the skills,
|
|
220
|
+
* the agent server and its seeds are left out, and the runtime still runs every turn of
|
|
221
|
+
* `context.turns`. Configuration mistakes stop here, naming the key and the fix.
|
|
109
222
|
*/
|
|
110
223
|
export async function defineRoundtable(
|
|
111
224
|
input: RoundtableConfig,
|
|
@@ -113,6 +226,7 @@ export async function defineRoundtable(
|
|
|
113
226
|
): Promise<DefinedRoundtable> {
|
|
114
227
|
const config = resolveConfig(input);
|
|
115
228
|
const { name, owner, discord } = config;
|
|
229
|
+
const crowd = configuredCrowd(config.access, config.plugins);
|
|
116
230
|
// Nothing here touches the process: the host applies the environment when it runs.
|
|
117
231
|
const modelRuntime =
|
|
118
232
|
overrides.modelRuntime ??
|
|
@@ -122,13 +236,13 @@ export async function defineRoundtable(
|
|
|
122
236
|
}));
|
|
123
237
|
const registry = new ModelRegistry(modelRuntime);
|
|
124
238
|
const errorReporter = config.ops
|
|
125
|
-
? new ErrorReporter({
|
|
239
|
+
? new ErrorReporter({ destination: config.ops, app: name })
|
|
126
240
|
: undefined;
|
|
127
241
|
const { errorSink } = overrides;
|
|
128
|
-
const logger =
|
|
242
|
+
const logger: Logger =
|
|
129
243
|
overrides.logger ??
|
|
130
244
|
createLogger(
|
|
131
|
-
|
|
245
|
+
config.slug,
|
|
132
246
|
errorReporter || errorSink
|
|
133
247
|
? (entry) => {
|
|
134
248
|
errorReporter?.record(entry);
|
|
@@ -136,17 +250,21 @@ export async function defineRoundtable(
|
|
|
136
250
|
}
|
|
137
251
|
: undefined,
|
|
138
252
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
?
|
|
149
|
-
|
|
253
|
+
if (config.memory && crowd && onClaudeBridge(config.model))
|
|
254
|
+
logger.warn(
|
|
255
|
+
{},
|
|
256
|
+
`${bridgeMemoryProblem(formatModelRef(config.model), crowd)} ${BRIDGE_MEMORY_FIX}`,
|
|
257
|
+
);
|
|
258
|
+
warnDeprecations(logger, config.deprecations);
|
|
259
|
+
// The agent server hands the runtime its per-agent settings once it sets up.
|
|
260
|
+
const agentSessions = agentSessionsSlot();
|
|
261
|
+
const assembly = discord
|
|
262
|
+
? discordAssembly(config, discord, {
|
|
263
|
+
modelRuntime,
|
|
264
|
+
agentSessions,
|
|
265
|
+
errorReporter,
|
|
266
|
+
})
|
|
267
|
+
: undefined;
|
|
150
268
|
return {
|
|
151
269
|
options: {
|
|
152
270
|
logger,
|
|
@@ -154,31 +272,25 @@ export async function defineRoundtable(
|
|
|
154
272
|
locale: config.locale,
|
|
155
273
|
timeZone: config.timeZone,
|
|
156
274
|
assistant: name,
|
|
157
|
-
rootCommand:
|
|
275
|
+
rootCommand: config.slug,
|
|
158
276
|
agentDir: config.agentDir,
|
|
159
277
|
},
|
|
160
278
|
database: { url: config.databaseUrl },
|
|
161
|
-
toolTiers: toolTiers(config.toolTiers),
|
|
162
|
-
listeners: [
|
|
279
|
+
toolTiers: toolTiers(config.toolTiers, logger),
|
|
280
|
+
listeners: [
|
|
281
|
+
...(config.http ? [publicListener(config.http)] : []),
|
|
282
|
+
...(overrides.listeners ?? []),
|
|
283
|
+
],
|
|
163
284
|
judgeModel: judgeThrough(modelRuntime, config.judge.model),
|
|
164
285
|
apiKey: (provider) => registry.getApiKeyForProvider(provider),
|
|
165
286
|
...(overrides.aborted ? { aborted: overrides.aborted } : {}),
|
|
166
287
|
},
|
|
167
288
|
plugins: [
|
|
289
|
+
identityPlugin({ rules: config.access, plugins: config.plugins }),
|
|
168
290
|
...(config.memory ? [memoryPlugin({ owner })] : []),
|
|
169
291
|
scheduleStorePlugin(),
|
|
170
292
|
precheckPlugin(),
|
|
171
|
-
|
|
172
|
-
token: discord.token,
|
|
173
|
-
ownerId: owner.id,
|
|
174
|
-
ownerName: owner.name,
|
|
175
|
-
speakers,
|
|
176
|
-
rootCommand: discord.rootCommand,
|
|
177
|
-
dataDir: config.dataDir,
|
|
178
|
-
...(discord.refusalHint === undefined
|
|
179
|
-
? {}
|
|
180
|
-
: { refusalHint: discord.refusalHint }),
|
|
181
|
-
}),
|
|
293
|
+
...(assembly ? [assembly.discord] : []),
|
|
182
294
|
modulesPlugin({
|
|
183
295
|
owner,
|
|
184
296
|
assistant: name,
|
|
@@ -186,42 +298,30 @@ export async function defineRoundtable(
|
|
|
186
298
|
agentDir: config.agentDir,
|
|
187
299
|
dataDir: config.dataDir,
|
|
188
300
|
delegation: config.delegation,
|
|
301
|
+
perPrincipal: config.background.perPrincipal,
|
|
302
|
+
// A conversation's reports are the modules' to deliver.
|
|
303
|
+
...(errorReporter && "conversation" in errorReporter.destination
|
|
304
|
+
? { errorReporter }
|
|
305
|
+
: {}),
|
|
189
306
|
}),
|
|
190
|
-
...(
|
|
191
|
-
...(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
guildId: discord.guild,
|
|
195
|
-
reposDir: config.skills.reposDir ?? join(config.dataDir, "repos"),
|
|
196
|
-
writtenDir: join(config.dataDir, "skills"),
|
|
197
|
-
builtinDir: config.skills.builtinDir ?? join(ASSETS, "skills"),
|
|
198
|
-
}),
|
|
199
|
-
]
|
|
200
|
-
: []),
|
|
201
|
-
agentServerPlugin({
|
|
202
|
-
guildId: discord.guild,
|
|
203
|
-
entryChannelId: discord.entryChannel,
|
|
307
|
+
...(assembly?.admin ?? []),
|
|
308
|
+
...(assembly?.skills ?? []),
|
|
309
|
+
conversationsPlugin(),
|
|
310
|
+
runtimePlugin({
|
|
204
311
|
owner,
|
|
205
|
-
assistant: name,
|
|
206
|
-
speakers,
|
|
207
312
|
modelRuntime,
|
|
208
313
|
agentDir: config.agentDir,
|
|
209
314
|
dataDir: config.dataDir,
|
|
210
315
|
model: config.model,
|
|
211
316
|
thinking: config.thinking,
|
|
212
317
|
judgeThreshold: config.judge.threshold,
|
|
213
|
-
|
|
214
|
-
scratchDir: config.scratchDir,
|
|
215
|
-
shellUser: userInfo().username,
|
|
216
|
-
prompts: { shared, guest },
|
|
217
|
-
avatarListener: "public",
|
|
218
|
-
avatarUrl: config.http.publicUrl,
|
|
219
|
-
avatarReference: config.avatar ?? join(ASSETS, "neutral.png"),
|
|
318
|
+
agents: agentSessions,
|
|
220
319
|
interimText: config.interimText,
|
|
221
320
|
interimPrimaryChars: config.interimPrimaryChars,
|
|
222
|
-
|
|
321
|
+
memory: config.memory,
|
|
322
|
+
...(crowd ? { crowd } : {}),
|
|
223
323
|
}),
|
|
224
|
-
|
|
324
|
+
...(assembly ? [assembly.agentServer, assembly.seeds] : []),
|
|
225
325
|
...config.plugins,
|
|
226
326
|
schedulerPlugin(),
|
|
227
327
|
],
|
|
@@ -47,7 +47,7 @@ export function agentPanel(options: AgentPanelOptions): AgentPanel {
|
|
|
47
47
|
interaction.isButton() &&
|
|
48
48
|
interaction.customId.startsWith(AGENT_BUTTON_PREFIX)
|
|
49
49
|
) {
|
|
50
|
-
if (!guard.
|
|
50
|
+
if (!(await guard.allows(interaction))) return true;
|
|
51
51
|
await guard.run(interaction, () => commands.button(interaction));
|
|
52
52
|
return true;
|
|
53
53
|
}
|
|
@@ -55,7 +55,7 @@ export function agentPanel(options: AgentPanelOptions): AgentPanel {
|
|
|
55
55
|
interaction.isModalSubmit() &&
|
|
56
56
|
interaction.customId.startsWith(AGENT_MODAL_PREFIX)
|
|
57
57
|
) {
|
|
58
|
-
if (!guard.
|
|
58
|
+
if (!(await guard.allows(interaction))) return true;
|
|
59
59
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
60
60
|
await guard.run(interaction, () => commands.submit(interaction));
|
|
61
61
|
return true;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { messages } from "../i18n/index.ts";
|
|
2
|
+
import type { PromptScope } from "../interactions/prompts.ts";
|
|
3
|
+
import { type Tier, tierAtLeast } from "../speakers.ts";
|
|
4
|
+
import type {
|
|
5
|
+
DiscordOwners,
|
|
6
|
+
DiscordOwnersOptions,
|
|
7
|
+
DiscordUser,
|
|
8
|
+
} from "./discord-owners.ts";
|
|
9
|
+
|
|
10
|
+
/** The people a card is for. */
|
|
11
|
+
export interface Audience {
|
|
12
|
+
/** Whether this user may answer, as they are when they press. */
|
|
13
|
+
allows(user: DiscordUser): Promise<boolean>;
|
|
14
|
+
/** The users the card mentions in a thread, in order. */
|
|
15
|
+
mentions: readonly string[];
|
|
16
|
+
/** Shown on the card; empty for none. */
|
|
17
|
+
note: string;
|
|
18
|
+
/** What someone else who presses it is told. */
|
|
19
|
+
refusal: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The primary owner, who answers every card the owners may, and who else holds a tier. */
|
|
23
|
+
export type CardAudienceOptions = DiscordOwnersOptions;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whom a card is for, by the prompt scope of the turn that asks. The speaker's principal answers
|
|
27
|
+
* their own card on any of their Discord identities that is still theirs when they press when,
|
|
28
|
+
* for an approval, they hold its tier, checked again then, and, for an owner-tier one, they are
|
|
29
|
+
* an owner; the card mentions the identity that spoke where it is one of those, or else all of
|
|
30
|
+
* them. Where the scope
|
|
31
|
+
* escalates to the owners, every owner with a Discord identity answers it too, and a card the
|
|
32
|
+
* speaker cannot answer, or whose principal has no Discord identity, is theirs alone; where it
|
|
33
|
+
* escalates to no one, that card goes to no one. Without a scope, a card is the owners'.
|
|
34
|
+
*/
|
|
35
|
+
export class CardAudiences {
|
|
36
|
+
readonly #options: CardAudienceOptions;
|
|
37
|
+
readonly #owners: DiscordOwners;
|
|
38
|
+
|
|
39
|
+
constructor(options: CardAudienceOptions, owners: DiscordOwners) {
|
|
40
|
+
this.#options = options;
|
|
41
|
+
this.#owners = owners;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Who may approve a call held at `minTier`; undefined when no one may. */
|
|
45
|
+
approval(
|
|
46
|
+
scope: PromptScope | undefined,
|
|
47
|
+
minTier: Tier,
|
|
48
|
+
): Promise<Audience | undefined> {
|
|
49
|
+
return this.#audience(scope, minTier, {
|
|
50
|
+
note: messages().cardApproversNote,
|
|
51
|
+
refusal: messages().cardApproversRefusal,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Who may answer a question; undefined when no one may. */
|
|
56
|
+
question(scope: PromptScope | undefined): Promise<Audience | undefined> {
|
|
57
|
+
return this.#audience(scope, undefined, {
|
|
58
|
+
note: messages().cardAskerNote,
|
|
59
|
+
refusal: messages().cardAskerRefusal,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async #audience(
|
|
64
|
+
scope: PromptScope | undefined,
|
|
65
|
+
minTier: Tier | undefined,
|
|
66
|
+
words: { note(...userIds: string[]): string; refusal: string },
|
|
67
|
+
): Promise<Audience | undefined> {
|
|
68
|
+
const { ownerId } = this.#options;
|
|
69
|
+
const escalates = scope?.escalate !== "none";
|
|
70
|
+
const own = scope && (await this.#ownIdentities(scope, minTier));
|
|
71
|
+
if (scope && own) {
|
|
72
|
+
const primary = own.includes(ownerId);
|
|
73
|
+
const mentions = own.includes(scope.speakerId) ? [scope.speakerId] : own;
|
|
74
|
+
return {
|
|
75
|
+
allows: async (user) => {
|
|
76
|
+
// Read the links again: one moved or unlinked since the card was posted is no longer theirs.
|
|
77
|
+
if (
|
|
78
|
+
own.includes(user.id) &&
|
|
79
|
+
(await this.#stillOwn(scope.principalId, user.id))
|
|
80
|
+
)
|
|
81
|
+
return (
|
|
82
|
+
(primary && user.id === ownerId) ||
|
|
83
|
+
this.#owners.holds(user, minTier, scope.principalId)
|
|
84
|
+
);
|
|
85
|
+
return escalates && this.#owners.isOwner(user);
|
|
86
|
+
},
|
|
87
|
+
mentions,
|
|
88
|
+
note: primary ? "" : words.note(...mentions),
|
|
89
|
+
refusal: primary ? messages().cardOwnerOnly : words.refusal,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (!escalates) return undefined;
|
|
93
|
+
return {
|
|
94
|
+
allows: (user) => this.#owners.isOwner(user),
|
|
95
|
+
mentions: await this.#owners.ids(),
|
|
96
|
+
note: "",
|
|
97
|
+
refusal: messages().cardOwnerOnly,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Whether the Discord user is still one of the principal's identities; false when that cannot be read. */
|
|
102
|
+
async #stillOwn(principalId: string, userId: string): Promise<boolean> {
|
|
103
|
+
return (
|
|
104
|
+
(await this.#discordIdentities(principalId))?.includes(userId) ?? false
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The principal's Discord user ids; undefined, and logged, when they cannot be read. */
|
|
109
|
+
async #discordIdentities(principalId: string): Promise<string[] | undefined> {
|
|
110
|
+
const { identity, logger } = this.#options;
|
|
111
|
+
if (!identity) return undefined;
|
|
112
|
+
try {
|
|
113
|
+
return (await identity.identities(principalId))
|
|
114
|
+
.filter((link) => link.provider === "discord")
|
|
115
|
+
.map((link) => link.subject);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.warn(
|
|
118
|
+
{ principal: principalId, err: error },
|
|
119
|
+
"could not read the speaker's identities; none counts as theirs, so their card is the owners'",
|
|
120
|
+
);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The Discord identities of the scope's principal when they may answer their own card: at the
|
|
127
|
+
* tier it needs and, for an owner-tier call, an owner; undefined when they may not, or have
|
|
128
|
+
* none.
|
|
129
|
+
*/
|
|
130
|
+
async #ownIdentities(
|
|
131
|
+
scope: PromptScope,
|
|
132
|
+
minTier: Tier | undefined,
|
|
133
|
+
): Promise<string[] | undefined> {
|
|
134
|
+
if (
|
|
135
|
+
!this.#options.identity ||
|
|
136
|
+
(minTier && !tierAtLeast(scope.tier, minTier))
|
|
137
|
+
)
|
|
138
|
+
return undefined;
|
|
139
|
+
// A turn whose tier defaulted to owner is not an owner's: its owner-tier call is the owners'.
|
|
140
|
+
if (
|
|
141
|
+
minTier === "owner" &&
|
|
142
|
+
!(await this.#owners.isOwnerPrincipal(scope.principalId))
|
|
143
|
+
)
|
|
144
|
+
return undefined;
|
|
145
|
+
const own = await this.#discordIdentities(scope.principalId);
|
|
146
|
+
return own && own.length > 0 ? own : undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|