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
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "
|
|
5
|
-
import { agentClaim } from "../agents/agent-claim.ts";
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { SQL } from "bun";
|
|
4
|
+
import { agentClaim, OWNER_TARGET } from "../agents/agent-claim.ts";
|
|
6
5
|
import { AgentDashboard } from "../agents/agent-dashboard.ts";
|
|
7
|
-
import {
|
|
6
|
+
import { PgAgentStore } from "../agents/agent-store.ts";
|
|
7
|
+
import { DiscordAgentTeam } from "../agents/agent-team.ts";
|
|
8
|
+
import { AGENT_TOOLS } from "../agents/agent-tools.ts";
|
|
9
|
+
import { FileAvatarStudio } from "../agents/avatar-studio.ts";
|
|
8
10
|
import { RelevanceScorer } from "../agents/group-round.ts";
|
|
9
|
-
import {
|
|
11
|
+
import { discordKey } from "../agents/team-keys.ts";
|
|
10
12
|
import { ownerAttachmentDir } from "../attachments/attachment-dir.ts";
|
|
13
|
+
import type { AgentRuntime, AgentSessions } from "../contract/runtime.ts";
|
|
11
14
|
import type { ChannelKey } from "../domain/conversation.ts";
|
|
12
15
|
import type { OwnerIdentity } from "../identity.ts";
|
|
13
16
|
import { ConfirmationJudge } from "../judging/confirmation-judge.ts";
|
|
@@ -18,14 +21,17 @@ import {
|
|
|
18
21
|
type ModelRef,
|
|
19
22
|
type ThinkingLevel,
|
|
20
23
|
} from "../models.ts";
|
|
21
|
-
import { SkillRegistry } from "../modules/skills/skill-registry.ts";
|
|
22
24
|
import type { ErrorReporter } from "../ops/error-reporter.ts";
|
|
23
|
-
import type { RoundtablePlugin } from "../plugin.ts";
|
|
25
|
+
import type { PluginContext, RoundtablePlugin } from "../plugin.ts";
|
|
24
26
|
import { splitReply } from "../presentation/reply-splitter.ts";
|
|
25
27
|
import { agentPromptExtension } from "../runtime/extensions/agent-prompt.ts";
|
|
28
|
+
import { PendingConfirmationStore } from "../runtime/pending-confirmation-store.ts";
|
|
26
29
|
import { PiAgentRuntime } from "../runtime/pi-agent-runtime.ts";
|
|
27
|
-
import
|
|
28
|
-
import type {
|
|
30
|
+
import { AGENTS, BACKGROUND_TURNS, SCHEDULES, SKILLS } from "../services.ts";
|
|
31
|
+
import type { SessionTool } from "../sessions.ts";
|
|
32
|
+
import type { Speaker, SpeakerPolicy, Tier } from "../speakers.ts";
|
|
33
|
+
import { DISCORD } from "./discord.ts";
|
|
34
|
+
import { agentOnly } from "./session-tool.ts";
|
|
29
35
|
|
|
30
36
|
export interface AgentServerOptions {
|
|
31
37
|
guildId: string;
|
|
@@ -51,37 +57,28 @@ export interface AgentServerOptions {
|
|
|
51
57
|
shellUser: string;
|
|
52
58
|
/** The prompt every agent starts with, and the one for speakers other than the owner. */
|
|
53
59
|
prompts: { shared: string; guest?: string };
|
|
54
|
-
/** Where the skill registry reads and writes skills. */
|
|
55
|
-
skills: { reposDir: string; writtenDir: string; builtinDir: string };
|
|
56
60
|
/** The listener the avatar pictures are served on. */
|
|
57
61
|
avatarListener: string;
|
|
62
|
+
/** The origin the agents' pictures are served from. */
|
|
63
|
+
avatarUrl: string;
|
|
64
|
+
/** The assistant's neutral avatar: the style reference and the picture of an agent without one. */
|
|
65
|
+
avatarReference: string;
|
|
58
66
|
/** Reports the process's own errors to an agent; without one nothing is reported. */
|
|
59
67
|
errorReporter?: ErrorReporter;
|
|
60
|
-
/**
|
|
61
|
-
* Conversations outside the agent server, such as the owner's own: their system prompt, and the
|
|
62
|
-
* tools startup refuses to run without.
|
|
63
|
-
*/
|
|
64
|
-
ownerSessions?: { persona: string; requiredTools: readonly string[] };
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
snapshot: () => ({
|
|
76
|
-
revision: 0,
|
|
77
|
-
factory: (session) => (session.agent ? factory(session.agent) : null),
|
|
78
|
-
}),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
70
|
+
/** The name of the agent server's plugin, as `serviceStarted` events name it. */
|
|
71
|
+
export const AGENT_SERVER_PLUGIN = "agent-server";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The agent server's service that starts the team and the dashboard in the background: once its
|
|
75
|
+
* `serviceStarted` event says `ready`, the agents' channels and the dashboard are up.
|
|
76
|
+
*/
|
|
77
|
+
export const AGENT_TEAM_SERVICE = "team";
|
|
81
78
|
|
|
82
79
|
/** The agent tools and the agent's own prompt, which only agent sessions load. */
|
|
83
80
|
export function agentSessionTools(
|
|
84
|
-
team: Pick<
|
|
81
|
+
team: Pick<DiscordAgentTeam, "extension" | "systemPrompt">,
|
|
85
82
|
): SessionTool[] {
|
|
86
83
|
return [
|
|
87
84
|
agentOnly("agent-tools", (scope) => team.extension(scope)),
|
|
@@ -91,30 +88,78 @@ export function agentSessionTools(
|
|
|
91
88
|
];
|
|
92
89
|
}
|
|
93
90
|
|
|
91
|
+
/** What every speaker may do with agents; admins create and edit them. */
|
|
92
|
+
const AGENT_TIERS: Readonly<Record<string, Tier>> = {
|
|
93
|
+
...Object.fromEntries(AGENT_TOOLS.map((tool) => [tool, "admin" as const])),
|
|
94
|
+
agent_list: "member",
|
|
95
|
+
agent_get: "member",
|
|
96
|
+
message_agent: "member",
|
|
97
|
+
channel_read: "member",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** The tables the agent server keeps: its agents and groups, and the held actions. */
|
|
101
|
+
export interface AgentServerStores {
|
|
102
|
+
agents: PgAgentStore;
|
|
103
|
+
/** Held actions, kept across a restart. */
|
|
104
|
+
confirmations: PendingConfirmationStore;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Attaches the agent server's stores over the host's migrated pool. */
|
|
108
|
+
async function attachStores(
|
|
109
|
+
sql: SQL,
|
|
110
|
+
guildId: string,
|
|
111
|
+
): Promise<AgentServerStores> {
|
|
112
|
+
return {
|
|
113
|
+
agents: await PgAgentStore.attach(sql, guildId),
|
|
114
|
+
confirmations: await PendingConfirmationStore.attach(sql),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
/**
|
|
95
|
-
* The agent server: the
|
|
96
|
-
*
|
|
119
|
+
* The agent server: the team with its channels and dashboard, and the runtime that runs every
|
|
120
|
+
* agent turn. The skills the agents carry come from the skills addon when it is on. It claims the agent channels, starts the team in the
|
|
97
121
|
* background, and hands what it built to the plugins after it.
|
|
98
122
|
*/
|
|
99
123
|
export function agentServerPlugin(
|
|
100
124
|
options: AgentServerOptions,
|
|
125
|
+
/** Opens the stores; a test hands stand-ins, so the plugin can be set up without a database. */
|
|
126
|
+
openStores: (
|
|
127
|
+
context: Pick<PluginContext, "database">,
|
|
128
|
+
guildId: string,
|
|
129
|
+
) => Promise<AgentServerStores> = (context, guildId) =>
|
|
130
|
+
attachStores(context.database(), guildId),
|
|
101
131
|
): RoundtablePlugin {
|
|
102
|
-
let runtime:
|
|
103
|
-
let team: AgentTeam | undefined;
|
|
104
|
-
let skills: SkillRegistry | undefined;
|
|
132
|
+
let runtime: AgentRuntime | undefined;
|
|
105
133
|
return {
|
|
106
|
-
name:
|
|
107
|
-
// The
|
|
134
|
+
name: AGENT_SERVER_PLUGIN,
|
|
135
|
+
// The agent server's own tables; the host runs them before any setup, in this order.
|
|
136
|
+
migrations: [
|
|
137
|
+
PendingConfirmationStore.migration,
|
|
138
|
+
...PgAgentStore.migrations(options.guildId),
|
|
139
|
+
],
|
|
140
|
+
provides: [AGENTS],
|
|
108
141
|
preflight: async () => {
|
|
109
|
-
|
|
110
|
-
await runtime?.preflight();
|
|
142
|
+
await runtime?.preflight?.();
|
|
111
143
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const {
|
|
116
|
-
const {
|
|
117
|
-
const
|
|
144
|
+
setup: async (context) => {
|
|
145
|
+
const discord = context.services.get(DISCORD);
|
|
146
|
+
const schedules = context.services.get(SCHEDULES);
|
|
147
|
+
const { connection } = discord;
|
|
148
|
+
const { logger, providers, surfaces } = context;
|
|
149
|
+
const studio = new FileAvatarStudio({
|
|
150
|
+
dir: join(options.dataDir, "avatars"),
|
|
151
|
+
publicUrl: options.avatarUrl,
|
|
152
|
+
referencePath: options.avatarReference,
|
|
153
|
+
// Without an image provider the studio makes each agent a picture from its name.
|
|
154
|
+
...(providers.filled.has("images") ? { draw: providers.images } : {}),
|
|
155
|
+
});
|
|
156
|
+
await studio.init();
|
|
157
|
+
// Absent when the skills addon is off: the agents then carry none.
|
|
158
|
+
const skills = context.services.find(SKILLS);
|
|
159
|
+
const { agents: store, confirmations: heldActions } = await openStores(
|
|
160
|
+
context,
|
|
161
|
+
options.guildId,
|
|
162
|
+
);
|
|
118
163
|
const judge = providers.judge;
|
|
119
164
|
const confirmations = new ConfirmationJudge({
|
|
120
165
|
judge,
|
|
@@ -122,15 +167,7 @@ export function agentServerPlugin(
|
|
|
122
167
|
assistant: options.assistant,
|
|
123
168
|
logger,
|
|
124
169
|
});
|
|
125
|
-
const
|
|
126
|
-
store: stores.skills,
|
|
127
|
-
reposDir: options.skills.reposDir,
|
|
128
|
-
writtenDir: options.skills.writtenDir,
|
|
129
|
-
builtinDir: options.skills.builtinDir,
|
|
130
|
-
logger,
|
|
131
|
-
});
|
|
132
|
-
skills = registry;
|
|
133
|
-
const built: AgentTeam = new AgentTeam({
|
|
170
|
+
const built: DiscordAgentTeam = new DiscordAgentTeam({
|
|
134
171
|
guildId: options.guildId,
|
|
135
172
|
entryChannelId: options.entryChannelId,
|
|
136
173
|
seeds: () => context.sessions().seeds,
|
|
@@ -141,9 +178,9 @@ export function agentServerPlugin(
|
|
|
141
178
|
owner: options.owner,
|
|
142
179
|
toolTiers: context.toolTiers,
|
|
143
180
|
shellUser: options.shellUser,
|
|
144
|
-
store
|
|
145
|
-
channels:
|
|
146
|
-
studio
|
|
181
|
+
store,
|
|
182
|
+
channels: connection.agentChannels(options.guildId),
|
|
183
|
+
studio,
|
|
147
184
|
// The runtime is built next, with this team's tools.
|
|
148
185
|
runtime: () => {
|
|
149
186
|
if (!runtime) throw new Error("the runtime is built with the team");
|
|
@@ -166,109 +203,138 @@ export function agentServerPlugin(
|
|
|
166
203
|
return models.map((model) => `${model.provider}/${model.id}`);
|
|
167
204
|
},
|
|
168
205
|
},
|
|
169
|
-
schedules
|
|
206
|
+
schedules,
|
|
170
207
|
queue: context.queue,
|
|
171
|
-
startTyping: (channel) =>
|
|
172
|
-
showStop: (channel) =>
|
|
208
|
+
startTyping: (channel) => surfaces.startTyping(channel),
|
|
209
|
+
showStop: (channel) => surfaces.showStop(channel),
|
|
173
210
|
workDir: options.workDir,
|
|
174
211
|
sharedPrompt: options.prompts.shared,
|
|
175
212
|
...(options.prompts.guest
|
|
176
213
|
? { guestPrompt: options.prompts.guest }
|
|
177
214
|
: {}),
|
|
178
|
-
skills:
|
|
215
|
+
...(skills ? { skills } : {}),
|
|
179
216
|
threads: discord.threads,
|
|
180
217
|
logger,
|
|
181
218
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!built.owns(channelKey(channelId))) return;
|
|
219
|
+
connection.onChannelDeleted((channelId) => {
|
|
220
|
+
if (!built.owns(discordKey(channelId))) return;
|
|
185
221
|
void context.queue
|
|
186
|
-
.run(
|
|
222
|
+
.run(discordKey(channelId), () => built.channelDeleted(channelId))
|
|
187
223
|
// pi-lens-ignore: no-unknown-parameters
|
|
188
224
|
.catch((error: unknown) =>
|
|
189
225
|
logger.error({ channelId, err: error }, "archive failed"),
|
|
190
226
|
);
|
|
191
227
|
});
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
228
|
+
const agentSessions: AgentSessions = {
|
|
229
|
+
workDir: options.workDir,
|
|
230
|
+
modelOf: (name) => built.modelOf(name),
|
|
231
|
+
skills: (name) => built.skillsOf(name),
|
|
232
|
+
turnChannel: (scope) => built.turnChannel(scope),
|
|
233
|
+
};
|
|
234
|
+
const prompts = (channel: ChannelKey, speaker?: Speaker) =>
|
|
235
|
+
context.surfaces.prompts(channel, speaker);
|
|
236
|
+
// A plugin that fills the runtime slot replaces the whole conversation runtime; without
|
|
237
|
+
// one the agent server runs Pi, built from the options below.
|
|
238
|
+
const running: AgentRuntime = providers.filled.has("runtime")
|
|
239
|
+
? providers.runtime({
|
|
240
|
+
logger,
|
|
241
|
+
env: context.env,
|
|
242
|
+
owner: { id: options.owner.id, name: options.owner.name },
|
|
243
|
+
sessions: context.sessions,
|
|
244
|
+
toolTiers: context.toolTiers,
|
|
245
|
+
prompts,
|
|
246
|
+
agents: agentSessions,
|
|
247
|
+
confirmations: heldActions,
|
|
248
|
+
judge,
|
|
249
|
+
})
|
|
250
|
+
: new PiAgentRuntime({
|
|
251
|
+
owner: options.owner,
|
|
252
|
+
sessions: context.sessions,
|
|
253
|
+
agentDir: options.agentDir,
|
|
254
|
+
modelRuntime: options.modelRuntime,
|
|
255
|
+
dataDir: options.dataDir,
|
|
256
|
+
model: options.model,
|
|
257
|
+
thinking: options.thinking,
|
|
258
|
+
effort: new EffortJudge({
|
|
259
|
+
judge,
|
|
260
|
+
brief: AGENT_BRIEF,
|
|
261
|
+
fallback: options.thinking,
|
|
262
|
+
threshold: options.judgeThreshold,
|
|
263
|
+
logger,
|
|
264
|
+
}),
|
|
265
|
+
confirmations: heldActions,
|
|
266
|
+
toolTiers: context.toolTiers,
|
|
267
|
+
agents: agentSessions,
|
|
268
|
+
prompts,
|
|
269
|
+
logger,
|
|
270
|
+
});
|
|
220
271
|
runtime = running;
|
|
221
|
-
context.
|
|
272
|
+
context.services.provide(AGENTS, {
|
|
222
273
|
team: built,
|
|
274
|
+
directory: store,
|
|
223
275
|
runtime: running,
|
|
224
|
-
|
|
225
|
-
|
|
276
|
+
approvals: confirmations,
|
|
277
|
+
avatars: studio,
|
|
226
278
|
});
|
|
227
279
|
const attachmentDir = (channel: ChannelKey) =>
|
|
228
280
|
ownerAttachmentDir(options.dataDir, channel);
|
|
229
281
|
const dashboard = new AgentDashboard({
|
|
230
282
|
status: () => built.status(),
|
|
231
|
-
board:
|
|
283
|
+
board: connection.agentDashboard(options.guildId),
|
|
232
284
|
lines: () => context.dashboard(),
|
|
233
285
|
logger,
|
|
234
286
|
});
|
|
235
287
|
built.onChange(() => dashboard.changed());
|
|
288
|
+
const connectErrorReporter = () =>
|
|
289
|
+
options.errorReporter?.connect({
|
|
290
|
+
channelOf: (name) => {
|
|
291
|
+
const agent = store.agent(name);
|
|
292
|
+
return agent?.status === "active" && agent.channelId
|
|
293
|
+
? discordKey(agent.channelId)
|
|
294
|
+
: undefined;
|
|
295
|
+
},
|
|
296
|
+
post: (channel, text) =>
|
|
297
|
+
surfaces.sendReply(channel, { chunks: splitReply(text) }),
|
|
298
|
+
turn: (channel, text) =>
|
|
299
|
+
context.services
|
|
300
|
+
.get(BACKGROUND_TURNS)
|
|
301
|
+
.runErrorReport(channel, text),
|
|
302
|
+
logger,
|
|
303
|
+
});
|
|
236
304
|
return {
|
|
237
305
|
services: [
|
|
238
|
-
{ name: "runtime", stop: () => running.dispose() },
|
|
306
|
+
{ name: "runtime", stop: () => running.dispose?.() },
|
|
239
307
|
{ name: "dashboard", stop: () => dashboard.stop() },
|
|
308
|
+
{
|
|
309
|
+
name: AGENT_TEAM_SERVICE,
|
|
310
|
+
// The dashboard shows the team, so it starts once the team has; the error
|
|
311
|
+
// reporter connects whether or not the team did, since it reports the failure.
|
|
312
|
+
startInBackground: async () => {
|
|
313
|
+
try {
|
|
314
|
+
await built.start();
|
|
315
|
+
dashboard.start();
|
|
316
|
+
} finally {
|
|
317
|
+
connectErrorReporter();
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
},
|
|
240
321
|
],
|
|
322
|
+
// The schedules and delegated tasks of the owner's and the agents' conversations are its own.
|
|
323
|
+
backgroundTargets: [OWNER_TARGET],
|
|
241
324
|
channels: [
|
|
242
325
|
agentClaim({
|
|
243
326
|
owner: options.owner,
|
|
244
327
|
speakers: options.speakers,
|
|
245
328
|
team: built,
|
|
246
329
|
runtime: running,
|
|
247
|
-
surface,
|
|
330
|
+
surface: context.surfaces,
|
|
248
331
|
attachmentDir,
|
|
249
332
|
logger,
|
|
250
333
|
}),
|
|
251
334
|
],
|
|
252
|
-
http: [
|
|
335
|
+
http: [studio.route(options.avatarListener)],
|
|
253
336
|
sessionTools: agentSessionTools(built),
|
|
254
|
-
|
|
255
|
-
agentServer: (outcome) => {
|
|
256
|
-
options.errorReporter?.connect({
|
|
257
|
-
channelOf: (name) => {
|
|
258
|
-
const agent = stores.agents.agent(name);
|
|
259
|
-
return agent?.status === "active" && agent.channelId
|
|
260
|
-
? channelKey(agent.channelId)
|
|
261
|
-
: undefined;
|
|
262
|
-
},
|
|
263
|
-
post: (channel, text) =>
|
|
264
|
-
surface.sendReply(channel, { chunks: splitReply(text) }),
|
|
265
|
-
turn: (channel, text) =>
|
|
266
|
-
context.core.background.runErrorReport(channel, text),
|
|
267
|
-
logger,
|
|
268
|
-
});
|
|
269
|
-
if (outcome === "ready") dashboard.start();
|
|
270
|
-
},
|
|
271
|
-
},
|
|
337
|
+
toolTiers: AGENT_TIERS,
|
|
272
338
|
};
|
|
273
339
|
},
|
|
274
340
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { OwnerIdentity } from "../identity.ts";
|
|
2
|
+
import {
|
|
3
|
+
DISCORD_ADMIN_TOOLS,
|
|
4
|
+
discordAdminExtension,
|
|
5
|
+
} from "../modules/discord-admin/discord-admin.ts";
|
|
6
|
+
import type { RoundtablePlugin } from "../plugin.ts";
|
|
7
|
+
import { DISCORD } from "./discord.ts";
|
|
8
|
+
import { fixed } from "./session-tool.ts";
|
|
9
|
+
|
|
10
|
+
export interface DiscordAdminOptions {
|
|
11
|
+
/** Whose Discord permissions the tools check, and who they serve. */
|
|
12
|
+
owner: OwnerIdentity & { id: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discord administration: the owner's tools to read and manage the server, each a session tool
|
|
17
|
+
* of every conversation, built on the Discord connection. An addon; `discord.admin: false` leaves
|
|
18
|
+
* it out, and no conversation has a Discord tool.
|
|
19
|
+
*/
|
|
20
|
+
export function discordAdminPlugin(
|
|
21
|
+
options: DiscordAdminOptions,
|
|
22
|
+
): RoundtablePlugin {
|
|
23
|
+
const { owner } = options;
|
|
24
|
+
return {
|
|
25
|
+
name: "discord-admin",
|
|
26
|
+
setup: ({ services }) => {
|
|
27
|
+
const { connection } = services.get(DISCORD);
|
|
28
|
+
return {
|
|
29
|
+
sessionTools: [
|
|
30
|
+
fixed("discord-admin", () =>
|
|
31
|
+
discordAdminExtension(connection.ownerOperations(), owner),
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
// Administering the server stays with the owner until an operator lowers a tool.
|
|
35
|
+
toolTiers: Object.fromEntries(
|
|
36
|
+
DISCORD_ADMIN_TOOLS.map((tool) => [tool, "owner" as const]),
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { type ServiceKey, serviceKey } from "../contract/services.ts";
|
|
3
|
+
import { CommandCollection } from "../discord/command-collection.ts";
|
|
4
|
+
import type { DiscordConnection } from "../discord/connection.ts";
|
|
3
5
|
import { DiscordSurface } from "../discord/discord-surface.ts";
|
|
4
6
|
import { DispatchThreads } from "../discord/dispatch-threads.ts";
|
|
7
|
+
import type {
|
|
8
|
+
CommandGuard,
|
|
9
|
+
CommandRegistrar,
|
|
10
|
+
} from "../discord/interaction-module.ts";
|
|
5
11
|
import { OwnerCards } from "../discord/owner-cards.ts";
|
|
6
|
-
import { OwnerGuard } from "../discord/owner-command.ts";
|
|
12
|
+
import { OwnerGuard, ownerRootCommand } from "../discord/owner-command.ts";
|
|
13
|
+
import { stopButtonModule } from "../discord/stop-button.ts";
|
|
7
14
|
import type { RoundtablePlugin } from "../plugin.ts";
|
|
8
15
|
import type { SpeakerPolicy } from "../speakers.ts";
|
|
9
16
|
|
|
17
|
+
/** The Discord connection and what stands on it, as plugins use it. Provided by the Discord plugin. */
|
|
18
|
+
export interface DiscordServices {
|
|
19
|
+
connection: DiscordConnection;
|
|
20
|
+
/** Adds a plugin's slash commands and components; during setup only. */
|
|
21
|
+
commands: CommandRegistrar;
|
|
22
|
+
/** Who may use the owner's commands. */
|
|
23
|
+
guard: CommandGuard;
|
|
24
|
+
/** The threads that carry background reports. */
|
|
25
|
+
threads: DispatchThreads;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The Discord connection, provided by the Discord plugin. */
|
|
29
|
+
export const DISCORD: ServiceKey<DiscordServices> =
|
|
30
|
+
serviceKey<DiscordServices>("roundtable.discord");
|
|
31
|
+
|
|
10
32
|
export interface DiscordOptions {
|
|
11
33
|
token: string;
|
|
12
34
|
ownerId: string;
|
|
@@ -15,80 +37,83 @@ export interface DiscordOptions {
|
|
|
15
37
|
/** Who may answer a card besides the owner. */
|
|
16
38
|
speakers: SpeakerPolicy;
|
|
17
39
|
dataDir: string;
|
|
18
|
-
/** The origin the agents' pictures are served from. */
|
|
19
|
-
avatarUrl: string;
|
|
20
40
|
/** The name of the root slash command, without the slash. */
|
|
21
41
|
rootCommand: string;
|
|
22
|
-
/**
|
|
23
|
-
|
|
42
|
+
/** Text appended as it is to the refusal a non-owner gets, such as a pointer to the commands anyone may use. */
|
|
43
|
+
refusalHint?: string;
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
/**
|
|
27
|
-
* The Discord connection and what stands on it: the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
47
|
+
* The Discord connection and what stands on it: the owner's cards, the command guard, and the
|
|
48
|
+
* threads that carry background reports. It contributes the Discord surface, collects the
|
|
49
|
+
* plugins' slash commands while they set up, and composes them under the root command in its
|
|
50
|
+
* preflight, so a clash fails before any service starts.
|
|
30
51
|
*/
|
|
31
52
|
export function discordPlugin(options: DiscordOptions): RoundtablePlugin {
|
|
32
|
-
|
|
53
|
+
const collection = new CommandCollection();
|
|
54
|
+
let surface: DiscordSurface | undefined;
|
|
33
55
|
return {
|
|
34
56
|
name: "discord",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
44
|
-
await studio.init();
|
|
57
|
+
provides: [DISCORD],
|
|
58
|
+
preflight: () => {
|
|
59
|
+
// The host has applied its environment by now, so the description is in its language.
|
|
60
|
+
surface?.setCommands(
|
|
61
|
+
collection.compose(ownerRootCommand(options.rootCommand)),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
setup: async ({ conversations, services, logger }) => {
|
|
45
65
|
// The surface exists by the time a turn posts a card.
|
|
46
66
|
const cards: OwnerCards = new OwnerCards({
|
|
47
67
|
ownerId: options.ownerId,
|
|
48
68
|
speakers: options.speakers,
|
|
49
|
-
channel: (channelId) =>
|
|
69
|
+
channel: (channelId) => connected.cardChannel(channelId),
|
|
50
70
|
logger,
|
|
51
71
|
});
|
|
52
|
-
const
|
|
72
|
+
const connected = new DiscordSurface({
|
|
53
73
|
token: options.token,
|
|
54
74
|
ownerId: options.ownerId,
|
|
55
75
|
ownerName: options.ownerName,
|
|
76
|
+
prompts: (channel, speaker) => cards.prompts(channel, speaker),
|
|
56
77
|
logger,
|
|
57
78
|
});
|
|
79
|
+
surface = connected;
|
|
58
80
|
const guard = new OwnerGuard(
|
|
59
81
|
options.ownerId,
|
|
60
82
|
logger,
|
|
61
83
|
options.rootCommand,
|
|
84
|
+
options.refusalHint,
|
|
62
85
|
);
|
|
63
|
-
// A channel whose
|
|
86
|
+
// A channel whose claim keeps reports in place, such as an open channel, opens no thread.
|
|
64
87
|
const threads = new DispatchThreads({
|
|
65
88
|
host: {
|
|
66
89
|
open: (parentId, name, line) =>
|
|
67
|
-
|
|
68
|
-
post: (threadId, text) =>
|
|
69
|
-
close: (threadId) =>
|
|
90
|
+
connected.threadHost().open(parentId, name, line),
|
|
91
|
+
post: (threadId, text) => connected.threadHost().post(threadId, text),
|
|
92
|
+
close: (threadId) => connected.threadHost().close(threadId),
|
|
70
93
|
},
|
|
71
94
|
ledgerPath: join(options.dataDir, "dispatch-threads.json"),
|
|
72
95
|
excluded: (channel) => conversations.postsInPlace(channel),
|
|
73
96
|
logger,
|
|
74
97
|
});
|
|
75
|
-
|
|
76
|
-
core
|
|
98
|
+
// The cards answer the interactions of held actions, and core posts the stop button, so
|
|
99
|
+
// core answers both; they come first, as they always have.
|
|
100
|
+
collection.registrar.add({ module: cards });
|
|
101
|
+
collection.registrar.add({
|
|
102
|
+
module: stopButtonModule({ guard, conversations }),
|
|
103
|
+
});
|
|
104
|
+
services.provide(DISCORD, {
|
|
105
|
+
connection: connected,
|
|
106
|
+
commands: collection.registrar,
|
|
107
|
+
guard,
|
|
108
|
+
threads,
|
|
109
|
+
});
|
|
77
110
|
return {
|
|
111
|
+
// The host starts the surface first; it registers the composed commands as it connects.
|
|
112
|
+
surfaces: [connected],
|
|
78
113
|
services: [
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
start: async () => {
|
|
82
|
-
await surface.start(
|
|
83
|
-
(message) => void conversations.handle(message),
|
|
84
|
-
);
|
|
85
|
-
void threads.sweep();
|
|
86
|
-
},
|
|
87
|
-
stop: () => surface.stop(),
|
|
88
|
-
},
|
|
89
|
-
{ name: "channel-queue", busy: () => queue.busy() },
|
|
114
|
+
// The sweep reads threads through the connection, so it follows the surface's start.
|
|
115
|
+
{ name: "threads", start: () => void threads.sweep() },
|
|
90
116
|
],
|
|
91
|
-
interactions: [{ module: cards }],
|
|
92
117
|
};
|
|
93
118
|
},
|
|
94
119
|
};
|