pi-roundtable 0.1.0 → 0.2.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 +138 -0
- package/README.md +6 -4
- 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 +9 -2
- 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,31 +1,33 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { SurfacePort } from "../contract/surface.ts";
|
|
3
4
|
import { scheduleCommands } from "../discord/schedule-commands.ts";
|
|
4
5
|
import type { ChannelKey } from "../domain/conversation.ts";
|
|
5
6
|
import type { OwnerIdentity } from "../identity.ts";
|
|
6
7
|
import type { ModelRef, ThinkingLevel } from "../models.ts";
|
|
7
|
-
import {
|
|
8
|
+
import { ConversationBackgroundTurns } from "../modules/background/background-turns.ts";
|
|
8
9
|
import { delegateExtension } from "../modules/delegation/delegate.ts";
|
|
9
10
|
import {
|
|
11
|
+
DefaultDelegator,
|
|
10
12
|
type DelegationWorker,
|
|
11
|
-
Delegator,
|
|
12
13
|
} from "../modules/delegation/delegator.ts";
|
|
13
|
-
import {
|
|
14
|
-
import { discordAdminExtension } from "../modules/discord-admin/discord-admin.ts";
|
|
15
|
-
import { ownerMemoryExtension } from "../modules/memory/owner-memory.ts";
|
|
14
|
+
import { WebResearchWorker } from "../modules/delegation/web-research-worker.ts";
|
|
16
15
|
import { notifyExtension } from "../modules/notify/notify.ts";
|
|
17
16
|
import { Scheduler } from "../modules/schedules/scheduler.ts";
|
|
18
|
-
import {
|
|
19
|
-
type AgentChannelLookup,
|
|
20
|
-
schedulesExtension,
|
|
21
|
-
} from "../modules/schedules/schedules.ts";
|
|
17
|
+
import { schedulesExtension } from "../modules/schedules/schedules.ts";
|
|
22
18
|
import type { RoundtablePlugin } from "../plugin.ts";
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
import {
|
|
20
|
+
AGENTS,
|
|
21
|
+
BACKGROUND_TURNS,
|
|
22
|
+
DELEGATION,
|
|
23
|
+
SCHEDULES,
|
|
24
|
+
} from "../services.ts";
|
|
25
|
+
import type { SessionContext } from "../sessions.ts";
|
|
26
|
+
import { DELEGATE_TOOL } from "../shared/delegate-tool.ts";
|
|
27
|
+
import { SCHEDULE_TOOLS } from "../shared/schedule-tools.ts";
|
|
28
|
+
import { THE_SPEAKER, type Tier } from "../speakers.ts";
|
|
29
|
+
import { DISCORD } from "./discord.ts";
|
|
30
|
+
import { fixed } from "./session-tool.ts";
|
|
29
31
|
|
|
30
32
|
export interface ModulesOptions {
|
|
31
33
|
/** Who the tools serve in the owner's own sessions. */
|
|
@@ -42,58 +44,68 @@ export interface ModulesOptions {
|
|
|
42
44
|
thinking: ThinkingLevel;
|
|
43
45
|
worker?: DelegationWorker;
|
|
44
46
|
};
|
|
45
|
-
/** Another agent's channel, for reading its schedules; throws ScheduleError when unknown. */
|
|
46
|
-
agentChannelOf?: AgentChannelLookup;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Admins schedule and delegate; every speaker may list schedules and use the web tools the
|
|
51
|
+
* delegated worker and pi-web-access give. Notifying the owner stays with the owner.
|
|
52
|
+
*/
|
|
53
|
+
const MODULE_TIERS: Readonly<Record<string, Tier>> = {
|
|
54
|
+
...Object.fromEntries(
|
|
55
|
+
[...SCHEDULE_TOOLS, DELEGATE_TOOL].map((tool) => [tool, "admin" as const]),
|
|
56
|
+
),
|
|
57
|
+
schedule_list: "member",
|
|
58
|
+
web_search: "member",
|
|
59
|
+
fetch_content: "member",
|
|
60
|
+
get_search_content: "member",
|
|
61
|
+
};
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
|
-
* The owner's modules:
|
|
59
|
-
*
|
|
60
|
-
* join the shutdown drain.
|
|
64
|
+
* The owner's modules: notifications, schedules, and delegated tasks, each a session tool, and
|
|
65
|
+
* the turns nobody wrote. The delegator's running jobs join the shutdown drain.
|
|
61
66
|
*/
|
|
62
67
|
export function modulesPlugin(options: ModulesOptions): RoundtablePlugin {
|
|
63
|
-
const { owner
|
|
68
|
+
const { owner } = options;
|
|
64
69
|
// Outside the agent server a conversation has no channel of its own to post a run in.
|
|
65
70
|
const channelFor = async (
|
|
66
71
|
channel: ChannelKey,
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
surfaces: SurfacePort,
|
|
73
|
+
ownerChannel: () => Promise<ChannelKey>,
|
|
74
|
+
) => (surfaces.of(channel) ? channel : ownerChannel());
|
|
69
75
|
return {
|
|
70
76
|
name: "modules",
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
77
|
+
provides: [BACKGROUND_TURNS, DELEGATION],
|
|
78
|
+
setup: ({ conversations, services, logger, surfaces }) => {
|
|
79
|
+
const schedules = services.get(SCHEDULES);
|
|
80
|
+
const discord = services.get(DISCORD);
|
|
81
|
+
const { connection } = discord;
|
|
82
|
+
// Another agent's channel, for schedule_list; asked when a tool runs, after the agent server set up.
|
|
83
|
+
const agentChannelOf = (agent: string) =>
|
|
84
|
+
services.get(AGENTS).team.channelOf(agent);
|
|
74
85
|
const ownerChannelFor = (channel: ChannelKey) =>
|
|
75
|
-
channelFor(channel, () =>
|
|
76
|
-
const background = new
|
|
86
|
+
channelFor(channel, surfaces, () => connection.ownerChannel());
|
|
87
|
+
const background = new ConversationBackgroundTurns({
|
|
77
88
|
conversations,
|
|
78
89
|
system: { id: "assistant", name: options.assistant },
|
|
79
90
|
logger,
|
|
80
91
|
});
|
|
81
|
-
const delegator = new
|
|
92
|
+
const delegator = new DefaultDelegator({
|
|
82
93
|
worker:
|
|
83
94
|
options.delegation.worker ??
|
|
84
|
-
new
|
|
95
|
+
new WebResearchWorker({
|
|
85
96
|
modelRuntime: options.modelRuntime,
|
|
86
97
|
agentDir: options.agentDir,
|
|
87
98
|
workDir: join(options.dataDir, "delegate"),
|
|
88
99
|
model: options.delegation.model,
|
|
89
100
|
thinking: options.delegation.thinking,
|
|
90
101
|
}),
|
|
102
|
+
targets: (name) => conversations.target(name),
|
|
91
103
|
deliver: (job, outcome) => background.runDelegated(job, outcome),
|
|
92
104
|
threads: discord.threads,
|
|
93
105
|
logger,
|
|
94
106
|
});
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
services.provide(BACKGROUND_TURNS, background);
|
|
108
|
+
services.provide(DELEGATION, delegator);
|
|
97
109
|
// An agent session serves every speaker, so its tools name none.
|
|
98
110
|
const served = (session: SessionContext) =>
|
|
99
111
|
session.agent ? THE_SPEAKER : owner;
|
|
@@ -102,19 +114,11 @@ export function modulesPlugin(options: ModulesOptions): RoundtablePlugin {
|
|
|
102
114
|
{ name: "delegator", busy: () => delegator.runningChannels() },
|
|
103
115
|
],
|
|
104
116
|
sessionTools: [
|
|
105
|
-
fixed("
|
|
106
|
-
ownerMemoryExtension(
|
|
107
|
-
stores.memory,
|
|
108
|
-
served(session),
|
|
109
|
-
// The owner's own chats have one speaker; the agent server's have several.
|
|
110
|
-
session.agent ? session.speaker : undefined,
|
|
111
|
-
),
|
|
112
|
-
),
|
|
113
|
-
fixed("notify", () => notifyExtension(surface, owner)),
|
|
117
|
+
fixed("notify", () => notifyExtension(connection, owner)),
|
|
114
118
|
fixed("schedules", (session) =>
|
|
115
119
|
schedulesExtension(
|
|
116
120
|
{
|
|
117
|
-
store:
|
|
121
|
+
store: schedules,
|
|
118
122
|
owner: { id: owner.id, name: owner.name },
|
|
119
123
|
channelFor: ownerChannelFor,
|
|
120
124
|
},
|
|
@@ -137,10 +141,8 @@ export function modulesPlugin(options: ModulesOptions): RoundtablePlugin {
|
|
|
137
141
|
session.speaker,
|
|
138
142
|
),
|
|
139
143
|
),
|
|
140
|
-
fixed("discord-admin", () =>
|
|
141
|
-
discordAdminExtension(surface.ownerDiscord(), owner),
|
|
142
|
-
),
|
|
143
144
|
],
|
|
145
|
+
toolTiers: MODULE_TIERS,
|
|
144
146
|
};
|
|
145
147
|
},
|
|
146
148
|
};
|
|
@@ -153,12 +155,22 @@ export function modulesPlugin(options: ModulesOptions): RoundtablePlugin {
|
|
|
153
155
|
export function schedulerPlugin(): RoundtablePlugin {
|
|
154
156
|
return {
|
|
155
157
|
name: "schedules",
|
|
156
|
-
setup: ({
|
|
158
|
+
setup: ({ conversations, services, env, logger }) => {
|
|
159
|
+
const schedules = services.get(SCHEDULES);
|
|
157
160
|
const scheduler = new Scheduler({
|
|
158
|
-
store:
|
|
159
|
-
runner:
|
|
161
|
+
store: schedules,
|
|
162
|
+
runner: services.get(BACKGROUND_TURNS),
|
|
160
163
|
logger,
|
|
161
164
|
});
|
|
165
|
+
// Without Discord there are no schedule commands; the scheduler still fires.
|
|
166
|
+
const discord = services.find(DISCORD);
|
|
167
|
+
discord?.commands.add(
|
|
168
|
+
scheduleCommands(
|
|
169
|
+
discord.guard,
|
|
170
|
+
schedules,
|
|
171
|
+
(target) => conversations.target(target)?.label(env.locale) ?? target,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
162
174
|
return {
|
|
163
175
|
services: [
|
|
164
176
|
{
|
|
@@ -167,9 +179,6 @@ export function schedulerPlugin(): RoundtablePlugin {
|
|
|
167
179
|
stop: () => scheduler.stop(),
|
|
168
180
|
},
|
|
169
181
|
],
|
|
170
|
-
interactions: [
|
|
171
|
-
scheduleCommands(core.discord.guard, core.stores.schedules),
|
|
172
|
-
],
|
|
173
182
|
};
|
|
174
183
|
},
|
|
175
184
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type {
|
|
3
|
+
AgentTurnScope,
|
|
4
|
+
SessionTool,
|
|
5
|
+
SessionToolSnapshot,
|
|
6
|
+
} from "../sessions.ts";
|
|
7
|
+
|
|
8
|
+
/** A session tool whose extension never changes. */
|
|
9
|
+
export function fixed(
|
|
10
|
+
name: string,
|
|
11
|
+
factory: SessionToolSnapshot["factory"],
|
|
12
|
+
): SessionTool {
|
|
13
|
+
return { name, phase: "tools", snapshot: () => ({ revision: 0, factory }) };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A session tool whose extension never changes, for agent sessions only. */
|
|
17
|
+
export function agentOnly(
|
|
18
|
+
name: string,
|
|
19
|
+
factory: (scope: AgentTurnScope) => ExtensionFactory,
|
|
20
|
+
): SessionTool {
|
|
21
|
+
return fixed(name, (session) =>
|
|
22
|
+
session.agent ? factory(session.agent) : null,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SQL } from "bun";
|
|
2
|
+
import { StoredSkillRegistry } from "../modules/skills/skill-registry.ts";
|
|
3
|
+
import { SkillStore } from "../modules/skills/skill-store.ts";
|
|
4
|
+
import {
|
|
5
|
+
SKILL_LIST_TOOL,
|
|
6
|
+
SKILL_TOOLS,
|
|
7
|
+
skillToolsExtension,
|
|
8
|
+
} from "../modules/skills/skill-tools.ts";
|
|
9
|
+
import type { RoundtablePlugin } from "../plugin.ts";
|
|
10
|
+
import { AGENTS, SKILLS } from "../services.ts";
|
|
11
|
+
import type { Tier } from "../speakers.ts";
|
|
12
|
+
import { agentOnly } from "./session-tool.ts";
|
|
13
|
+
|
|
14
|
+
export interface SkillsOptions {
|
|
15
|
+
/** The guild whose agents' skills the store keeps. */
|
|
16
|
+
guildId: string;
|
|
17
|
+
/** Where the registry reads and writes skills. */
|
|
18
|
+
reposDir: string;
|
|
19
|
+
writtenDir: string;
|
|
20
|
+
builtinDir: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Admins manage skills; every speaker may look one up. */
|
|
24
|
+
const SKILL_TIERS: Readonly<Record<string, Tier>> = {
|
|
25
|
+
...Object.fromEntries(SKILL_TOOLS.map((tool) => [tool, "admin" as const])),
|
|
26
|
+
[SKILL_LIST_TOOL]: "member",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The skills agents carry: the tables, the registry provided as `SKILLS`, and the skill tools of
|
|
31
|
+
* agent sessions. An addon; `skills: false` leaves it out, and agents carry none.
|
|
32
|
+
*/
|
|
33
|
+
export function skillsPlugin(
|
|
34
|
+
options: SkillsOptions,
|
|
35
|
+
/** Opens the store; a test hands a stand-in, so the plugin can be set up without a database. */
|
|
36
|
+
openStore: (sql: SQL, guildId: string) => Promise<SkillStore> = (
|
|
37
|
+
sql,
|
|
38
|
+
guildId,
|
|
39
|
+
) => SkillStore.attach(sql, guildId),
|
|
40
|
+
): RoundtablePlugin {
|
|
41
|
+
let registry: StoredSkillRegistry | undefined;
|
|
42
|
+
return {
|
|
43
|
+
name: "skills",
|
|
44
|
+
migrations: SkillStore.migrations(options.guildId),
|
|
45
|
+
provides: [SKILLS],
|
|
46
|
+
// The skills are read once every plugin is set up, so a plugin may still place some in setup.
|
|
47
|
+
preflight: () => registry?.init(),
|
|
48
|
+
setup: async ({ database, services, logger }) => {
|
|
49
|
+
const built = new StoredSkillRegistry({
|
|
50
|
+
store: await openStore(database(), options.guildId),
|
|
51
|
+
reposDir: options.reposDir,
|
|
52
|
+
writtenDir: options.writtenDir,
|
|
53
|
+
builtinDir: options.builtinDir,
|
|
54
|
+
logger,
|
|
55
|
+
});
|
|
56
|
+
registry = built;
|
|
57
|
+
services.provide(SKILLS, built);
|
|
58
|
+
return {
|
|
59
|
+
sessionTools: [
|
|
60
|
+
agentOnly("skill-tools", () =>
|
|
61
|
+
skillToolsExtension(built, (name) => {
|
|
62
|
+
// The agent server is set up after this plugin and read when a session is built.
|
|
63
|
+
services.get(AGENTS).directory.activeAgent(name);
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
],
|
|
67
|
+
agentSelection: () => ({ tools: SKILL_TOOLS, groups: [] }),
|
|
68
|
+
toolTiers: SKILL_TIERS,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -1,41 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import type { OwnerIdentity } from "../identity.ts";
|
|
2
|
+
import { ownerMemoryExtension } from "../modules/memory/owner-memory.ts";
|
|
3
|
+
import { PgMemoryStore } from "../modules/memory/owner-memory-store.ts";
|
|
4
|
+
import { PgScheduleStore } from "../modules/schedules/schedule-store.ts";
|
|
5
5
|
import type { RoundtablePlugin } from "../plugin.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { MEMORY, SCHEDULES } from "../services.ts";
|
|
7
|
+
import { THE_SPEAKER, type Tier } from "../speakers.ts";
|
|
8
|
+
import { fixed } from "./session-tool.ts";
|
|
7
9
|
|
|
8
|
-
export interface
|
|
9
|
-
/** The owner
|
|
10
|
-
|
|
11
|
-
/** The agent server's guild; every agent-server row carries it. */
|
|
12
|
-
guildId: string;
|
|
10
|
+
export interface MemoryOptions {
|
|
11
|
+
/** The owner: whose memory the first rows belong to, and whom the tools serve in the owner's own sessions. */
|
|
12
|
+
owner: OwnerIdentity & { id: string };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Each speaker keeps a memory of their own, so every tier may use the tools; the owner's reaches only the owner's turns. */
|
|
16
|
+
const MEMORY_TIERS: Readonly<Record<string, Tier>> = {
|
|
17
|
+
memory_add: "member",
|
|
18
|
+
memory_search: "member",
|
|
19
|
+
memory_remove: "member",
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
23
|
+
* The remembered facts of each speaker: the table, the store over it provided as `MEMORY`, and
|
|
24
|
+
* the memory tools and prompt block of every conversation. An addon; `memory: false` leaves it
|
|
25
|
+
* out. The host migrates the table over its one pool before any setup.
|
|
18
26
|
*/
|
|
19
|
-
export function
|
|
20
|
-
const {
|
|
27
|
+
export function memoryPlugin(options: MemoryOptions): RoundtablePlugin {
|
|
28
|
+
const { owner } = options;
|
|
29
|
+
return {
|
|
30
|
+
name: "memory",
|
|
31
|
+
migrations: PgMemoryStore.migrations(owner.id),
|
|
32
|
+
provides: [MEMORY],
|
|
33
|
+
setup: ({ database, services }) => {
|
|
34
|
+
const memory = PgMemoryStore.attach(database(), owner.id);
|
|
35
|
+
services.provide(MEMORY, memory);
|
|
36
|
+
return {
|
|
37
|
+
sessionTools: [
|
|
38
|
+
fixed("owner-memory", (session) =>
|
|
39
|
+
ownerMemoryExtension(
|
|
40
|
+
memory,
|
|
41
|
+
owner.id,
|
|
42
|
+
// An agent session serves every speaker, so its tools name none.
|
|
43
|
+
session.agent ? THE_SPEAKER : owner,
|
|
44
|
+
// The owner's own chats have one speaker; the agent server's have several.
|
|
45
|
+
session.agent ? session.speaker : undefined,
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
toolTiers: MEMORY_TIERS,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The scheduled turns' table, and the store over it provided as `SCHEDULES`. */
|
|
56
|
+
export function scheduleStorePlugin(): RoundtablePlugin {
|
|
21
57
|
return {
|
|
22
|
-
name: "
|
|
23
|
-
migrations: [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
...AgentStore.migrations(guildId),
|
|
28
|
-
...SkillStore.migrations(guildId),
|
|
29
|
-
],
|
|
30
|
-
setup: async ({ database, core }) => {
|
|
31
|
-
const sql = database();
|
|
32
|
-
core.provide("stores", {
|
|
33
|
-
memory: OwnerMemoryStore.attach(sql, ownerId),
|
|
34
|
-
schedules: await ScheduleStore.attach(sql),
|
|
35
|
-
confirmations: await PendingConfirmationStore.attach(sql),
|
|
36
|
-
agents: await AgentStore.attach(sql, guildId),
|
|
37
|
-
skills: await SkillStore.attach(sql, guildId),
|
|
38
|
-
});
|
|
58
|
+
name: "schedule-store",
|
|
59
|
+
migrations: [PgScheduleStore.migration],
|
|
60
|
+
provides: [SCHEDULES],
|
|
61
|
+
setup: async ({ database, services }) => {
|
|
62
|
+
services.provide(SCHEDULES, await PgScheduleStore.attach(database()));
|
|
39
63
|
return {};
|
|
40
64
|
},
|
|
41
65
|
};
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
number,
|
|
19
19
|
oneOf,
|
|
20
20
|
optional,
|
|
21
|
+
orOff,
|
|
21
22
|
record,
|
|
22
23
|
shape,
|
|
23
24
|
text,
|
|
@@ -48,6 +49,17 @@ export interface RoundtableConfig {
|
|
|
48
49
|
entryChannel: string;
|
|
49
50
|
/** The root slash command, without the slash; default the lowercase assistant name. */
|
|
50
51
|
rootCommand?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the owner's conversations have Discord administration tools; default true. `false`
|
|
54
|
+
* leaves the `discord-admin` addon out.
|
|
55
|
+
*/
|
|
56
|
+
admin?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Text appended as it is to the refusal a non-owner gets from the root command, such as a
|
|
59
|
+
* pointer to the commands anyone may use; include the space or punctuation your language
|
|
60
|
+
* needs before it. Default none.
|
|
61
|
+
*/
|
|
62
|
+
refusalHint?: string;
|
|
51
63
|
};
|
|
52
64
|
database: { url: string };
|
|
53
65
|
/** Where the process keeps its files, such as pictures, attachments, and skills. */
|
|
@@ -74,19 +86,31 @@ export interface RoundtableConfig {
|
|
|
74
86
|
agents?: AgentSeed[];
|
|
75
87
|
/**
|
|
76
88
|
* Where the agents' avatars are served: the public address that reaches it, and the TCP port
|
|
77
|
-
* (default 3000) or unix socket the process listens on.
|
|
89
|
+
* (default 3000) or unix socket the process listens on. `socketMode` is the socket file's
|
|
90
|
+
* permission bits (default `0o660`); widen it only for a proxy that runs as another user.
|
|
78
91
|
*/
|
|
79
92
|
http: {
|
|
80
93
|
publicUrl: string;
|
|
81
94
|
port?: number;
|
|
82
95
|
hostname?: string;
|
|
83
96
|
socketPath?: string;
|
|
97
|
+
socketMode?: number;
|
|
84
98
|
};
|
|
85
99
|
/** A picture of your own for agents without one, and the style reference for drawing them; default a plain bot. */
|
|
86
100
|
avatar?: string;
|
|
87
101
|
/** The agents' shared working directory; default `<dataDir>/work`. */
|
|
88
102
|
workDir?: string;
|
|
89
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Where the skill registry reads built-in skills and keeps linked repositories. `false` leaves
|
|
105
|
+
* the `skills` addon out: agents carry no skills, and no skill tools exist. Stored skills stay
|
|
106
|
+
* in their tables.
|
|
107
|
+
*/
|
|
108
|
+
skills?: false | { builtinDir?: string; reposDir?: string };
|
|
109
|
+
/**
|
|
110
|
+
* Whether each speaker's memory is kept and offered as tools; default true. `false` leaves the
|
|
111
|
+
* `memory` addon out: no memory tools, no memory prompt block, and the table stays as it is.
|
|
112
|
+
*/
|
|
113
|
+
memory?: boolean;
|
|
90
114
|
/** The agent that investigates the process's own errors, by name. */
|
|
91
115
|
ops?: { agent: string };
|
|
92
116
|
plugins?: RoundtablePlugin[];
|
|
@@ -116,6 +140,8 @@ const schema = shape({
|
|
|
116
140
|
guild: text,
|
|
117
141
|
entryChannel: text,
|
|
118
142
|
rootCommand: optional(text),
|
|
143
|
+
admin: optional(bool),
|
|
144
|
+
refusalHint: optional(text),
|
|
119
145
|
}),
|
|
120
146
|
database: shape({ url: text }),
|
|
121
147
|
dataDir: text,
|
|
@@ -158,12 +184,14 @@ const schema = shape({
|
|
|
158
184
|
port: optional(integer(1, 65535)),
|
|
159
185
|
hostname: optional(text),
|
|
160
186
|
socketPath: optional(text),
|
|
187
|
+
socketMode: optional(integer(0, 0o777)),
|
|
161
188
|
}),
|
|
162
189
|
avatar: optional(text),
|
|
163
190
|
workDir: optional(text),
|
|
164
191
|
skills: optional(
|
|
165
|
-
shape({ builtinDir: optional(text), reposDir: optional(text) }),
|
|
192
|
+
orOff(shape({ builtinDir: optional(text), reposDir: optional(text) })),
|
|
166
193
|
),
|
|
194
|
+
memory: optional(bool),
|
|
167
195
|
ops: optional(shape({ agent: text })),
|
|
168
196
|
plugins: optional(
|
|
169
197
|
list(
|
|
@@ -187,6 +215,8 @@ export interface ResolvedConfig {
|
|
|
187
215
|
guild: string;
|
|
188
216
|
entryChannel: string;
|
|
189
217
|
rootCommand: string;
|
|
218
|
+
admin: boolean;
|
|
219
|
+
refusalHint?: string;
|
|
190
220
|
};
|
|
191
221
|
databaseUrl: string;
|
|
192
222
|
dataDir: string;
|
|
@@ -206,10 +236,13 @@ export interface ResolvedConfig {
|
|
|
206
236
|
port: number;
|
|
207
237
|
hostname?: string;
|
|
208
238
|
socketPath?: string;
|
|
239
|
+
socketMode?: number;
|
|
209
240
|
};
|
|
210
241
|
avatar?: string;
|
|
211
242
|
workDir: string;
|
|
212
|
-
|
|
243
|
+
/** `false` when the skills addon is off. */
|
|
244
|
+
skills: false | { builtinDir?: string; reposDir?: string };
|
|
245
|
+
memory: boolean;
|
|
213
246
|
ops?: { agent: string };
|
|
214
247
|
plugins: RoundtablePlugin[];
|
|
215
248
|
}
|
|
@@ -243,6 +276,10 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
243
276
|
rootCommand:
|
|
244
277
|
config.discord.rootCommand ??
|
|
245
278
|
name.toLowerCase().replace(/[^a-z0-9-]+/g, "-"),
|
|
279
|
+
admin: config.discord.admin ?? true,
|
|
280
|
+
...(config.discord.refusalHint === undefined
|
|
281
|
+
? {}
|
|
282
|
+
: { refusalHint: config.discord.refusalHint }),
|
|
246
283
|
},
|
|
247
284
|
databaseUrl: config.database.url,
|
|
248
285
|
dataDir: config.dataDir,
|
|
@@ -275,10 +312,14 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
275
312
|
port: config.http.port ?? 3000,
|
|
276
313
|
...(config.http.hostname ? { hostname: config.http.hostname } : {}),
|
|
277
314
|
...(config.http.socketPath ? { socketPath: config.http.socketPath } : {}),
|
|
315
|
+
...(config.http.socketMode === undefined
|
|
316
|
+
? {}
|
|
317
|
+
: { socketMode: config.http.socketMode }),
|
|
278
318
|
},
|
|
279
319
|
...(config.avatar ? { avatar: config.avatar } : {}),
|
|
280
320
|
workDir: config.workDir ?? `${config.dataDir}/work`,
|
|
281
321
|
skills: config.skills ?? {},
|
|
322
|
+
memory: config.memory ?? true,
|
|
282
323
|
...(config.ops ? { ops: config.ops } : {}),
|
|
283
324
|
plugins: config.plugins ?? [],
|
|
284
325
|
};
|
|
@@ -115,6 +115,12 @@ export const guarded = <T>(
|
|
|
115
115
|
},
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
/** `false`, or whatever `item` accepts: a feature that is switched off, or configured. */
|
|
119
|
+
export const orOff = <T>(item: Field<T>): Field<T | false> => ({
|
|
120
|
+
describe: `false, or ${item.describe}`,
|
|
121
|
+
check: (value, path) => (value === false ? false : item.check(value, path)),
|
|
122
|
+
});
|
|
123
|
+
|
|
118
124
|
/** A field that may be left out. */
|
|
119
125
|
export type Optional<T> = { optional: Field<T> };
|
|
120
126
|
|