pi-roundtable 0.1.0 → 0.2.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.
- package/CHANGELOG.md +148 -0
- package/README.md +10 -6
- package/README.zh-TW.md +144 -0
- package/docs/plugins.md +1448 -66
- package/examples/echo-runtime.test.ts +122 -0
- package/examples/echo-runtime.ts +107 -0
- package/examples/events.test.ts +1 -0
- package/examples/events.ts +2 -2
- package/examples/fake-surface.test.ts +180 -0
- package/examples/fake-surface.ts +109 -0
- package/examples/interactions.test.ts +12 -3
- package/examples/interactions.ts +19 -19
- package/examples/shared-services.test.ts +74 -0
- package/examples/shared-services.ts +60 -0
- package/examples/study-room.test.ts +69 -0
- package/examples/study-room.ts +52 -0
- package/examples/support-desk.test.ts +34 -0
- package/examples/support-desk.ts +40 -0
- package/package.json +11 -3
- package/src/cli/checks/database.ts +11 -30
- package/src/cli/checks/images.ts +19 -0
- package/src/cli/doctor.ts +6 -0
- package/src/cli/main.ts +2 -1
- package/src/core/agents/agent-claim.ts +42 -16
- package/src/core/agents/agent-messages.ts +4 -4
- package/src/core/agents/agent-ports.ts +8 -21
- package/src/core/agents/agent-prompt.ts +3 -1
- package/src/core/agents/agent-store.ts +6 -4
- package/src/core/agents/agent-team-fixture.ts +389 -0
- package/src/core/agents/agent-team.ts +26 -25
- package/src/core/agents/agent-tools.ts +34 -15
- package/src/core/agents/avatar-studio.ts +40 -6
- package/src/core/agents/fallback-avatar.ts +85 -0
- package/src/core/agents/team-editing.ts +49 -10
- package/src/core/agents/team-keys.ts +22 -9
- package/src/core/agents/team-layout.ts +4 -3
- package/src/core/agents/team-lifecycle.ts +28 -2
- package/src/core/agents/team-options.ts +9 -5
- package/src/core/agents/team-status.ts +4 -4
- package/src/core/agents/team-text.ts +8 -6
- package/src/core/agents/team-turn-types.ts +4 -4
- package/src/core/agents/team-turns.ts +6 -5
- package/src/core/builtin/agent-server.ts +187 -121
- package/src/core/builtin/discord-admin.ts +41 -0
- package/src/core/builtin/discord.ts +65 -40
- package/src/core/builtin/modules.ts +67 -58
- package/src/core/builtin/session-tool.ts +24 -0
- package/src/core/builtin/skills.ts +72 -0
- package/src/core/builtin/stores.ts +55 -31
- package/src/core/config/config.ts +45 -4
- package/src/core/config/schema.ts +6 -0
- package/src/core/contract/channels.ts +54 -14
- package/src/core/contract/providers.ts +17 -0
- package/src/core/contract/runtime.ts +138 -0
- package/src/core/contract/services.ts +45 -0
- package/src/core/contract/surface.ts +88 -0
- package/src/core/db/migrations.ts +118 -3
- package/src/core/define-roundtable.ts +70 -46
- package/src/core/define.ts +2 -1
- package/src/core/discord/agent-commands.ts +39 -14
- package/src/core/discord/agent-panel.ts +66 -0
- package/src/core/discord/channel-executor.ts +6 -3
- package/src/core/discord/channel-operations.ts +14 -11
- package/src/core/discord/command-collection.ts +46 -0
- package/src/core/{registry/interactions.ts → discord/compose-commands.ts} +5 -5
- package/src/core/discord/connection.ts +35 -0
- package/src/core/discord/discord-surface.ts +32 -94
- package/src/core/discord/dispatch-threads.ts +2 -2
- package/src/core/discord/inbound-message.ts +99 -0
- package/src/core/discord/interaction-module.ts +57 -1
- package/src/core/discord/owner-cards.ts +4 -4
- package/src/core/discord/owner-command.ts +44 -11
- package/src/core/discord/owner-discord.ts +2 -2
- package/src/core/discord/schedule-commands.ts +21 -18
- package/src/core/discord/stop-button.ts +44 -1
- package/src/core/domain/attachment.ts +12 -8
- package/src/core/domain/conversation.ts +3 -4
- package/src/core/domain/owner-prompts.ts +5 -5
- package/src/core/domain/ports.ts +9 -53
- package/src/core/freeze.ts +9 -0
- package/src/core/host.ts +255 -82
- package/src/core/http/listeners.ts +55 -20
- package/src/core/i18n/agent-panel.ts +4 -0
- package/src/core/i18n/index.ts +12 -2
- package/src/core/i18n/owner.ts +7 -1
- package/src/core/i18n/schedules.ts +12 -6
- package/src/core/identity.ts +1 -1
- package/src/core/judging/effort-judge.ts +25 -3
- package/src/core/log.ts +21 -3
- package/src/core/models.ts +2 -2
- package/src/core/modules/background/background-turns.ts +6 -4
- package/src/core/modules/delegation/delegate.ts +3 -2
- package/src/core/modules/delegation/delegator.ts +15 -13
- package/src/core/modules/delegation/{sol-worker.ts → web-research-worker.ts} +6 -6
- package/src/core/modules/discord-admin/discord-admin.ts +6 -6
- package/src/core/modules/host-shell/shell-policy.ts +8 -3
- package/src/core/modules/memory/owner-memory-store.ts +27 -25
- package/src/core/modules/memory/owner-memory.ts +9 -13
- package/src/core/modules/schedules/recurrence.ts +5 -3
- package/src/core/modules/schedules/schedule-store.ts +11 -11
- package/src/core/modules/schedules/schedule-tools.ts +22 -20
- package/src/core/modules/schedules/scheduler.ts +7 -2
- package/src/core/modules/schedules/schedules.ts +9 -3
- package/src/core/modules/skills/skill-registry.ts +3 -2
- package/src/core/modules/skills/skill-store.ts +2 -15
- package/src/core/modules/skills/skill-tools.ts +10 -4
- package/src/core/ops/error-reporter.ts +1 -1
- package/src/core/plugin.ts +232 -28
- package/src/core/registry/contributions.ts +173 -14
- package/src/core/registry/providers.ts +35 -5
- package/src/core/registry/services.ts +229 -0
- package/src/core/routing/channel-queue.ts +5 -0
- package/src/core/routing/channel-router.ts +21 -7
- package/src/core/routing/conversation-turns.ts +139 -0
- package/src/core/routing/message-text.ts +9 -2
- package/src/core/routing/surface-port.ts +39 -0
- package/src/core/runtime/conversation-sessions.ts +4 -3
- package/src/core/runtime/extensions/confirmation-fixture.ts +11 -0
- package/src/core/runtime/extensions/confirmation-gate.ts +11 -9
- package/src/core/runtime/mcp.ts +1 -1
- package/src/core/runtime/pending-confirmation-store.ts +10 -12
- package/src/core/runtime/pi-agent-runtime.ts +9 -6
- package/src/core/runtime/prompt-slot.ts +8 -3
- package/src/core/runtime/runtime-types.ts +9 -35
- package/src/core/runtime/session-factory.ts +21 -5
- package/src/core/runtime/text-tools.ts +1 -3
- package/src/core/services.ts +238 -90
- package/src/core/sessions.ts +3 -2
- package/src/core/shared/{profile-tools.ts → active-tools.ts} +2 -2
- package/src/core/shared/delegate-tool.ts +4 -3
- package/src/core/shared/schedule-tools.ts +22 -13
- package/src/core/shared/session-messages.ts +1 -1
- package/src/core/speakers.ts +6 -38
- package/src/core/testing/database.ts +1 -7
- package/src/core/testing/eager-catalog.ts +30 -0
- package/src/core/testing/locale.ts +23 -5
- package/src/core/testing/modules.ts +105 -41
- package/src/core/testing/test-host.ts +279 -0
- package/src/core/testing/tool-set.ts +64 -0
- package/src/core/time.ts +6 -1
- package/src/core/tool-set.snapshot.json +288 -0
- package/src/core/tool-tiers.ts +17 -57
- package/src/discord/index.ts +64 -0
- package/src/index.ts +181 -10
- package/src/kit/channels.ts +14 -0
- package/src/kit/domain.ts +18 -0
- package/src/kit/holds.ts +4 -0
- package/src/kit/index.ts +129 -0
- package/src/kit/judging.ts +10 -0
- package/src/kit/memory.ts +3 -0
- package/src/kit/mirror.ts +19 -0
- package/src/kit/presentation.ts +7 -0
- package/src/kit/shell.ts +7 -0
- package/src/kit/skills.ts +8 -0
- package/src/kit/support.ts +21 -0
- package/src/kit/threads.ts +7 -0
- package/src/kit/tools.ts +14 -0
- package/src/kit/worker.ts +16 -0
- package/src/testing.ts +394 -34
- package/examples/guide.test.ts +0 -52
- package/src/cli/add-plugin.test.ts +0 -107
- package/src/cli/checks/basic.test.ts +0 -276
- package/src/cli/checks/database.test.ts +0 -117
- package/src/cli/checks/discord.test.ts +0 -200
- package/src/cli/cli.test.ts +0 -122
- package/src/cli/config-edit.test.ts +0 -130
- package/src/cli/doctor.test.ts +0 -184
- package/src/cli/init.test.ts +0 -127
- package/src/cli/size.test.ts +0 -9
- package/src/cli/templates.test.ts +0 -129
- package/src/cli/testing/fixtures.ts +0 -106
- package/src/core/agents/agent-claim.test.ts +0 -142
- package/src/core/agents/agent-dashboard.test.ts +0 -120
- package/src/core/agents/agent-guild.test.ts +0 -124
- package/src/core/agents/agent-store.test.ts +0 -205
- package/src/core/agents/avatar-studio.test.ts +0 -88
- package/src/core/agents/group-round.test.ts +0 -143
- package/src/core/agents/owner-identity.test.ts +0 -172
- package/src/core/agents/team-turns.test.ts +0 -278
- package/src/core/attachments/attachments.test.ts +0 -85
- package/src/core/boundary.test.ts +0 -45
- package/src/core/builtin/modules.test.ts +0 -127
- package/src/core/config/config.test.ts +0 -135
- package/src/core/contract/discord.ts +0 -33
- package/src/core/db/migrations.test.ts +0 -237
- package/src/core/define-roundtable.test.ts +0 -134
- package/src/core/define.test.ts +0 -144
- package/src/core/discord/agent-commands.test.ts +0 -58
- package/src/core/discord/agent-discord.test.ts +0 -70
- package/src/core/discord/dispatch-thread-host.test.ts +0 -114
- package/src/core/discord/dispatch-threads.test.ts +0 -94
- package/src/core/discord/owner-cards.test.ts +0 -435
- package/src/core/discord/owner-discord.test.ts +0 -325
- package/src/core/domain/expression.ts +0 -21
- package/src/core/domain/profile.ts +0 -32
- package/src/core/drain.test.ts +0 -46
- package/src/core/events.test.ts +0 -81
- package/src/core/holds.test.ts +0 -54
- package/src/core/host.test.ts +0 -536
- package/src/core/http/listeners.test.ts +0 -164
- package/src/core/i18n/i18n.test.ts +0 -139
- package/src/core/identity.test.ts +0 -18
- package/src/core/judging/effort-judge.test.ts +0 -112
- package/src/core/judging/model-judge.test.ts +0 -126
- package/src/core/modules/delegation/delegator.test.ts +0 -170
- package/src/core/modules/memory/owner-memory-store.test.ts +0 -244
- package/src/core/modules/schedules/schedule.test.ts +0 -358
- package/src/core/modules/skills/skill-kind.test.ts +0 -55
- package/src/core/modules/skills/skill-registry.test.ts +0 -376
- package/src/core/ops/error-reporter.test.ts +0 -283
- package/src/core/presentation/card-cadence.ts +0 -41
- package/src/core/presentation/presentation.test.ts +0 -166
- package/src/core/public-entry.test.ts +0 -59
- package/src/core/registry/contributions.test.ts +0 -215
- package/src/core/registry/interactions.test.ts +0 -87
- package/src/core/registry/providers.test.ts +0 -103
- package/src/core/routing/channel-queue.test.ts +0 -31
- package/src/core/routing/channel-router.test.ts +0 -326
- package/src/core/routing/conversation-kind.ts +0 -14
- package/src/core/routing/settle-turn.test.ts +0 -21
- package/src/core/runtime/compaction-tiers.test.ts +0 -227
- package/src/core/runtime/extensions/ask-user.test.ts +0 -114
- package/src/core/runtime/extensions/self-compact-guard.test.ts +0 -50
- package/src/core/runtime/pending-confirmation-store.test.ts +0 -52
- package/src/core/runtime/session-archive.test.ts +0 -20
- package/src/core/runtime/steerable-run.test.ts +0 -264
- package/src/core/runtime/text-tools.test.ts +0 -110
- package/src/core/runtime/turn-answer.test.ts +0 -72
- package/src/core/runtime/worker-task.test.ts +0 -82
- package/src/core/services.test.ts +0 -28
- package/src/core/sessions.test.ts +0 -85
- package/src/core/shared/unix-server.ts +0 -18
- package/src/core/size.test.ts +0 -9
- package/src/core/speakers.test.ts +0 -78
- package/src/core/testing/file-size.ts +0 -34
- package/src/core/time.test.ts +0 -166
- package/src/core/tool-tiers.test.ts +0 -117
- package/src/entries.test.ts +0 -112
- package/src/testing.test.ts +0 -156
|
@@ -8,24 +8,30 @@ import {
|
|
|
8
8
|
type Interaction,
|
|
9
9
|
type Message,
|
|
10
10
|
MessageFlags,
|
|
11
|
-
MessageReferenceType,
|
|
12
11
|
Partials,
|
|
13
12
|
} from "discord.js";
|
|
14
|
-
import type {
|
|
13
|
+
import type { AgentChannels, DashboardBoard } from "../agents/agent-ports.ts";
|
|
14
|
+
import type { ChatSurface } from "../contract/surface.ts";
|
|
15
15
|
import type {
|
|
16
16
|
ChannelKey,
|
|
17
17
|
InboundMessage,
|
|
18
18
|
OutboundReply,
|
|
19
19
|
} from "../domain/conversation.ts";
|
|
20
|
-
import type {
|
|
20
|
+
import type { OwnerPrompts } from "../domain/owner-prompts.ts";
|
|
21
|
+
import type { OwnerNotifier } from "../domain/ports.ts";
|
|
21
22
|
import { messages } from "../i18n/index.ts";
|
|
22
23
|
import type { Logger } from "../log.ts";
|
|
24
|
+
import type { OwnerOperations } from "../modules/discord-admin/discord-admin.ts";
|
|
23
25
|
import { splitReply } from "../presentation/reply-splitter.ts";
|
|
24
|
-
import type {
|
|
26
|
+
import type { Speaker } from "../speakers.ts";
|
|
25
27
|
import { DiscordAgentChannels, DiscordDashboard } from "./agent-discord.ts";
|
|
26
28
|
import { discordChannelExecutor } from "./channel-executor.ts";
|
|
27
29
|
import type { ChannelExecutor } from "./channel-operations.ts";
|
|
30
|
+
import type { ComposedCommands } from "./compose-commands.ts";
|
|
31
|
+
import type { ChannelInfo, DiscordConnection } from "./connection.ts";
|
|
28
32
|
import { DiscordThreadHost } from "./dispatch-thread-host.ts";
|
|
33
|
+
import type { ThreadHost } from "./dispatch-threads.ts";
|
|
34
|
+
import { toInbound } from "./inbound-message.ts";
|
|
29
35
|
import type { CardChannel } from "./owner-cards.ts";
|
|
30
36
|
import { DiscordOwnerOps } from "./owner-discord.ts";
|
|
31
37
|
import { stopPanel } from "./stop-button.ts";
|
|
@@ -34,9 +40,6 @@ const PREFIX = "discord:";
|
|
|
34
40
|
/** Discord's error code for a channel that does not exist or cannot be seen. */
|
|
35
41
|
const UNKNOWN_CHANNEL = 10003;
|
|
36
42
|
|
|
37
|
-
export type ChannelInfo =
|
|
38
|
-
| { kind: "dm"; name: string }
|
|
39
|
-
| { kind: "guild"; name: string; guild: string; guildId: string };
|
|
40
43
|
const TYPING_REFRESH_MS = 8_000;
|
|
41
44
|
/** A turn shorter than this shows no stop button, so quick answers do not flicker one. */
|
|
42
45
|
const STOP_PANEL_DELAY_MS = 5_000;
|
|
@@ -46,6 +49,8 @@ export interface DiscordSurfaceOptions {
|
|
|
46
49
|
ownerId: string;
|
|
47
50
|
/** How the owner is named in audit-log reasons and refusals. */
|
|
48
51
|
ownerName: string;
|
|
52
|
+
/** The owner's cards in a Discord channel; the surface hands them out as its `prompts`. */
|
|
53
|
+
prompts: (channel: ChannelKey, speaker?: Speaker) => OwnerPrompts | undefined;
|
|
49
54
|
logger: Logger;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -55,7 +60,10 @@ export interface DiscordSurfaceOptions {
|
|
|
55
60
|
* server's channels answer the owner without a mention; elsewhere the assistant still answers only
|
|
56
61
|
* DMs and messages that mention it.
|
|
57
62
|
*/
|
|
58
|
-
export class DiscordSurface
|
|
63
|
+
export class DiscordSurface
|
|
64
|
+
implements ChatSurface, OwnerNotifier, DiscordConnection
|
|
65
|
+
{
|
|
66
|
+
readonly surface = "discord";
|
|
59
67
|
readonly #options: DiscordSurfaceOptions;
|
|
60
68
|
readonly #client = new Client({
|
|
61
69
|
intents: [
|
|
@@ -68,15 +76,15 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
68
76
|
allowedMentions: { parse: [] },
|
|
69
77
|
});
|
|
70
78
|
|
|
71
|
-
#interactions:
|
|
79
|
+
#interactions: ComposedCommands = { commands: [], modules: [] };
|
|
72
80
|
|
|
73
81
|
constructor(options: DiscordSurfaceOptions) {
|
|
74
82
|
this.#options = options;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/** Slash commands and the modules that answer them; the commands are registered globally at startup. */
|
|
78
|
-
|
|
79
|
-
this.#interactions =
|
|
86
|
+
setCommands(composed: ComposedCommands): void {
|
|
87
|
+
this.#interactions = composed;
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/** Channel operations for outside agents; undefined until the connection is ready. */
|
|
@@ -87,7 +95,7 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
/** Discord reading and management for the owner's agent; calls fail until the connection is ready. */
|
|
90
|
-
|
|
98
|
+
ownerOperations(): OwnerOperations {
|
|
91
99
|
return new DiscordOwnerOps(
|
|
92
100
|
this.#client,
|
|
93
101
|
this.#options.ownerId,
|
|
@@ -96,17 +104,17 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
/** The agent server's channels and webhooks. */
|
|
99
|
-
agentChannels(guildId: string):
|
|
107
|
+
agentChannels(guildId: string): AgentChannels {
|
|
100
108
|
return new DiscordAgentChannels(this.#client, guildId);
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/** Threads for background dispatches, in the channel that started each. */
|
|
104
|
-
threadHost():
|
|
112
|
+
threadHost(): ThreadHost {
|
|
105
113
|
return new DiscordThreadHost(this.#client);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
/** The agent server's `#dashboard` and its pinned status message. */
|
|
109
|
-
agentDashboard(guildId: string):
|
|
117
|
+
agentDashboard(guildId: string): DashboardBoard {
|
|
110
118
|
return new DiscordDashboard(this.#client, guildId, this.#options.logger);
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -145,10 +153,18 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
145
153
|
this.#client.on(Events.ChannelDelete, (channel) => handler(channel.id));
|
|
146
154
|
}
|
|
147
155
|
|
|
156
|
+
/** The owner's approval and question cards in a Discord channel. */
|
|
157
|
+
prompts(channel: ChannelKey, speaker?: Speaker): OwnerPrompts | undefined {
|
|
158
|
+
return this.#options.prompts(channel, speaker);
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
async start(onMessage: (message: InboundMessage) => void): Promise<void> {
|
|
149
162
|
const { logger, token } = this.#options;
|
|
150
163
|
this.#client.on(Events.MessageCreate, (message) => {
|
|
151
|
-
|
|
164
|
+
toInbound(message, {
|
|
165
|
+
botId: this.#client.user?.id,
|
|
166
|
+
applicationId: this.#client.application?.id,
|
|
167
|
+
})
|
|
152
168
|
.then((inbound) => {
|
|
153
169
|
if (inbound) onMessage(inbound);
|
|
154
170
|
})
|
|
@@ -317,73 +333,6 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
317
333
|
await this.#client.destroy();
|
|
318
334
|
}
|
|
319
335
|
|
|
320
|
-
async #toInbound(message: Message): Promise<InboundMessage | undefined> {
|
|
321
|
-
const botId = this.#client.user?.id;
|
|
322
|
-
if (!botId || message.author.id === botId) return undefined;
|
|
323
|
-
const mention = new RegExp(`<@!?${botId}>`, "g");
|
|
324
|
-
const reference = message.reference;
|
|
325
|
-
// A forward also has a reference, to a message elsewhere; its copy is the snapshot.
|
|
326
|
-
const forward =
|
|
327
|
-
reference?.type === MessageReferenceType.Forward
|
|
328
|
-
? message.messageSnapshots.first()
|
|
329
|
-
: undefined;
|
|
330
|
-
const referenced =
|
|
331
|
-
reference?.messageId && !forward
|
|
332
|
-
? await message.fetchReference().catch(() => undefined)
|
|
333
|
-
: undefined;
|
|
334
|
-
return {
|
|
335
|
-
channel: `${PREFIX}${message.channelId}`,
|
|
336
|
-
messageId: message.id,
|
|
337
|
-
authorId: message.author.id,
|
|
338
|
-
authorName:
|
|
339
|
-
message.member?.displayName ??
|
|
340
|
-
message.author.globalName ??
|
|
341
|
-
message.author.username,
|
|
342
|
-
authorIsBot: message.author.bot,
|
|
343
|
-
...(message.member
|
|
344
|
-
? { authorRoleIds: [...message.member.roles.cache.keys()] }
|
|
345
|
-
: {}),
|
|
346
|
-
...(message.webhookId
|
|
347
|
-
? {
|
|
348
|
-
webhookId: message.webhookId,
|
|
349
|
-
// Webhooks the assistant created carry its application on every message they post.
|
|
350
|
-
ownWebhook:
|
|
351
|
-
message.applicationId !== null &&
|
|
352
|
-
message.applicationId === (this.#client.application?.id ?? botId),
|
|
353
|
-
}
|
|
354
|
-
: {}),
|
|
355
|
-
isDirect: message.channel.isDMBased(),
|
|
356
|
-
...(message.guildId ? { guildId: message.guildId } : {}),
|
|
357
|
-
mentionsBot: message.mentions.users.has(botId),
|
|
358
|
-
repliesToBot: referenced?.author.id === botId,
|
|
359
|
-
text: message.content.replace(mention, "").trim(),
|
|
360
|
-
attachments: [
|
|
361
|
-
...attachmentRefs(message),
|
|
362
|
-
...(forward ? attachmentRefs(forward) : []),
|
|
363
|
-
],
|
|
364
|
-
...(forward && reference?.channelId
|
|
365
|
-
? {
|
|
366
|
-
forwarded: {
|
|
367
|
-
text: forward.content ?? "",
|
|
368
|
-
channelMention: `<#${reference.channelId}>`,
|
|
369
|
-
url: `https://discord.com/channels/${reference.guildId ?? "@me"}/${reference.channelId}/${reference.messageId ?? ""}`,
|
|
370
|
-
},
|
|
371
|
-
}
|
|
372
|
-
: {}),
|
|
373
|
-
...(referenced
|
|
374
|
-
? {
|
|
375
|
-
reference: {
|
|
376
|
-
text: referenced.content.replace(mention, "").trim(),
|
|
377
|
-
attachments: attachmentRefs(referenced),
|
|
378
|
-
...(referenced.webhookId
|
|
379
|
-
? { webhookName: referenced.author.username }
|
|
380
|
-
: {}),
|
|
381
|
-
},
|
|
382
|
-
}
|
|
383
|
-
: {}),
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
336
|
async #dispatch(interaction: Interaction): Promise<void> {
|
|
388
337
|
const { logger } = this.#options;
|
|
389
338
|
try {
|
|
@@ -418,14 +367,3 @@ export class DiscordSurface implements ChatSurface, OwnerNotifier {
|
|
|
418
367
|
return target;
|
|
419
368
|
}
|
|
420
369
|
}
|
|
421
|
-
|
|
422
|
-
function attachmentRefs(
|
|
423
|
-
message: Pick<Message, "attachments">,
|
|
424
|
-
): AttachmentRef[] {
|
|
425
|
-
return [...message.attachments.values()].map((attachment) => ({
|
|
426
|
-
url: attachment.url,
|
|
427
|
-
name: attachment.name,
|
|
428
|
-
...(attachment.contentType ? { contentType: attachment.contentType } : {}),
|
|
429
|
-
size: attachment.size,
|
|
430
|
-
}));
|
|
431
|
-
}
|
|
@@ -12,7 +12,7 @@ import type { Logger } from "../log.ts";
|
|
|
12
12
|
|
|
13
13
|
const PREFIX = "discord:";
|
|
14
14
|
/** Discord's limit on a thread name. */
|
|
15
|
-
|
|
15
|
+
const THREAD_NAME_LIMIT = 100;
|
|
16
16
|
|
|
17
17
|
/** The chat side of dispatch threads; the Discord surface implements it. */
|
|
18
18
|
export interface ThreadHost {
|
|
@@ -48,7 +48,7 @@ export interface DispatchThreadsOptions {
|
|
|
48
48
|
host: ThreadHost;
|
|
49
49
|
/** Threads still open, so a start after a crash can archive them. */
|
|
50
50
|
ledgerPath: string;
|
|
51
|
-
/** Channels
|
|
51
|
+
/** Channels whose claims keep reports in place rather than opening threads. */
|
|
52
52
|
excluded?: (channel: ChannelKey) => boolean;
|
|
53
53
|
logger: Logger;
|
|
54
54
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type Message, MessageReferenceType } from "discord.js";
|
|
2
|
+
import type { InboundMessage } from "../contract/channels.ts";
|
|
3
|
+
import { channelKey } from "../contract/surface.ts";
|
|
4
|
+
import type { AttachmentRef } from "../domain/attachment.ts";
|
|
5
|
+
|
|
6
|
+
/** Who the bot is on Discord, as far as the client knows it. */
|
|
7
|
+
export interface BotIdentity {
|
|
8
|
+
/** The bot user's id; undefined until the client is ready. */
|
|
9
|
+
botId: string | undefined;
|
|
10
|
+
/** The application's id, which the webhooks the assistant created carry. */
|
|
11
|
+
applicationId: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function attachmentRefs(
|
|
15
|
+
message: Pick<Message, "attachments">,
|
|
16
|
+
): AttachmentRef[] {
|
|
17
|
+
return [...message.attachments.values()].map((attachment) => ({
|
|
18
|
+
url: attachment.url,
|
|
19
|
+
name: attachment.name,
|
|
20
|
+
...(attachment.contentType ? { contentType: attachment.contentType } : {}),
|
|
21
|
+
size: attachment.size,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The facts of one Discord message, as the neutral `InboundMessage`; undefined for the bot's own
|
|
27
|
+
* messages and before the client is ready. A forward reports the channel it came from as a
|
|
28
|
+
* channel key, and a webhook's post as an integration.
|
|
29
|
+
*/
|
|
30
|
+
export async function toInbound(
|
|
31
|
+
message: Message,
|
|
32
|
+
{ botId, applicationId }: BotIdentity,
|
|
33
|
+
): Promise<InboundMessage | undefined> {
|
|
34
|
+
if (!botId || message.author.id === botId) return undefined;
|
|
35
|
+
const mention = new RegExp(`<@!?${botId}>`, "g");
|
|
36
|
+
const reference = message.reference;
|
|
37
|
+
// A forward also has a reference, to a message elsewhere; its copy is the snapshot.
|
|
38
|
+
const forward =
|
|
39
|
+
reference?.type === MessageReferenceType.Forward
|
|
40
|
+
? message.messageSnapshots.first()
|
|
41
|
+
: undefined;
|
|
42
|
+
const referenced =
|
|
43
|
+
reference?.messageId && !forward
|
|
44
|
+
? await message.fetchReference().catch(() => undefined)
|
|
45
|
+
: undefined;
|
|
46
|
+
return {
|
|
47
|
+
channel: channelKey("discord", message.channelId),
|
|
48
|
+
messageId: message.id,
|
|
49
|
+
authorId: message.author.id,
|
|
50
|
+
authorName:
|
|
51
|
+
message.member?.displayName ??
|
|
52
|
+
message.author.globalName ??
|
|
53
|
+
message.author.username,
|
|
54
|
+
authorIsBot: message.author.bot,
|
|
55
|
+
...(message.member
|
|
56
|
+
? { authorRoleIds: [...message.member.roles.cache.keys()] }
|
|
57
|
+
: {}),
|
|
58
|
+
...(message.webhookId
|
|
59
|
+
? {
|
|
60
|
+
integration: {
|
|
61
|
+
id: message.webhookId,
|
|
62
|
+
// Webhooks the assistant created carry its application on every message they post.
|
|
63
|
+
own:
|
|
64
|
+
message.applicationId !== null &&
|
|
65
|
+
message.applicationId === (applicationId ?? botId),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
isDirect: message.channel.isDMBased(),
|
|
70
|
+
...(message.guildId ? { space: message.guildId } : {}),
|
|
71
|
+
mentionsBot: message.mentions.users.has(botId),
|
|
72
|
+
repliesToBot: referenced?.author.id === botId,
|
|
73
|
+
text: message.content.replace(mention, "").trim(),
|
|
74
|
+
attachments: [
|
|
75
|
+
...attachmentRefs(message),
|
|
76
|
+
...(forward ? attachmentRefs(forward) : []),
|
|
77
|
+
],
|
|
78
|
+
...(forward && reference?.channelId
|
|
79
|
+
? {
|
|
80
|
+
forwarded: {
|
|
81
|
+
text: forward.content ?? "",
|
|
82
|
+
source: channelKey("discord", reference.channelId),
|
|
83
|
+
url: `https://discord.com/channels/${reference.guildId ?? "@me"}/${reference.channelId}/${reference.messageId ?? ""}`,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
: {}),
|
|
87
|
+
...(referenced
|
|
88
|
+
? {
|
|
89
|
+
reference: {
|
|
90
|
+
text: referenced.content.replace(mention, "").trim(),
|
|
91
|
+
attachments: attachmentRefs(referenced),
|
|
92
|
+
...(referenced.webhookId
|
|
93
|
+
? { webhookName: referenced.author.username }
|
|
94
|
+
: {}),
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -1 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
import type {
|
|
2
|
+
APIApplicationCommandSubcommandGroupOption,
|
|
3
|
+
APIApplicationCommandSubcommandOption,
|
|
4
|
+
Interaction,
|
|
5
|
+
InteractionContextType,
|
|
6
|
+
RESTPostAPIChatInputApplicationCommandsJSONBody,
|
|
7
|
+
} from "discord.js";
|
|
8
|
+
|
|
9
|
+
/** A group of slash commands and the components they post, handled together. */
|
|
10
|
+
export interface InteractionModule {
|
|
11
|
+
/** Top-level commands of its own; never the configured root command. */
|
|
12
|
+
commands(): RESTPostAPIChatInputApplicationCommandsJSONBody[];
|
|
13
|
+
/** Resolves true when this module handled the interaction. */
|
|
14
|
+
handle(interaction: Interaction): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A subcommand or subcommand group under the root command. */
|
|
18
|
+
export type RootOption =
|
|
19
|
+
| APIApplicationCommandSubcommandOption
|
|
20
|
+
| APIApplicationCommandSubcommandGroupOption;
|
|
21
|
+
|
|
22
|
+
/** A plugin's interactions: its module, and the subcommands it adds under the root command. */
|
|
23
|
+
export interface InteractionContribution {
|
|
24
|
+
module: InteractionModule;
|
|
25
|
+
rootOptions?: readonly RootOption[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The one root command every plugin's subcommands go under, such as `/roundtable`. */
|
|
29
|
+
export interface CommandRoot {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
contexts: readonly InteractionContextType[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Takes the slash commands and components of the plugins. Plugins add theirs while they set up;
|
|
37
|
+
* the Discord plugin composes them in its preflight, so a clash fails before any service starts.
|
|
38
|
+
*/
|
|
39
|
+
export interface CommandRegistrar {
|
|
40
|
+
/** Throws PluginError once the Discord plugin's preflight has composed the commands. */
|
|
41
|
+
add(contribution: InteractionContribution): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Who may use the owner's commands, and how a failing command answers. */
|
|
45
|
+
export interface CommandGuard {
|
|
46
|
+
/** The name of the root command the guard serves, without the slash. */
|
|
47
|
+
readonly root: string;
|
|
48
|
+
/**
|
|
49
|
+
* Text appended as it is to the refusal a non-owner gets, such as a pointer to the commands
|
|
50
|
+
* anyone may use; include the space or punctuation your language needs before it.
|
|
51
|
+
*/
|
|
52
|
+
readonly refusalHint?: string;
|
|
53
|
+
/** Whether the actor is the owner: an interaction, or anything with `user.id`, such as a test's. */
|
|
54
|
+
isOwner(actor: { user: { id: string } }): boolean;
|
|
55
|
+
/** Runs a handler; a failure becomes a panel instead of a hanging interaction. */
|
|
56
|
+
run(interaction: Interaction, run: () => Promise<void>): Promise<void>;
|
|
57
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
TextInputBuilder,
|
|
13
13
|
TextInputStyle,
|
|
14
14
|
} from "discord.js";
|
|
15
|
+
import { parseChannelKey } from "../contract/surface.ts";
|
|
15
16
|
import type { ChannelKey } from "../domain/conversation.ts";
|
|
16
17
|
import type {
|
|
17
18
|
Approval,
|
|
@@ -56,7 +57,7 @@ export interface CardMessage {
|
|
|
56
57
|
/** Where a channel's cards are posted. */
|
|
57
58
|
export interface CardChannel {
|
|
58
59
|
send(payload: CardPayload): Promise<CardMessage>;
|
|
59
|
-
/** A thread, where the owner is mentioned so
|
|
60
|
+
/** A thread, where the owner is mentioned so they hear of the card; a channel never mentions them. */
|
|
60
61
|
thread?: boolean;
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -118,10 +119,9 @@ export class OwnerCards implements InteractionModule {
|
|
|
118
119
|
return [];
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
/** The channel's cards;
|
|
122
|
+
/** The channel's cards; the chat surface port sends only Discord keys here. */
|
|
122
123
|
prompts(channel: ChannelKey, speaker?: Speaker): OwnerPrompts | undefined {
|
|
123
|
-
|
|
124
|
-
const channelId = channel.slice("discord:".length);
|
|
124
|
+
const channelId = parseChannelKey(channel).id;
|
|
125
125
|
return {
|
|
126
126
|
confirm: (title, message, signal, minTier = "owner") =>
|
|
127
127
|
this.#post<Approval>(channelId, signal, "expired", "cancelled", {
|
|
@@ -6,10 +6,14 @@ import {
|
|
|
6
6
|
MessageFlags,
|
|
7
7
|
SlashCommandSubcommandGroupBuilder,
|
|
8
8
|
} from "discord.js";
|
|
9
|
-
import type { CommandRoot, RootOption } from "../contract/discord.ts";
|
|
10
9
|
import { messages } from "../i18n/index.ts";
|
|
11
10
|
import type { Logger } from "../log.ts";
|
|
12
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
CommandGuard,
|
|
13
|
+
CommandRoot,
|
|
14
|
+
InteractionModule,
|
|
15
|
+
RootOption,
|
|
16
|
+
} from "./interaction-module.ts";
|
|
13
17
|
import { ephemeralPanel, OwnerFacingError, ownerPanel } from "./owner-panel.ts";
|
|
14
18
|
|
|
15
19
|
/** The owner's single control command, such as `/roundtable`, every feature adds its subcommands to. */
|
|
@@ -29,24 +33,31 @@ export const groupOption = (
|
|
|
29
33
|
): RootOption => build(new SlashCommandSubcommandGroupBuilder()).toJSON();
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
|
-
* Only the owner may use the root command, checked by user ID; every answer is visible only to
|
|
36
|
+
* Only the owner may use the root command, checked by user ID; every answer is visible only to them,
|
|
33
37
|
* and a failure becomes a panel instead of a hanging interaction.
|
|
34
38
|
*/
|
|
35
39
|
// pi-lens-ignore: large-class — two methods: the owner check and the failure panel
|
|
36
|
-
export class OwnerGuard {
|
|
40
|
+
export class OwnerGuard implements CommandGuard {
|
|
37
41
|
readonly #ownerId: string;
|
|
38
42
|
readonly #logger: Logger;
|
|
39
43
|
/** The name of the root command the guard serves, without the slash. */
|
|
40
44
|
readonly root: string;
|
|
45
|
+
readonly refusalHint: string | undefined;
|
|
41
46
|
|
|
42
|
-
constructor(
|
|
47
|
+
constructor(
|
|
48
|
+
ownerId: string,
|
|
49
|
+
logger: Logger,
|
|
50
|
+
root: string,
|
|
51
|
+
refusalHint?: string,
|
|
52
|
+
) {
|
|
43
53
|
this.#ownerId = ownerId;
|
|
44
54
|
this.#logger = logger;
|
|
45
55
|
this.root = root;
|
|
56
|
+
this.refusalHint = refusalHint;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
isOwner(
|
|
49
|
-
return
|
|
59
|
+
isOwner(actor: { user: { id: string } }): boolean {
|
|
60
|
+
return actor.user.id === this.#ownerId;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
/** Runs a handler; failures become a panel instead of a hanging interaction. */
|
|
@@ -69,6 +80,28 @@ export class OwnerGuard {
|
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
|
|
83
|
+
/** What `commandGuard` needs. */
|
|
84
|
+
export interface CommandGuardOptions {
|
|
85
|
+
/** The one user who may use the owner's commands. */
|
|
86
|
+
ownerId: string;
|
|
87
|
+
/** The name of the root command the guard serves, without the slash. */
|
|
88
|
+
root: string;
|
|
89
|
+
/** Where a failing command is logged. */
|
|
90
|
+
logger: Logger;
|
|
91
|
+
/** Text appended as it is to the refusal a non-owner gets. */
|
|
92
|
+
refusalHint?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A guard for the owner's commands, such as the one a test hands a module; the Discord plugin makes its own. */
|
|
96
|
+
export function commandGuard(options: CommandGuardOptions): CommandGuard {
|
|
97
|
+
return new OwnerGuard(
|
|
98
|
+
options.ownerId,
|
|
99
|
+
options.logger,
|
|
100
|
+
options.root,
|
|
101
|
+
options.refusalHint,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
/** One feature's part of the root command. */
|
|
73
106
|
export interface OwnerCommandHandlers {
|
|
74
107
|
/** Whether a root subcommand, in its group or null at the top level, is this feature's. */
|
|
@@ -82,15 +115,15 @@ export interface OwnerCommandHandlers {
|
|
|
82
115
|
component?(interaction: Interaction): Promise<boolean>;
|
|
83
116
|
}
|
|
84
117
|
|
|
85
|
-
const refusal = () =>
|
|
118
|
+
const refusal = (hint: string | undefined) =>
|
|
86
119
|
ephemeralPanel({
|
|
87
120
|
title: messages().ownerRefusalTitle,
|
|
88
|
-
sections: [messages().ownerRefusalBody],
|
|
121
|
+
sections: [`${messages().ownerRefusalBody}${hint ?? ""}`],
|
|
89
122
|
});
|
|
90
123
|
|
|
91
124
|
/** Answers a feature's root subcommands and components, for the owner only. */
|
|
92
125
|
export function ownerCommandModule(
|
|
93
|
-
guard:
|
|
126
|
+
guard: CommandGuard,
|
|
94
127
|
handlers: OwnerCommandHandlers,
|
|
95
128
|
): InteractionModule {
|
|
96
129
|
const owned = (
|
|
@@ -114,7 +147,7 @@ export function ownerCommandModule(
|
|
|
114
147
|
if (!interaction.isChatInputCommand() || !owned(interaction))
|
|
115
148
|
return false;
|
|
116
149
|
if (!guard.isOwner(interaction)) {
|
|
117
|
-
await interaction.reply(refusal());
|
|
150
|
+
await interaction.reply(refusal(guard.refusalHint));
|
|
118
151
|
return true;
|
|
119
152
|
}
|
|
120
153
|
if (await handlers.open?.(interaction)) return true;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PermissionFlagsBits,
|
|
6
6
|
} from "discord.js";
|
|
7
7
|
import { messages } from "../i18n/index.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type { OwnerOperations } from "../modules/discord-admin/discord-admin.ts";
|
|
9
9
|
import {
|
|
10
10
|
discordChannelExecutor,
|
|
11
11
|
fetchOwnerChannel,
|
|
@@ -29,7 +29,7 @@ export { outranksMember, outranksRole } from "./owner-discord-access.ts";
|
|
|
29
29
|
* Discord operations for the owner's agent over the bot's connection. Every call is bounded
|
|
30
30
|
* by what both the owner and the bot may do where it acts, read fresh from Discord.
|
|
31
31
|
*/
|
|
32
|
-
export class DiscordOwnerOps implements
|
|
32
|
+
export class DiscordOwnerOps implements OwnerOperations {
|
|
33
33
|
readonly #client: Client;
|
|
34
34
|
readonly #ownerId: string;
|
|
35
35
|
/** The audit-log reason of an action taken without one of its own. */
|
|
@@ -3,18 +3,15 @@ import type {
|
|
|
3
3
|
ChatInputCommandInteraction,
|
|
4
4
|
SlashCommandSubcommandGroupBuilder,
|
|
5
5
|
} from "discord.js";
|
|
6
|
-
import type { InteractionContribution } from "../contract/discord.ts";
|
|
7
6
|
import { messages } from "../i18n/index.ts";
|
|
8
7
|
import { describeRecurrence } from "../modules/schedules/recurrence.ts";
|
|
8
|
+
import type { Schedule } from "../modules/schedules/schedule-store.ts";
|
|
9
|
+
import type { ScheduleStore } from "../services.ts";
|
|
9
10
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
13
|
-
import {
|
|
14
|
-
groupOption,
|
|
15
|
-
type OwnerGuard,
|
|
16
|
-
ownerCommandModule,
|
|
17
|
-
} from "./owner-command.ts";
|
|
11
|
+
CommandGuard,
|
|
12
|
+
InteractionContribution,
|
|
13
|
+
} from "./interaction-module.ts";
|
|
14
|
+
import { groupOption, ownerCommandModule } from "./owner-command.ts";
|
|
18
15
|
import {
|
|
19
16
|
OwnerFacingError,
|
|
20
17
|
ownerPanel,
|
|
@@ -52,7 +49,10 @@ function channelMention(schedule: Schedule): string {
|
|
|
52
49
|
|
|
53
50
|
const unix = (at: Date) => Math.floor(at.getTime() / 1000);
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
/** How a schedule's target reads in a list; the target's name when no plugin contributes it. */
|
|
53
|
+
export type TargetLabel = (target: string) => string;
|
|
54
|
+
|
|
55
|
+
function section(schedule: Schedule, label: TargetLabel): string {
|
|
56
56
|
const prompt =
|
|
57
57
|
schedule.prompt.length > PROMPT_PREVIEW
|
|
58
58
|
? `${schedule.prompt.slice(0, PROMPT_PREVIEW)}…`
|
|
@@ -64,14 +64,12 @@ function section(schedule: Schedule): string {
|
|
|
64
64
|
plain(schedule.lastStatus ?? "?"),
|
|
65
65
|
)
|
|
66
66
|
: "";
|
|
67
|
-
const mode =
|
|
68
|
-
schedule.mode === "owner" ? text.scheduleModeOwner : text.scheduleModeParty;
|
|
69
67
|
return [
|
|
70
68
|
text.scheduleHead(
|
|
71
69
|
schedule.id,
|
|
72
70
|
plain(schedule.title),
|
|
73
71
|
channelMention(schedule),
|
|
74
|
-
|
|
72
|
+
plain(label(schedule.target)),
|
|
75
73
|
),
|
|
76
74
|
text.scheduleNextRun(
|
|
77
75
|
describeRecurrence(schedule.recurrence),
|
|
@@ -85,9 +83,11 @@ function section(schedule: Schedule): string {
|
|
|
85
83
|
/** `/<root> schedule`: the owner's view over every channel's schedules. */
|
|
86
84
|
export class ScheduleCommands {
|
|
87
85
|
readonly #store: ScheduleStore;
|
|
86
|
+
readonly #label: TargetLabel;
|
|
88
87
|
|
|
89
|
-
constructor(store: ScheduleStore) {
|
|
88
|
+
constructor(store: ScheduleStore, label: TargetLabel) {
|
|
90
89
|
this.#store = store;
|
|
90
|
+
this.#label = label;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async autocomplete(interaction: AutocompleteInteraction): Promise<void> {
|
|
@@ -110,7 +110,9 @@ export class ScheduleCommands {
|
|
|
110
110
|
const all = await this.#store.all();
|
|
111
111
|
await replyWithPanels(interaction, {
|
|
112
112
|
title: messages().scheduleTitle,
|
|
113
|
-
sections: all.length
|
|
113
|
+
sections: all.length
|
|
114
|
+
? all.map((s) => section(s, this.#label))
|
|
115
|
+
: [messages().scheduleNone],
|
|
114
116
|
footer: messages().scheduleFooter,
|
|
115
117
|
});
|
|
116
118
|
}
|
|
@@ -126,7 +128,7 @@ export class ScheduleCommands {
|
|
|
126
128
|
await interaction.editReply(
|
|
127
129
|
ownerPanel({
|
|
128
130
|
title: messages().scheduleCancelled,
|
|
129
|
-
sections: [section(removed)],
|
|
131
|
+
sections: [section(removed, this.#label)],
|
|
130
132
|
}),
|
|
131
133
|
);
|
|
132
134
|
}
|
|
@@ -134,10 +136,11 @@ export class ScheduleCommands {
|
|
|
134
136
|
|
|
135
137
|
/** `/<root> schedule list|cancel`. */
|
|
136
138
|
export function scheduleCommands(
|
|
137
|
-
guard:
|
|
139
|
+
guard: CommandGuard,
|
|
138
140
|
store: ScheduleStore,
|
|
141
|
+
label: TargetLabel,
|
|
139
142
|
): InteractionContribution {
|
|
140
|
-
const commands = new ScheduleCommands(store);
|
|
143
|
+
const commands = new ScheduleCommands(store, label);
|
|
141
144
|
return {
|
|
142
145
|
module: ownerCommandModule(guard, {
|
|
143
146
|
owns: (group) => group === "schedule",
|