pi-roundtable 0.1.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 +28 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/docs/plugins.md +924 -0
- package/examples/channels.test.ts +13 -0
- package/examples/channels.ts +27 -0
- package/examples/dashboard.test.ts +11 -0
- package/examples/dashboard.ts +9 -0
- package/examples/events.test.ts +26 -0
- package/examples/events.ts +25 -0
- package/examples/guide.test.ts +52 -0
- package/examples/holds.test.ts +16 -0
- package/examples/holds.ts +30 -0
- package/examples/http.test.ts +12 -0
- package/examples/http.ts +17 -0
- package/examples/interactions.test.ts +25 -0
- package/examples/interactions.ts +28 -0
- package/examples/migrations.test.ts +31 -0
- package/examples/migrations.ts +42 -0
- package/examples/packages.test.ts +9 -0
- package/examples/packages.ts +9 -0
- package/examples/preflight.test.ts +11 -0
- package/examples/preflight.ts +19 -0
- package/examples/prompt.test.ts +25 -0
- package/examples/prompt.ts +20 -0
- package/examples/providers.test.ts +10 -0
- package/examples/providers.ts +17 -0
- package/examples/seeds.test.ts +12 -0
- package/examples/seeds.ts +18 -0
- package/examples/selection.test.ts +13 -0
- package/examples/selection.ts +14 -0
- package/examples/services.test.ts +20 -0
- package/examples/services.ts +30 -0
- package/examples/session-tools.test.ts +24 -0
- package/examples/session-tools.ts +34 -0
- package/examples/tools.test.ts +18 -0
- package/examples/tools.ts +28 -0
- package/package.json +55 -0
- package/src/cli/add-plugin.test.ts +107 -0
- package/src/cli/add-plugin.ts +75 -0
- package/src/cli/checks/basic.test.ts +276 -0
- package/src/cli/checks/bun.ts +26 -0
- package/src/cli/checks/configuration.ts +28 -0
- package/src/cli/checks/database.test.ts +117 -0
- package/src/cli/checks/database.ts +100 -0
- package/src/cli/checks/discord.test.ts +200 -0
- package/src/cli/checks/discord.ts +218 -0
- package/src/cli/checks/environment.ts +27 -0
- package/src/cli/checks/model.ts +28 -0
- package/src/cli/checks/public-url.ts +40 -0
- package/src/cli/cli.test.ts +122 -0
- package/src/cli/cli.ts +166 -0
- package/src/cli/config-edit.test.ts +130 -0
- package/src/cli/config-edit.ts +199 -0
- package/src/cli/discord-api.ts +96 -0
- package/src/cli/doctor.test.ts +184 -0
- package/src/cli/doctor.ts +92 -0
- package/src/cli/http.ts +23 -0
- package/src/cli/init.test.ts +127 -0
- package/src/cli/init.ts +82 -0
- package/src/cli/main.ts +13 -0
- package/src/cli/project.ts +123 -0
- package/src/cli/report.ts +68 -0
- package/src/cli/roundtable.mjs +22 -0
- package/src/cli/runtime.ts +53 -0
- package/src/cli/size.test.ts +9 -0
- package/src/cli/start.ts +33 -0
- package/src/cli/templates.test.ts +129 -0
- package/src/cli/templates.ts +119 -0
- package/src/cli/testing/fixtures.ts +106 -0
- package/src/core/agents/agent-claim.test.ts +142 -0
- package/src/core/agents/agent-claim.ts +170 -0
- package/src/core/agents/agent-dashboard.test.ts +120 -0
- package/src/core/agents/agent-dashboard.ts +185 -0
- package/src/core/agents/agent-guild.test.ts +124 -0
- package/src/core/agents/agent-messages.ts +189 -0
- package/src/core/agents/agent-ports.ts +103 -0
- package/src/core/agents/agent-prompt.ts +163 -0
- package/src/core/agents/agent-rules.ts +106 -0
- package/src/core/agents/agent-schema.ts +115 -0
- package/src/core/agents/agent-settings.ts +49 -0
- package/src/core/agents/agent-store.test.ts +205 -0
- package/src/core/agents/agent-store.ts +384 -0
- package/src/core/agents/agent-team.ts +445 -0
- package/src/core/agents/agent-tools.ts +386 -0
- package/src/core/agents/avatar-studio.test.ts +88 -0
- package/src/core/agents/avatar-studio.ts +147 -0
- package/src/core/agents/group-messages.ts +99 -0
- package/src/core/agents/group-round.test.ts +143 -0
- package/src/core/agents/group-round.ts +117 -0
- package/src/core/agents/group-turns.ts +132 -0
- package/src/core/agents/owner-identity.test.ts +172 -0
- package/src/core/agents/team-editing.ts +205 -0
- package/src/core/agents/team-keys.ts +40 -0
- package/src/core/agents/team-layout.ts +135 -0
- package/src/core/agents/team-lifecycle.ts +188 -0
- package/src/core/agents/team-options.ts +52 -0
- package/src/core/agents/team-status.ts +80 -0
- package/src/core/agents/team-text.ts +60 -0
- package/src/core/agents/team-turn-types.ts +107 -0
- package/src/core/agents/team-turns.test.ts +278 -0
- package/src/core/agents/team-turns.ts +323 -0
- package/src/core/assets/neutral.png +0 -0
- package/src/core/assets/prompts/shared-guest.md +11 -0
- package/src/core/assets/prompts/shared.md +11 -0
- package/src/core/assets/skills/writing-skills/SKILL.md +21 -0
- package/src/core/attachments/attachment-dir.ts +15 -0
- package/src/core/attachments/attachment-fetcher.ts +83 -0
- package/src/core/attachments/attachments.test.ts +85 -0
- package/src/core/attachments/image-prep.ts +50 -0
- package/src/core/attachments/prompt-block.ts +37 -0
- package/src/core/attachments/turn-attachments.ts +79 -0
- package/src/core/boundary.test.ts +45 -0
- package/src/core/builtin/agent-server.ts +275 -0
- package/src/core/builtin/discord.ts +95 -0
- package/src/core/builtin/modules.test.ts +127 -0
- package/src/core/builtin/modules.ts +176 -0
- package/src/core/builtin/seeds.ts +10 -0
- package/src/core/builtin/stores.ts +42 -0
- package/src/core/config/config.test.ts +135 -0
- package/src/core/config/config.ts +285 -0
- package/src/core/config/schema.ts +181 -0
- package/src/core/contract/channels.ts +152 -0
- package/src/core/contract/discord.ts +33 -0
- package/src/core/contract/providers.ts +67 -0
- package/src/core/db/guild-scope.ts +53 -0
- package/src/core/db/migrations.test.ts +237 -0
- package/src/core/db/migrations.ts +37 -0
- package/src/core/define-roundtable.test.ts +134 -0
- package/src/core/define-roundtable.ts +195 -0
- package/src/core/define.test.ts +144 -0
- package/src/core/define.ts +154 -0
- package/src/core/discord/agent-commands.test.ts +58 -0
- package/src/core/discord/agent-commands.ts +385 -0
- package/src/core/discord/agent-discord.test.ts +70 -0
- package/src/core/discord/agent-discord.ts +367 -0
- package/src/core/discord/channel-executor.ts +404 -0
- package/src/core/discord/channel-operations.ts +329 -0
- package/src/core/discord/discord-surface.ts +431 -0
- package/src/core/discord/dispatch-thread-host.test.ts +114 -0
- package/src/core/discord/dispatch-thread-host.ts +72 -0
- package/src/core/discord/dispatch-threads.test.ts +94 -0
- package/src/core/discord/dispatch-threads.ts +180 -0
- package/src/core/discord/interaction-module.ts +1 -0
- package/src/core/discord/owner-cards.test.ts +435 -0
- package/src/core/discord/owner-cards.ts +463 -0
- package/src/core/discord/owner-command.ts +126 -0
- package/src/core/discord/owner-discord-access.ts +183 -0
- package/src/core/discord/owner-discord-threads.ts +160 -0
- package/src/core/discord/owner-discord.test.ts +325 -0
- package/src/core/discord/owner-discord.ts +282 -0
- package/src/core/discord/owner-panel.ts +103 -0
- package/src/core/discord/schedule-commands.ts +149 -0
- package/src/core/discord/stop-button.ts +20 -0
- package/src/core/domain/attachment.ts +39 -0
- package/src/core/domain/conversation.ts +42 -0
- package/src/core/domain/errors.ts +31 -0
- package/src/core/domain/expression.ts +21 -0
- package/src/core/domain/owner-prompts.ts +45 -0
- package/src/core/domain/ports.ts +91 -0
- package/src/core/domain/profile.ts +32 -0
- package/src/core/drain.test.ts +46 -0
- package/src/core/drain.ts +33 -0
- package/src/core/errors.ts +29 -0
- package/src/core/events.test.ts +81 -0
- package/src/core/events.ts +55 -0
- package/src/core/holds.test.ts +54 -0
- package/src/core/holds.ts +46 -0
- package/src/core/host.test.ts +536 -0
- package/src/core/host.ts +297 -0
- package/src/core/http/listeners.test.ts +164 -0
- package/src/core/http/listeners.ts +151 -0
- package/src/core/i18n/agent-panel.ts +96 -0
- package/src/core/i18n/cards.ts +64 -0
- package/src/core/i18n/channels.ts +28 -0
- package/src/core/i18n/conversation.ts +49 -0
- package/src/core/i18n/dashboard.ts +72 -0
- package/src/core/i18n/discord.ts +23 -0
- package/src/core/i18n/en.ts +27 -0
- package/src/core/i18n/i18n.test.ts +139 -0
- package/src/core/i18n/index.ts +58 -0
- package/src/core/i18n/owner.ts +35 -0
- package/src/core/i18n/schedules.ts +92 -0
- package/src/core/i18n/time-zones.ts +27 -0
- package/src/core/i18n/types.ts +7 -0
- package/src/core/i18n/zh-tw.ts +26 -0
- package/src/core/identity.test.ts +18 -0
- package/src/core/identity.ts +31 -0
- package/src/core/judging/confirmation-judge.ts +68 -0
- package/src/core/judging/effort-judge.test.ts +112 -0
- package/src/core/judging/effort-judge.ts +158 -0
- package/src/core/judging/model-judge.test.ts +126 -0
- package/src/core/judging/model-judge.ts +193 -0
- package/src/core/log.ts +45 -0
- package/src/core/models.ts +52 -0
- package/src/core/modules/background/background-turns.ts +78 -0
- package/src/core/modules/delegation/delegate.ts +54 -0
- package/src/core/modules/delegation/delegator.test.ts +170 -0
- package/src/core/modules/delegation/delegator.ts +191 -0
- package/src/core/modules/delegation/sol-worker.ts +78 -0
- package/src/core/modules/discord-admin/discord-admin.ts +288 -0
- package/src/core/modules/host-shell/shell-policy.ts +386 -0
- package/src/core/modules/memory/owner-memory-store.test.ts +244 -0
- package/src/core/modules/memory/owner-memory-store.ts +228 -0
- package/src/core/modules/memory/owner-memory.ts +153 -0
- package/src/core/modules/notify/notify.ts +26 -0
- package/src/core/modules/schedules/recurrence.ts +206 -0
- package/src/core/modules/schedules/schedule-store.ts +213 -0
- package/src/core/modules/schedules/schedule-tools.ts +196 -0
- package/src/core/modules/schedules/schedule.test.ts +358 -0
- package/src/core/modules/schedules/scheduler.ts +121 -0
- package/src/core/modules/schedules/schedules.ts +87 -0
- package/src/core/modules/skills/repo-name.ts +11 -0
- package/src/core/modules/skills/skill-kind.test.ts +55 -0
- package/src/core/modules/skills/skill-link.ts +69 -0
- package/src/core/modules/skills/skill-listing.ts +128 -0
- package/src/core/modules/skills/skill-registry.test.ts +376 -0
- package/src/core/modules/skills/skill-registry.ts +323 -0
- package/src/core/modules/skills/skill-rules.ts +120 -0
- package/src/core/modules/skills/skill-store.ts +285 -0
- package/src/core/modules/skills/skill-tools.ts +207 -0
- package/src/core/ops/error-reporter.test.ts +283 -0
- package/src/core/ops/error-reporter.ts +271 -0
- package/src/core/plugin.ts +200 -0
- package/src/core/presentation/card-cadence.ts +41 -0
- package/src/core/presentation/headline.ts +34 -0
- package/src/core/presentation/presentation.test.ts +166 -0
- package/src/core/presentation/quiet-links.ts +36 -0
- package/src/core/presentation/reply-splitter.ts +110 -0
- package/src/core/presentation/thinking-line.ts +24 -0
- package/src/core/public-entry.test.ts +59 -0
- package/src/core/registry/contributions.test.ts +215 -0
- package/src/core/registry/contributions.ts +249 -0
- package/src/core/registry/interactions.test.ts +87 -0
- package/src/core/registry/interactions.ts +55 -0
- package/src/core/registry/providers.test.ts +103 -0
- package/src/core/registry/providers.ts +55 -0
- package/src/core/routing/channel-queue.test.ts +31 -0
- package/src/core/routing/channel-queue.ts +44 -0
- package/src/core/routing/channel-router.test.ts +326 -0
- package/src/core/routing/channel-router.ts +157 -0
- package/src/core/routing/conversation-kind.ts +14 -0
- package/src/core/routing/forward-join.ts +65 -0
- package/src/core/routing/message-text.ts +16 -0
- package/src/core/routing/settle-turn.test.ts +21 -0
- package/src/core/routing/settle-turn.ts +26 -0
- package/src/core/runtime/compaction-tiers.test.ts +227 -0
- package/src/core/runtime/compaction-tiers.ts +203 -0
- package/src/core/runtime/conversation-sessions.ts +162 -0
- package/src/core/runtime/extensions/agent-prompt.ts +16 -0
- package/src/core/runtime/extensions/ask-user.test.ts +114 -0
- package/src/core/runtime/extensions/ask-user.ts +98 -0
- package/src/core/runtime/extensions/confirmation-gate.ts +247 -0
- package/src/core/runtime/extensions/self-compact-guard.test.ts +50 -0
- package/src/core/runtime/extensions/self-compact-guard.ts +23 -0
- package/src/core/runtime/mcp.ts +23 -0
- package/src/core/runtime/pending-confirmation-store.test.ts +52 -0
- package/src/core/runtime/pending-confirmation-store.ts +75 -0
- package/src/core/runtime/pi-agent-runtime.ts +477 -0
- package/src/core/runtime/prompt-slot.ts +81 -0
- package/src/core/runtime/runtime-types.ts +150 -0
- package/src/core/runtime/session-archive.test.ts +20 -0
- package/src/core/runtime/session-archive.ts +17 -0
- package/src/core/runtime/session-factory.ts +291 -0
- package/src/core/runtime/steerable-run.test.ts +264 -0
- package/src/core/runtime/steerable-run.ts +109 -0
- package/src/core/runtime/text-tools.test.ts +110 -0
- package/src/core/runtime/text-tools.ts +66 -0
- package/src/core/runtime/turn-answer.test.ts +72 -0
- package/src/core/runtime/turn-answer.ts +79 -0
- package/src/core/runtime/worker-task.test.ts +82 -0
- package/src/core/runtime/worker-task.ts +65 -0
- package/src/core/services.test.ts +28 -0
- package/src/core/services.ts +107 -0
- package/src/core/sessions.test.ts +85 -0
- package/src/core/sessions.ts +149 -0
- package/src/core/shared/attachment-reader.ts +91 -0
- package/src/core/shared/delegate-tool.ts +15 -0
- package/src/core/shared/mcp-adapter.ts +58 -0
- package/src/core/shared/package-dir.ts +9 -0
- package/src/core/shared/profile-tools.ts +16 -0
- package/src/core/shared/read-attachment-tool.ts +43 -0
- package/src/core/shared/schedule-tools.ts +118 -0
- package/src/core/shared/session-messages.ts +50 -0
- package/src/core/shared/tool-result.ts +9 -0
- package/src/core/shared/unix-server.ts +18 -0
- package/src/core/size.test.ts +9 -0
- package/src/core/speakers.test.ts +78 -0
- package/src/core/speakers.ts +143 -0
- package/src/core/testing/database.ts +52 -0
- package/src/core/testing/file-size.ts +34 -0
- package/src/core/testing/locale.ts +8 -0
- package/src/core/testing/modules.ts +97 -0
- package/src/core/testing/owner.ts +15 -0
- package/src/core/testing/thread-host.ts +60 -0
- package/src/core/time.test.ts +166 -0
- package/src/core/time.ts +67 -0
- package/src/core/tool-tiers.test.ts +117 -0
- package/src/core/tool-tiers.ts +127 -0
- package/src/entries.test.ts +112 -0
- package/src/index.ts +28 -0
- package/src/testing.test.ts +156 -0
- package/src/testing.ts +182 -0
- package/templates/.env.example +22 -0
- package/templates/README.md +11 -0
- package/templates/_gitignore +3 -0
- package/templates/agents.ts +12 -0
- package/templates/biome.json.tmpl +7 -0
- package/templates/docker-compose.yml +16 -0
- package/templates/package.json.tmpl +24 -0
- package/templates/persona/shared.md +2 -0
- package/templates/plugin/plugin.test.ts.tmpl +13 -0
- package/templates/plugin/plugin.ts +18 -0
- package/templates/roundtable.config.ts +24 -0
- package/templates/tsconfig.json.tmpl +16 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChannelType,
|
|
3
|
+
type Client,
|
|
4
|
+
type Guild,
|
|
5
|
+
PermissionFlagsBits,
|
|
6
|
+
} from "discord.js";
|
|
7
|
+
import { messages } from "../i18n/index.ts";
|
|
8
|
+
import type { OwnerDiscord } from "../modules/discord-admin/discord-admin.ts";
|
|
9
|
+
import {
|
|
10
|
+
discordChannelExecutor,
|
|
11
|
+
fetchOwnerChannel,
|
|
12
|
+
operationPermissions,
|
|
13
|
+
} from "./channel-executor.ts";
|
|
14
|
+
import { CHANNEL_TOOLS, parseChannelTool } from "./channel-operations.ts";
|
|
15
|
+
import {
|
|
16
|
+
CHANNEL_TYPES,
|
|
17
|
+
OwnerAccess,
|
|
18
|
+
permissionFlags,
|
|
19
|
+
permissionNames,
|
|
20
|
+
refuse,
|
|
21
|
+
roleOptions,
|
|
22
|
+
SERVER_PERMISSIONS,
|
|
23
|
+
} from "./owner-discord-access.ts";
|
|
24
|
+
import { OwnerThreads } from "./owner-discord-threads.ts";
|
|
25
|
+
|
|
26
|
+
export { outranksMember, outranksRole } from "./owner-discord-access.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Discord operations for the owner's agent over the bot's connection. Every call is bounded
|
|
30
|
+
* by what both the owner and the bot may do where it acts, read fresh from Discord.
|
|
31
|
+
*/
|
|
32
|
+
export class DiscordOwnerOps implements OwnerDiscord {
|
|
33
|
+
readonly #client: Client;
|
|
34
|
+
readonly #ownerId: string;
|
|
35
|
+
/** The audit-log reason of an action taken without one of its own. */
|
|
36
|
+
readonly #reason: string;
|
|
37
|
+
readonly #access: OwnerAccess;
|
|
38
|
+
readonly #threadOps: OwnerThreads;
|
|
39
|
+
|
|
40
|
+
constructor(client: Client, ownerId: string, ownerName = "the owner") {
|
|
41
|
+
this.#client = client;
|
|
42
|
+
this.#ownerId = ownerId;
|
|
43
|
+
this.#reason = messages().auditRequested(ownerName);
|
|
44
|
+
this.#access = new OwnerAccess(ownerId, ownerName);
|
|
45
|
+
this.#threadOps = new OwnerThreads(
|
|
46
|
+
client,
|
|
47
|
+
this.#access,
|
|
48
|
+
ownerId,
|
|
49
|
+
this.#reason,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async run(tool: string, args: Record<string, unknown>): Promise<unknown> {
|
|
54
|
+
if (!this.#client.isReady())
|
|
55
|
+
throw refuse("DISCORD_NOT_READY", "try again shortly");
|
|
56
|
+
const channelTool = CHANNEL_TOOLS[tool];
|
|
57
|
+
if (channelTool) {
|
|
58
|
+
const parsed = parseChannelTool(tool, args);
|
|
59
|
+
const channel = await fetchOwnerChannel(
|
|
60
|
+
this.#client,
|
|
61
|
+
String(parsed.channelId),
|
|
62
|
+
);
|
|
63
|
+
await this.#access.members(
|
|
64
|
+
channel.guild,
|
|
65
|
+
operationPermissions(channelTool.operation, channel),
|
|
66
|
+
channel,
|
|
67
|
+
);
|
|
68
|
+
return discordChannelExecutor(this.#client, { threads: true }).execute(
|
|
69
|
+
tool,
|
|
70
|
+
parsed,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (tool === "discord_list_servers") return this.#servers();
|
|
74
|
+
if (tool === "discord_delete_channel") return this.#deleteChannel(args);
|
|
75
|
+
if (tool === "discord_create_thread") return this.#threadOps.create(args);
|
|
76
|
+
if (tool === "discord_edit_thread") return this.#threadOps.edit(args);
|
|
77
|
+
const needed = SERVER_PERMISSIONS[tool];
|
|
78
|
+
if (!needed) throw refuse("INVALID_TOOL", tool);
|
|
79
|
+
const guild = await this.#client.guilds
|
|
80
|
+
.fetch(String(args.guildId))
|
|
81
|
+
.catch(() => {
|
|
82
|
+
throw refuse("UNKNOWN_SERVER", messages().refuseNotInServer);
|
|
83
|
+
});
|
|
84
|
+
return this.#serverTool(tool, guild, needed, args);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async #serverTool(
|
|
88
|
+
tool: string,
|
|
89
|
+
guild: Guild,
|
|
90
|
+
needed: bigint[],
|
|
91
|
+
args: Record<string, unknown>,
|
|
92
|
+
): Promise<unknown> {
|
|
93
|
+
const reason = typeof args.reason === "string" ? args.reason : this.#reason;
|
|
94
|
+
if (tool === "discord_create_channel") {
|
|
95
|
+
const parent = args.parentId
|
|
96
|
+
? await this.#access.category(guild, String(args.parentId))
|
|
97
|
+
: undefined;
|
|
98
|
+
await this.#access.members(guild, needed, parent);
|
|
99
|
+
const created = await guild.channels.create({
|
|
100
|
+
name: String(args.name),
|
|
101
|
+
type: CHANNEL_TYPES[args.type as keyof typeof CHANNEL_TYPES],
|
|
102
|
+
...(parent ? { parent } : {}),
|
|
103
|
+
...(typeof args.topic === "string" ? { topic: args.topic } : {}),
|
|
104
|
+
reason,
|
|
105
|
+
});
|
|
106
|
+
return { id: created.id, name: created.name, parentId: created.parentId };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { owner, bot } = await this.#access.members(guild, needed);
|
|
110
|
+
switch (tool) {
|
|
111
|
+
case "discord_list_channels": {
|
|
112
|
+
const channels = await guild.channels.fetch();
|
|
113
|
+
return [...channels.values()]
|
|
114
|
+
.filter((c) => c !== null)
|
|
115
|
+
.filter(
|
|
116
|
+
(c) =>
|
|
117
|
+
c.permissionsFor(owner).has(PermissionFlagsBits.ViewChannel) &&
|
|
118
|
+
c.permissionsFor(bot).has(PermissionFlagsBits.ViewChannel),
|
|
119
|
+
)
|
|
120
|
+
.sort((a, b) => a.rawPosition - b.rawPosition)
|
|
121
|
+
.map((c) => ({
|
|
122
|
+
id: c.id,
|
|
123
|
+
name: c.name,
|
|
124
|
+
type: ChannelType[c.type],
|
|
125
|
+
parentId: c.parentId,
|
|
126
|
+
...("topic" in c && c.topic ? { topic: c.topic } : {}),
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
case "discord_list_threads":
|
|
130
|
+
return this.#threadOps.list(guild, owner, bot, args);
|
|
131
|
+
case "discord_list_roles": {
|
|
132
|
+
const roles = await guild.roles.fetch();
|
|
133
|
+
return [...roles.values()]
|
|
134
|
+
.sort((a, b) => b.comparePositionTo(a))
|
|
135
|
+
.map((role) => ({
|
|
136
|
+
id: role.id,
|
|
137
|
+
name: role.name,
|
|
138
|
+
color: role.hexColor,
|
|
139
|
+
managed: role.managed,
|
|
140
|
+
hoist: role.hoist,
|
|
141
|
+
mentionable: role.mentionable,
|
|
142
|
+
permissions: role.permissions.toArray(),
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
case "discord_find_members": {
|
|
146
|
+
const found = await guild.members.search({
|
|
147
|
+
query: String(args.query),
|
|
148
|
+
limit: typeof args.limit === "number" ? args.limit : 10,
|
|
149
|
+
});
|
|
150
|
+
return [...found.values()].map((m) => ({
|
|
151
|
+
id: m.id,
|
|
152
|
+
username: m.user.username,
|
|
153
|
+
displayName: m.displayName,
|
|
154
|
+
nickname: m.nickname,
|
|
155
|
+
bot: m.user.bot,
|
|
156
|
+
roles: m.roles.cache
|
|
157
|
+
.filter((r) => r.id !== guild.id)
|
|
158
|
+
.map((r) => r.name),
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
case "discord_create_role": {
|
|
162
|
+
const permissions = permissionFlags(args.permissions);
|
|
163
|
+
this.#access.holdsAll(owner, permissions);
|
|
164
|
+
const role = await guild.roles.create({
|
|
165
|
+
...roleOptions(args, permissions),
|
|
166
|
+
reason,
|
|
167
|
+
});
|
|
168
|
+
return { id: role.id, name: role.name };
|
|
169
|
+
}
|
|
170
|
+
case "discord_edit_role": {
|
|
171
|
+
const role = await this.#access.role(guild, owner, args.roleId);
|
|
172
|
+
const permissions =
|
|
173
|
+
args.permissions === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: permissionFlags(args.permissions);
|
|
176
|
+
if (permissions) this.#access.holdsAll(owner, permissions);
|
|
177
|
+
await role.edit({ ...roleOptions(args, permissions), reason });
|
|
178
|
+
return { id: role.id, name: role.name };
|
|
179
|
+
}
|
|
180
|
+
case "discord_delete_role": {
|
|
181
|
+
const role = await this.#access.role(guild, owner, args.roleId);
|
|
182
|
+
await role.delete(reason);
|
|
183
|
+
return { id: role.id, deleted: true };
|
|
184
|
+
}
|
|
185
|
+
case "discord_add_member_role":
|
|
186
|
+
case "discord_remove_member_role": {
|
|
187
|
+
const role = await this.#access.role(guild, owner, args.roleId);
|
|
188
|
+
const member = await this.#access.member(guild, args.userId);
|
|
189
|
+
if (tool === "discord_add_member_role")
|
|
190
|
+
await member.roles.add(role, reason);
|
|
191
|
+
else await member.roles.remove(role, reason);
|
|
192
|
+
return { userId: member.id, roleId: role.id };
|
|
193
|
+
}
|
|
194
|
+
case "discord_set_nickname": {
|
|
195
|
+
const member = await this.#access.member(guild, args.userId);
|
|
196
|
+
// The assistant's own nickname is the owner's to set wherever the owner may manage nicknames.
|
|
197
|
+
if (member.id !== bot.id) this.#access.outranks(owner, member);
|
|
198
|
+
const nickname = String(args.nickname).trim();
|
|
199
|
+
await member.setNickname(nickname || null, reason);
|
|
200
|
+
return { userId: member.id, nickname: nickname || null };
|
|
201
|
+
}
|
|
202
|
+
case "discord_timeout_member": {
|
|
203
|
+
const member = await this.#access.member(guild, args.userId);
|
|
204
|
+
this.#access.outranks(owner, member);
|
|
205
|
+
const minutes = Number(args.minutes);
|
|
206
|
+
await member.timeout(minutes > 0 ? minutes * 60_000 : null, reason);
|
|
207
|
+
return { userId: member.id, minutes };
|
|
208
|
+
}
|
|
209
|
+
case "discord_kick_member": {
|
|
210
|
+
const member = await this.#access.member(guild, args.userId);
|
|
211
|
+
this.#access.outranks(owner, member);
|
|
212
|
+
await member.kick(reason);
|
|
213
|
+
return { userId: member.id, kicked: true };
|
|
214
|
+
}
|
|
215
|
+
case "discord_ban_member": {
|
|
216
|
+
const userId = String(args.userId);
|
|
217
|
+
// A user who already left can still be banned by id.
|
|
218
|
+
const member = await guild.members
|
|
219
|
+
.fetch({ user: userId, force: true })
|
|
220
|
+
.catch(() => undefined);
|
|
221
|
+
if (member) this.#access.outranks(owner, member);
|
|
222
|
+
await guild.members.ban(userId, {
|
|
223
|
+
deleteMessageSeconds:
|
|
224
|
+
typeof args.deleteMessageSeconds === "number"
|
|
225
|
+
? args.deleteMessageSeconds
|
|
226
|
+
: 0,
|
|
227
|
+
reason,
|
|
228
|
+
});
|
|
229
|
+
return { userId, banned: true };
|
|
230
|
+
}
|
|
231
|
+
case "discord_unban_member": {
|
|
232
|
+
const userId = String(args.userId);
|
|
233
|
+
await guild.members.unban(userId, reason);
|
|
234
|
+
return { userId, banned: false };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
throw refuse("INVALID_TOOL", tool);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Servers the owner and the assistant share, with what each of them may do there. */
|
|
241
|
+
async #servers() {
|
|
242
|
+
const servers = [];
|
|
243
|
+
for (const guild of this.#client.guilds.cache.values()) {
|
|
244
|
+
const owner = await guild.members
|
|
245
|
+
.fetch({ user: this.#ownerId })
|
|
246
|
+
.catch(() => undefined);
|
|
247
|
+
if (!owner) continue;
|
|
248
|
+
const bot = await guild.members.fetchMe();
|
|
249
|
+
servers.push({
|
|
250
|
+
id: guild.id,
|
|
251
|
+
name: guild.name,
|
|
252
|
+
memberCount: guild.memberCount,
|
|
253
|
+
ownerOwnsServer: guild.ownerId === this.#ownerId,
|
|
254
|
+
assistantPermissions: permissionNames(bot),
|
|
255
|
+
ownerPermissions: permissionNames(owner),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return servers;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async #deleteChannel(args: Record<string, unknown>) {
|
|
262
|
+
const channel = await this.#client.channels
|
|
263
|
+
.fetch(String(args.channelId), { force: true })
|
|
264
|
+
.catch(() => null);
|
|
265
|
+
if (!channel || channel.isDMBased())
|
|
266
|
+
throw refuse("INVALID_CHANNEL", messages().refuseNotVisible);
|
|
267
|
+
await this.#access.members(
|
|
268
|
+
channel.guild,
|
|
269
|
+
[
|
|
270
|
+
PermissionFlagsBits.ViewChannel,
|
|
271
|
+
channel.isThread()
|
|
272
|
+
? PermissionFlagsBits.ManageThreads
|
|
273
|
+
: PermissionFlagsBits.ManageChannels,
|
|
274
|
+
],
|
|
275
|
+
channel,
|
|
276
|
+
);
|
|
277
|
+
await channel.delete(
|
|
278
|
+
typeof args.reason === "string" ? args.reason : this.#reason,
|
|
279
|
+
);
|
|
280
|
+
return { id: channel.id, deleted: true };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ActionRowBuilder,
|
|
3
|
+
type ButtonBuilder,
|
|
4
|
+
type ChatInputCommandInteraction,
|
|
5
|
+
ContainerBuilder,
|
|
6
|
+
MessageFlags,
|
|
7
|
+
SeparatorBuilder,
|
|
8
|
+
SeparatorSpacingSize,
|
|
9
|
+
type StringSelectMenuBuilder,
|
|
10
|
+
TextDisplayBuilder,
|
|
11
|
+
} from "discord.js";
|
|
12
|
+
import { messages } from "../i18n/index.ts";
|
|
13
|
+
|
|
14
|
+
export interface PanelContent {
|
|
15
|
+
title: string;
|
|
16
|
+
/** Markdown sections, each its own text block. */
|
|
17
|
+
sections: string[];
|
|
18
|
+
/** One quiet line under the content, such as what to do next. */
|
|
19
|
+
footer?: string;
|
|
20
|
+
rows?: ActionRowBuilder<ButtonBuilder | StringSelectMenuBuilder>[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A message the owner should read as is; anything else becomes a generic failure. */
|
|
24
|
+
export class OwnerFacingError extends Error {}
|
|
25
|
+
|
|
26
|
+
/** Discord allows 4,000 characters of text per Components V2 message; keep a margin. */
|
|
27
|
+
const PANEL_TEXT_LIMIT = 3_600;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The single look of every root-command answer: a Components V2 container with no accent color
|
|
31
|
+
* (Discord draws accents as a bar on one side), a heading, sections, controls, and a footer.
|
|
32
|
+
*/
|
|
33
|
+
export function ownerPanel(content: PanelContent) {
|
|
34
|
+
const container = new ContainerBuilder().addTextDisplayComponents(
|
|
35
|
+
new TextDisplayBuilder().setContent(`### ${content.title}`),
|
|
36
|
+
);
|
|
37
|
+
for (const section of content.sections)
|
|
38
|
+
container.addTextDisplayComponents(
|
|
39
|
+
new TextDisplayBuilder().setContent(section),
|
|
40
|
+
);
|
|
41
|
+
if (content.rows?.length)
|
|
42
|
+
container
|
|
43
|
+
.addSeparatorComponents(
|
|
44
|
+
new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Small),
|
|
45
|
+
)
|
|
46
|
+
.addActionRowComponents(...content.rows);
|
|
47
|
+
if (content.footer)
|
|
48
|
+
container.addTextDisplayComponents(
|
|
49
|
+
new TextDisplayBuilder().setContent(`-# ${content.footer}`),
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
components: [container],
|
|
53
|
+
flags: MessageFlags.IsComponentsV2 as const,
|
|
54
|
+
allowedMentions: { parse: [] },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A panel only the person who used the command sees. */
|
|
59
|
+
export function ephemeralPanel(content: PanelContent) {
|
|
60
|
+
return {
|
|
61
|
+
...ownerPanel(content),
|
|
62
|
+
flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Answers a deferred command with the panel, its overflow pages as ephemeral follow-ups. */
|
|
67
|
+
export async function replyWithPanels(
|
|
68
|
+
interaction: ChatInputCommandInteraction,
|
|
69
|
+
content: PanelContent,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const [first, ...rest] = ownerPanels(content);
|
|
72
|
+
if (first) await interaction.editReply(ownerPanel(first));
|
|
73
|
+
for (const page of rest) await interaction.followUp(ephemeralPanel(page));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Splits long sections into panels that each fit Discord's text limit. */
|
|
77
|
+
export function ownerPanels(content: PanelContent): PanelContent[] {
|
|
78
|
+
const pages: PanelContent[] = [];
|
|
79
|
+
let sections: string[] = [];
|
|
80
|
+
let size = content.title.length;
|
|
81
|
+
for (const section of content.sections) {
|
|
82
|
+
if (sections.length > 0 && size + section.length > PANEL_TEXT_LIMIT) {
|
|
83
|
+
pages.push({ title: content.title, sections });
|
|
84
|
+
sections = [];
|
|
85
|
+
size = content.title.length;
|
|
86
|
+
}
|
|
87
|
+
sections.push(section.slice(0, PANEL_TEXT_LIMIT));
|
|
88
|
+
size += section.length;
|
|
89
|
+
}
|
|
90
|
+
pages.push({ ...content, sections });
|
|
91
|
+
return pages.map((page, i) =>
|
|
92
|
+
pages.length > 1
|
|
93
|
+
? {
|
|
94
|
+
...page,
|
|
95
|
+
title: messages().panelPage(page.title, i + 1, pages.length),
|
|
96
|
+
}
|
|
97
|
+
: page,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Text a user typed, made safe to put inside Discord markdown. */
|
|
102
|
+
export const plain = (value: string): string =>
|
|
103
|
+
value.replace(/[\n\r`*_~<>|#@]/g, " ").trim();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AutocompleteInteraction,
|
|
3
|
+
ChatInputCommandInteraction,
|
|
4
|
+
SlashCommandSubcommandGroupBuilder,
|
|
5
|
+
} from "discord.js";
|
|
6
|
+
import type { InteractionContribution } from "../contract/discord.ts";
|
|
7
|
+
import { messages } from "../i18n/index.ts";
|
|
8
|
+
import { describeRecurrence } from "../modules/schedules/recurrence.ts";
|
|
9
|
+
import type {
|
|
10
|
+
Schedule,
|
|
11
|
+
ScheduleStore,
|
|
12
|
+
} from "../modules/schedules/schedule-store.ts";
|
|
13
|
+
import {
|
|
14
|
+
groupOption,
|
|
15
|
+
type OwnerGuard,
|
|
16
|
+
ownerCommandModule,
|
|
17
|
+
} from "./owner-command.ts";
|
|
18
|
+
import {
|
|
19
|
+
OwnerFacingError,
|
|
20
|
+
ownerPanel,
|
|
21
|
+
plain,
|
|
22
|
+
replyWithPanels,
|
|
23
|
+
} from "./owner-panel.ts";
|
|
24
|
+
|
|
25
|
+
const PROMPT_PREVIEW = 160;
|
|
26
|
+
|
|
27
|
+
function scheduleGroup(group: SlashCommandSubcommandGroupBuilder) {
|
|
28
|
+
return group
|
|
29
|
+
.setName("schedule")
|
|
30
|
+
.setDescription(messages().scheduleGroupDescription)
|
|
31
|
+
.addSubcommand((sub) =>
|
|
32
|
+
sub.setName("list").setDescription(messages().scheduleListDescription),
|
|
33
|
+
)
|
|
34
|
+
.addSubcommand((sub) =>
|
|
35
|
+
sub
|
|
36
|
+
.setName("cancel")
|
|
37
|
+
.setDescription(messages().scheduleCancelDescription)
|
|
38
|
+
.addStringOption((option) =>
|
|
39
|
+
option
|
|
40
|
+
.setName("id")
|
|
41
|
+
.setDescription(messages().scheduleIdDescription)
|
|
42
|
+
.setRequired(true)
|
|
43
|
+
.setAutocomplete(true),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function channelMention(schedule: Schedule): string {
|
|
49
|
+
const [surface, id] = schedule.channel.split(":");
|
|
50
|
+
return surface === "discord" && id ? `<#${id}>` : plain(schedule.channel);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const unix = (at: Date) => Math.floor(at.getTime() / 1000);
|
|
54
|
+
|
|
55
|
+
function section(schedule: Schedule): string {
|
|
56
|
+
const prompt =
|
|
57
|
+
schedule.prompt.length > PROMPT_PREVIEW
|
|
58
|
+
? `${schedule.prompt.slice(0, PROMPT_PREVIEW)}…`
|
|
59
|
+
: schedule.prompt;
|
|
60
|
+
const text = messages();
|
|
61
|
+
const last = schedule.lastRun
|
|
62
|
+
? text.scheduleLastRun(
|
|
63
|
+
unix(schedule.lastRun),
|
|
64
|
+
plain(schedule.lastStatus ?? "?"),
|
|
65
|
+
)
|
|
66
|
+
: "";
|
|
67
|
+
const mode =
|
|
68
|
+
schedule.mode === "owner" ? text.scheduleModeOwner : text.scheduleModeParty;
|
|
69
|
+
return [
|
|
70
|
+
text.scheduleHead(
|
|
71
|
+
schedule.id,
|
|
72
|
+
plain(schedule.title),
|
|
73
|
+
channelMention(schedule),
|
|
74
|
+
mode,
|
|
75
|
+
),
|
|
76
|
+
text.scheduleNextRun(
|
|
77
|
+
describeRecurrence(schedule.recurrence),
|
|
78
|
+
unix(schedule.nextRun),
|
|
79
|
+
),
|
|
80
|
+
text.scheduleSetBy(plain(schedule.createdByName), last),
|
|
81
|
+
`-# ${plain(prompt)}`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `/<root> schedule`: the owner's view over every channel's schedules. */
|
|
86
|
+
export class ScheduleCommands {
|
|
87
|
+
readonly #store: ScheduleStore;
|
|
88
|
+
|
|
89
|
+
constructor(store: ScheduleStore) {
|
|
90
|
+
this.#store = store;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async autocomplete(interaction: AutocompleteInteraction): Promise<void> {
|
|
94
|
+
const query = interaction.options.getFocused().trim().toLocaleLowerCase();
|
|
95
|
+
const choices = (await this.#store.all())
|
|
96
|
+
.map((s) => ({
|
|
97
|
+
name: messages()
|
|
98
|
+
.scheduleChoice(s.id, s.title, describeRecurrence(s.recurrence))
|
|
99
|
+
.slice(0, 100),
|
|
100
|
+
value: String(s.id),
|
|
101
|
+
}))
|
|
102
|
+
.filter((choice) => choice.name.toLocaleLowerCase().includes(query));
|
|
103
|
+
await interaction.respond(choices.slice(0, 25));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The interaction is deferred and ephemeral. */
|
|
107
|
+
async command(interaction: ChatInputCommandInteraction): Promise<void> {
|
|
108
|
+
if (interaction.options.getSubcommand(true) === "cancel")
|
|
109
|
+
return this.#cancel(interaction);
|
|
110
|
+
const all = await this.#store.all();
|
|
111
|
+
await replyWithPanels(interaction, {
|
|
112
|
+
title: messages().scheduleTitle,
|
|
113
|
+
sections: all.length ? all.map(section) : [messages().scheduleNone],
|
|
114
|
+
footer: messages().scheduleFooter,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async #cancel(interaction: ChatInputCommandInteraction): Promise<void> {
|
|
119
|
+
const raw = interaction.options.getString("id", true).replace(/^#/, "");
|
|
120
|
+
const id = Number(raw);
|
|
121
|
+
if (!Number.isInteger(id))
|
|
122
|
+
throw new OwnerFacingError(messages().schedulePickOne);
|
|
123
|
+
const removed = await this.#store.remove(id);
|
|
124
|
+
if (!removed)
|
|
125
|
+
throw new OwnerFacingError(messages().scheduleUnknown(plain(raw)));
|
|
126
|
+
await interaction.editReply(
|
|
127
|
+
ownerPanel({
|
|
128
|
+
title: messages().scheduleCancelled,
|
|
129
|
+
sections: [section(removed)],
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** `/<root> schedule list|cancel`. */
|
|
136
|
+
export function scheduleCommands(
|
|
137
|
+
guard: OwnerGuard,
|
|
138
|
+
store: ScheduleStore,
|
|
139
|
+
): InteractionContribution {
|
|
140
|
+
const commands = new ScheduleCommands(store);
|
|
141
|
+
return {
|
|
142
|
+
module: ownerCommandModule(guard, {
|
|
143
|
+
owns: (group) => group === "schedule",
|
|
144
|
+
autocomplete: (interaction) => commands.autocomplete(interaction),
|
|
145
|
+
command: (interaction) => commands.command(interaction),
|
|
146
|
+
}),
|
|
147
|
+
rootOptions: [groupOption(scheduleGroup)],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
|
2
|
+
import { messages } from "../i18n/index.ts";
|
|
3
|
+
|
|
4
|
+
export const STOP_BUTTON_ID = "roundtable:stop";
|
|
5
|
+
|
|
6
|
+
/** The message a long turn shows: a stop button only the owner may press. */
|
|
7
|
+
export function stopPanel() {
|
|
8
|
+
return {
|
|
9
|
+
content: messages().stopNote,
|
|
10
|
+
components: [
|
|
11
|
+
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
12
|
+
new ButtonBuilder()
|
|
13
|
+
.setCustomId(STOP_BUTTON_ID)
|
|
14
|
+
.setLabel(messages().stopLabel)
|
|
15
|
+
.setEmoji("⏹️")
|
|
16
|
+
.setStyle(ButtonStyle.Danger),
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type { AttachmentRef } from "../contract/channels.ts";
|
|
2
|
+
|
|
3
|
+
/** An attachment saved in the channel's attachment store. */
|
|
4
|
+
export interface StoredAttachment {
|
|
5
|
+
name: string;
|
|
6
|
+
/** File name inside the channel's attachment store. */
|
|
7
|
+
file: string;
|
|
8
|
+
/** Absolute path on the host. */
|
|
9
|
+
path: string;
|
|
10
|
+
contentType: string;
|
|
11
|
+
size: number;
|
|
12
|
+
/** True for files from the message this one replies to. */
|
|
13
|
+
fromReference: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AttachmentFailure {
|
|
17
|
+
name: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
fromReference: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** An image in the form the model accepts. */
|
|
23
|
+
export interface ModelImage {
|
|
24
|
+
data: string;
|
|
25
|
+
mimeType: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Everything a turn carries besides its text. */
|
|
29
|
+
export interface TurnAttachments {
|
|
30
|
+
files: StoredAttachment[];
|
|
31
|
+
images: ModelImage[];
|
|
32
|
+
failures: AttachmentFailure[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const NO_ATTACHMENTS: TurnAttachments = Object.freeze({
|
|
36
|
+
files: [],
|
|
37
|
+
images: [],
|
|
38
|
+
failures: [],
|
|
39
|
+
}) as TurnAttachments;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { InboundMessage } from "../contract/channels.ts";
|
|
2
|
+
import type { ChannelKey } from "../sessions.ts";
|
|
3
|
+
import type { AgentRunError } from "./errors.ts";
|
|
4
|
+
import type { ProfileId } from "./profile.ts";
|
|
5
|
+
|
|
6
|
+
export { QUEUED_MARK, STEERED_MARK } from "../contract/channels.ts";
|
|
7
|
+
export type { ChannelKey, InboundMessage };
|
|
8
|
+
|
|
9
|
+
export interface TranscriptEntry {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** One tool call held for the owner's confirmation, exactly as the model made it. */
|
|
15
|
+
export interface HeldCall {
|
|
16
|
+
tool: string;
|
|
17
|
+
/** The call's input as canonical JSON; only an identical call is released. */
|
|
18
|
+
input: string;
|
|
19
|
+
/** What the call would do, in plain words. */
|
|
20
|
+
action: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PendingConfirmation {
|
|
24
|
+
profile: ProfileId;
|
|
25
|
+
heldAt: Date;
|
|
26
|
+
calls: HeldCall[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TurnResult =
|
|
30
|
+
| { ok: true; text: string; thinking?: string }
|
|
31
|
+
| { ok: false; error: AgentRunError; stopped?: true };
|
|
32
|
+
|
|
33
|
+
export interface OutboundReply {
|
|
34
|
+
/** The assistant's thinking for this turn, posted as a quiet line before the card. */
|
|
35
|
+
thinking?: string;
|
|
36
|
+
/** PNG card, absent when rendering failed or the channel shows none. */
|
|
37
|
+
card?: Uint8Array;
|
|
38
|
+
/** Message texts in posting order; the card is posted before them. */
|
|
39
|
+
chunks: string[];
|
|
40
|
+
/** Files the run produced, posted after the text. */
|
|
41
|
+
files?: { name: string; data: Uint8Array }[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Error types shared across layers. Each carries the failing component in its name so logs and
|
|
2
|
+
// tests can tell them apart without string matching.
|
|
3
|
+
|
|
4
|
+
export class ConfigError extends Error {
|
|
5
|
+
override name = "ConfigError";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class AgentRunError extends Error {
|
|
9
|
+
override name = "AgentRunError";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class CardRenderError extends Error {
|
|
13
|
+
override name = "CardRenderError";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class MemoryError extends Error {
|
|
17
|
+
override name = "MemoryError";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ScheduleError extends Error {
|
|
21
|
+
override name = "ScheduleError";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class DelegationError extends Error {
|
|
25
|
+
override name = "DelegationError";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A request about agents or groups that cannot be met; its message is shown to whoever asked. */
|
|
29
|
+
export class AgentError extends Error {
|
|
30
|
+
override name = "AgentError";
|
|
31
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const EXPRESSIONS = [
|
|
2
|
+
"neutral",
|
|
3
|
+
"happy",
|
|
4
|
+
"thinking",
|
|
5
|
+
"concerned",
|
|
6
|
+
"apologetic",
|
|
7
|
+
"surprised",
|
|
8
|
+
"proud",
|
|
9
|
+
"amused",
|
|
10
|
+
"curious",
|
|
11
|
+
"sympathetic",
|
|
12
|
+
"playful",
|
|
13
|
+
"determined",
|
|
14
|
+
"sleepy",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export type Expression = (typeof EXPRESSIONS)[number];
|
|
18
|
+
|
|
19
|
+
export function isExpression(value: string): value is Expression {
|
|
20
|
+
return (EXPRESSIONS as readonly string[]).includes(value);
|
|
21
|
+
}
|