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,285 @@
|
|
|
1
|
+
import type { SQL } from "bun";
|
|
2
|
+
import { scopeToGuild } from "../../db/guild-scope.ts";
|
|
3
|
+
import type { Migration } from "../../db/migrations.ts";
|
|
4
|
+
import { AgentError } from "../../domain/errors.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A registered skill (repos-and-skills spec behavior 13). A linked skill's name and description
|
|
8
|
+
* live in its repository's file; an agent-written one keeps them here.
|
|
9
|
+
*/
|
|
10
|
+
export type SkillEntry =
|
|
11
|
+
| { kind: "linked"; name: string; repo: string; path: string }
|
|
12
|
+
| { kind: "written"; name: string; description: string; body: string };
|
|
13
|
+
|
|
14
|
+
export interface SkillGroup {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
skills: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Who lost a removed skill (behavior 17). */
|
|
21
|
+
export interface Detached {
|
|
22
|
+
agents: string[];
|
|
23
|
+
groups: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SkillRow {
|
|
27
|
+
name: string;
|
|
28
|
+
kind: "linked" | "written";
|
|
29
|
+
repo: string | null;
|
|
30
|
+
path: string | null;
|
|
31
|
+
description: string | null;
|
|
32
|
+
body: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface GroupRow {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
skills: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const toEntry = (row: SkillRow): SkillEntry =>
|
|
42
|
+
row.kind === "linked"
|
|
43
|
+
? {
|
|
44
|
+
kind: "linked",
|
|
45
|
+
name: row.name,
|
|
46
|
+
repo: row.repo ?? "",
|
|
47
|
+
path: row.path ?? "",
|
|
48
|
+
}
|
|
49
|
+
: {
|
|
50
|
+
kind: "written",
|
|
51
|
+
name: row.name,
|
|
52
|
+
description: row.description ?? "",
|
|
53
|
+
body: row.body ?? "",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const toGroup = (row: GroupRow): SkillGroup => ({
|
|
57
|
+
name: row.name,
|
|
58
|
+
description: row.description,
|
|
59
|
+
skills: row.skills.split(",").filter(Boolean),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The kind an agent-written skill was stored under before it became 'written': the first
|
|
64
|
+
* consumer's own name, spelled from parts so the public tree names no consumer.
|
|
65
|
+
*/
|
|
66
|
+
export const LEGACY_WRITTEN_KIND = ["mer", "lin"].join("");
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Skills, skill groups, and the skills each agent carries, in the database. All of it is
|
|
70
|
+
* also held in memory, because every agent turn resolves its skills and only this process
|
|
71
|
+
* changes them.
|
|
72
|
+
*/
|
|
73
|
+
export class SkillStore {
|
|
74
|
+
readonly #sql: SQL;
|
|
75
|
+
readonly #skills = new Map<string, SkillEntry>();
|
|
76
|
+
readonly #groups = new Map<string, SkillGroup>();
|
|
77
|
+
readonly #carried = new Map<string, string[]>();
|
|
78
|
+
|
|
79
|
+
readonly #guild: string;
|
|
80
|
+
|
|
81
|
+
private constructor(sql: SQL, guildId: string) {
|
|
82
|
+
this.#sql = sql;
|
|
83
|
+
this.#guild = guildId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The store's tables; the host runs this before any store attaches. */
|
|
87
|
+
static readonly migration: Migration = {
|
|
88
|
+
name: "skills",
|
|
89
|
+
up: async (sql) => {
|
|
90
|
+
await sql`
|
|
91
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
92
|
+
name text PRIMARY KEY,
|
|
93
|
+
kind text NOT NULL,
|
|
94
|
+
repo text,
|
|
95
|
+
path text,
|
|
96
|
+
description text,
|
|
97
|
+
body text,
|
|
98
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
99
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
100
|
+
)`;
|
|
101
|
+
await sql`
|
|
102
|
+
CREATE TABLE IF NOT EXISTS skill_groups (
|
|
103
|
+
name text PRIMARY KEY,
|
|
104
|
+
description text NOT NULL,
|
|
105
|
+
-- Comma-separated skill names, which never contain a comma.
|
|
106
|
+
skills text NOT NULL
|
|
107
|
+
)`;
|
|
108
|
+
await sql`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS agent_skills (
|
|
110
|
+
agent text NOT NULL,
|
|
111
|
+
skill text NOT NULL,
|
|
112
|
+
PRIMARY KEY (agent, skill)
|
|
113
|
+
)`;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The store's tables, then the guild of each agent's skills, existing rows given `guildId`.
|
|
119
|
+
* The skill catalog itself is shared by every guild.
|
|
120
|
+
*/
|
|
121
|
+
static migrations(guildId: string): Migration[] {
|
|
122
|
+
return [
|
|
123
|
+
SkillStore.migration,
|
|
124
|
+
{
|
|
125
|
+
name: "skills-guild",
|
|
126
|
+
up: (sql) =>
|
|
127
|
+
scopeToGuild(sql, guildId, [
|
|
128
|
+
{ table: "agent_skills", key: ["agent", "skill"] },
|
|
129
|
+
]),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
// Skills an agent wrote were stored under the legacy kind; the kind is now 'written'.
|
|
133
|
+
name: "skills-written-kind",
|
|
134
|
+
up: async (sql) => {
|
|
135
|
+
await sql`UPDATE skills SET kind = 'written' WHERE kind = ${LEGACY_WRITTEN_KIND}`;
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The store over the host's migrated pool; the skills agents carry are those of one guild. */
|
|
142
|
+
static async attach(sql: SQL, guildId: string): Promise<SkillStore> {
|
|
143
|
+
const store = new SkillStore(sql, guildId);
|
|
144
|
+
await store.#reload();
|
|
145
|
+
return store;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async #reload(): Promise<void> {
|
|
149
|
+
const skills: SkillRow[] = await this.#sql`SELECT * FROM skills`;
|
|
150
|
+
const groups: GroupRow[] = await this.#sql`SELECT * FROM skill_groups`;
|
|
151
|
+
const carried: { agent: string; skill: string }[] = await this
|
|
152
|
+
.#sql`SELECT agent, skill FROM agent_skills
|
|
153
|
+
WHERE guild_id = ${this.#guild} ORDER BY agent, skill`;
|
|
154
|
+
this.#skills.clear();
|
|
155
|
+
this.#groups.clear();
|
|
156
|
+
this.#carried.clear();
|
|
157
|
+
for (const row of skills) this.#skills.set(row.name, toEntry(row));
|
|
158
|
+
for (const row of groups) this.#groups.set(row.name, toGroup(row));
|
|
159
|
+
for (const row of carried)
|
|
160
|
+
this.#carried.set(row.agent, [
|
|
161
|
+
...(this.#carried.get(row.agent) ?? []),
|
|
162
|
+
row.skill,
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
skills(): SkillEntry[] {
|
|
167
|
+
return [...this.#skills.values()].sort((a, b) =>
|
|
168
|
+
a.name.localeCompare(b.name),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
skill(name: string): SkillEntry | undefined {
|
|
173
|
+
return this.#skills.get(name);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
groups(): SkillGroup[] {
|
|
177
|
+
return [...this.#groups.values()].sort((a, b) =>
|
|
178
|
+
a.name.localeCompare(b.name),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
group(name: string): SkillGroup | undefined {
|
|
183
|
+
return this.#groups.get(name);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The registered skills an agent carries, by name. */
|
|
187
|
+
carriedBy(agent: string): string[] {
|
|
188
|
+
return this.#carried.get(agent) ?? [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The agents carrying a skill. */
|
|
192
|
+
carriers(skill: string): string[] {
|
|
193
|
+
return [...this.#carried]
|
|
194
|
+
.filter(([, skills]) => skills.includes(skill))
|
|
195
|
+
.map(([agent]) => agent)
|
|
196
|
+
.sort();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Registers linked skills together, or none when any name is taken. */
|
|
200
|
+
async addLinked(
|
|
201
|
+
entries: readonly { name: string; repo: string; path: string }[],
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const taken = entries.filter((entry) => this.#skills.has(entry.name));
|
|
204
|
+
if (taken.length > 0)
|
|
205
|
+
throw new AgentError(
|
|
206
|
+
`Already registered: ${taken.map((entry) => entry.name).join(", ")}. Nothing was linked.`,
|
|
207
|
+
);
|
|
208
|
+
await this.#sql.begin(async (tx) => {
|
|
209
|
+
for (const entry of entries)
|
|
210
|
+
await tx`
|
|
211
|
+
INSERT INTO skills (name, kind, repo, path)
|
|
212
|
+
VALUES (${entry.name}, 'linked', ${entry.repo}, ${entry.path})`;
|
|
213
|
+
});
|
|
214
|
+
for (const entry of entries)
|
|
215
|
+
this.#skills.set(entry.name, { kind: "linked", ...entry });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async addWritten(skill: {
|
|
219
|
+
name: string;
|
|
220
|
+
description: string;
|
|
221
|
+
body: string;
|
|
222
|
+
}): Promise<void> {
|
|
223
|
+
if (this.#skills.has(skill.name))
|
|
224
|
+
throw new AgentError(`A skill named "${skill.name}" already exists.`);
|
|
225
|
+
await this.#sql`
|
|
226
|
+
INSERT INTO skills (name, kind, description, body)
|
|
227
|
+
VALUES (${skill.name}, 'written', ${skill.description}, ${skill.body})`;
|
|
228
|
+
this.#skills.set(skill.name, { kind: "written", ...skill });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async updateWritten(skill: {
|
|
232
|
+
name: string;
|
|
233
|
+
description: string;
|
|
234
|
+
body: string;
|
|
235
|
+
}): Promise<void> {
|
|
236
|
+
await this.#sql`
|
|
237
|
+
UPDATE skills SET description = ${skill.description}, body = ${skill.body},
|
|
238
|
+
updated_at = now()
|
|
239
|
+
WHERE name = ${skill.name} AND kind = 'written'`;
|
|
240
|
+
this.#skills.set(skill.name, { kind: "written", ...skill });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Removes a skill and detaches it from every agent and group. */
|
|
244
|
+
async remove(name: string): Promise<Detached> {
|
|
245
|
+
const agents = this.carriers(name);
|
|
246
|
+
const groups = this.groups().filter((group) => group.skills.includes(name));
|
|
247
|
+
await this.#sql.begin(async (tx) => {
|
|
248
|
+
await tx`DELETE FROM skills WHERE name = ${name}`;
|
|
249
|
+
await tx`DELETE FROM agent_skills WHERE guild_id = ${this.#guild} AND skill = ${name}`;
|
|
250
|
+
for (const group of groups)
|
|
251
|
+
await tx`
|
|
252
|
+
UPDATE skill_groups
|
|
253
|
+
SET skills = ${group.skills.filter((s) => s !== name).join(",")}
|
|
254
|
+
WHERE name = ${group.name}`;
|
|
255
|
+
});
|
|
256
|
+
await this.#reload();
|
|
257
|
+
return { agents, groups: groups.map((group) => group.name) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async setGroup(group: SkillGroup): Promise<void> {
|
|
261
|
+
await this.#sql`
|
|
262
|
+
INSERT INTO skill_groups (name, description, skills)
|
|
263
|
+
VALUES (${group.name}, ${group.description}, ${group.skills.join(",")})
|
|
264
|
+
ON CONFLICT (name) DO UPDATE
|
|
265
|
+
SET description = EXCLUDED.description, skills = EXCLUDED.skills`;
|
|
266
|
+
this.#groups.set(group.name, { ...group, skills: [...group.skills] });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async removeGroup(name: string): Promise<void> {
|
|
270
|
+
await this.#sql`DELETE FROM skill_groups WHERE name = ${name}`;
|
|
271
|
+
this.#groups.delete(name);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Replaces the registered skills an agent carries. */
|
|
275
|
+
async setCarried(agent: string, skills: readonly string[]): Promise<void> {
|
|
276
|
+
const sorted = [...new Set(skills)].sort();
|
|
277
|
+
await this.#sql.begin(async (tx) => {
|
|
278
|
+
await tx`DELETE FROM agent_skills WHERE guild_id = ${this.#guild} AND agent = ${agent}`;
|
|
279
|
+
for (const skill of sorted)
|
|
280
|
+
await tx`INSERT INTO agent_skills (guild_id, agent, skill) VALUES (${this.#guild}, ${agent}, ${skill})`;
|
|
281
|
+
});
|
|
282
|
+
if (sorted.length > 0) this.#carried.set(agent, sorted);
|
|
283
|
+
else this.#carried.delete(agent);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { AgentError } from "../../domain/errors.ts";
|
|
4
|
+
import {
|
|
5
|
+
optionalString,
|
|
6
|
+
requiredString,
|
|
7
|
+
stringList,
|
|
8
|
+
textToolsExtension,
|
|
9
|
+
} from "../../runtime/text-tools.ts";
|
|
10
|
+
import { MAX_SKILL_BODY_CHARS, type SkillRegistry } from "./skill-registry.ts";
|
|
11
|
+
|
|
12
|
+
export const SKILL_LIST_TOOL = "skill_list";
|
|
13
|
+
|
|
14
|
+
/** The skill tools only agents have; the DM reads the registry with skill_list alone. */
|
|
15
|
+
export const SKILL_TOOLS = [
|
|
16
|
+
"skill_link",
|
|
17
|
+
"skill_unlink",
|
|
18
|
+
"skill_create",
|
|
19
|
+
"skill_update",
|
|
20
|
+
"skill_delete",
|
|
21
|
+
"skill_group_set",
|
|
22
|
+
"skill_group_delete",
|
|
23
|
+
"agent_skills",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
const SKILL_NAME = Type.String({
|
|
27
|
+
description: "A skill's name, from skill_list.",
|
|
28
|
+
});
|
|
29
|
+
const SKILL_NAMES = Type.Array(Type.String(), {
|
|
30
|
+
description: "Skill names, from skill_list.",
|
|
31
|
+
});
|
|
32
|
+
const DESCRIPTION = Type.String({
|
|
33
|
+
minLength: 1,
|
|
34
|
+
maxLength: 1024,
|
|
35
|
+
description:
|
|
36
|
+
'When to use the skill and nothing else, starting with "Use when": one trigger per distinct way it is used, no summary of its steps.',
|
|
37
|
+
});
|
|
38
|
+
const BODY = Type.String({
|
|
39
|
+
minLength: 1,
|
|
40
|
+
maxLength: MAX_SKILL_BODY_CHARS,
|
|
41
|
+
description:
|
|
42
|
+
"The Markdown body after the frontmatter, which the tool writes. Follow the writing-skills skill: short, stated positively, steps ending on a checkable completion criterion.",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** skill_list, for the DM and for agents. */
|
|
46
|
+
export function skillListExtension(registry: SkillRegistry): ExtensionFactory {
|
|
47
|
+
return textToolsExtension(
|
|
48
|
+
[
|
|
49
|
+
{
|
|
50
|
+
name: SKILL_LIST_TOOL,
|
|
51
|
+
label: "List skills",
|
|
52
|
+
description:
|
|
53
|
+
"List the skill registry: each skill's kind (built in, linked from a repository and read-only, or written by an agent), description, groups, and the agents carrying it, then the skill groups. Filter by a group or a word.",
|
|
54
|
+
parameters: Type.Object({
|
|
55
|
+
group: Type.Optional(
|
|
56
|
+
Type.String({ description: "A skill group's name." }),
|
|
57
|
+
),
|
|
58
|
+
query: Type.Optional(
|
|
59
|
+
Type.String({
|
|
60
|
+
description: "A word to find in names and descriptions.",
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
}),
|
|
64
|
+
run: (i) => {
|
|
65
|
+
const group = optionalString(i, "group");
|
|
66
|
+
const query = optionalString(i, "query");
|
|
67
|
+
return registry.list({
|
|
68
|
+
...(group ? { group } : {}),
|
|
69
|
+
...(query ? { query } : {}),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
AgentError,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The skill tools of an agent session (repos-and-skills spec behaviors 15–19). */
|
|
79
|
+
export function skillToolsExtension(
|
|
80
|
+
registry: SkillRegistry,
|
|
81
|
+
/** Throws AgentError unless the name is an active agent. */
|
|
82
|
+
checkAgent: (name: string) => void,
|
|
83
|
+
): ExtensionFactory {
|
|
84
|
+
return textToolsExtension(
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
name: "skill_link",
|
|
88
|
+
label: "Link skills",
|
|
89
|
+
description:
|
|
90
|
+
"Register a skill that lives in a managed repository: the path of a folder with SKILL.md, or of a folder whose child folders hold skills, all of which are linked. Name and description come from each file. Linked skills are read-only here; they change in their repository.",
|
|
91
|
+
parameters: Type.Object({
|
|
92
|
+
repo: Type.String({ description: "<owner>/<repo>, from repo_list." }),
|
|
93
|
+
path: Type.String({
|
|
94
|
+
description:
|
|
95
|
+
"A folder inside the repository, such as skills/plan-work or skills.",
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
run: (i) =>
|
|
99
|
+
registry.link(requiredString(i, "repo"), requiredString(i, "path")),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "skill_unlink",
|
|
103
|
+
label: "Unlink skill",
|
|
104
|
+
description:
|
|
105
|
+
"Remove a linked skill from the registry; it is detached from every agent and group. The repository is untouched.",
|
|
106
|
+
parameters: Type.Object({ name: SKILL_NAME }),
|
|
107
|
+
run: (i) => registry.unlink(requiredString(i, "name")),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "skill_create",
|
|
111
|
+
label: "Create skill",
|
|
112
|
+
description:
|
|
113
|
+
"Write a new skill into the registry. Read the writing-skills skill first. It does not attach the skill to anyone; use agent_skills.",
|
|
114
|
+
parameters: Type.Object({
|
|
115
|
+
name: Type.String({
|
|
116
|
+
pattern: "^[a-z0-9-]{1,64}$",
|
|
117
|
+
description:
|
|
118
|
+
"Permanent: lowercase letters, digits, and single hyphens.",
|
|
119
|
+
}),
|
|
120
|
+
description: DESCRIPTION,
|
|
121
|
+
body: BODY,
|
|
122
|
+
}),
|
|
123
|
+
run: (i) =>
|
|
124
|
+
registry.create({
|
|
125
|
+
name: requiredString(i, "name"),
|
|
126
|
+
description: requiredString(i, "description"),
|
|
127
|
+
body: requiredString(i, "body"),
|
|
128
|
+
}),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "skill_update",
|
|
132
|
+
label: "Update skill",
|
|
133
|
+
description:
|
|
134
|
+
"Replace a agent-written skill's description or body whole; read its SKILL.md (path in skill_list) first. Its carriers read the new version from their next turn.",
|
|
135
|
+
parameters: Type.Object({
|
|
136
|
+
name: SKILL_NAME,
|
|
137
|
+
description: Type.Optional(DESCRIPTION),
|
|
138
|
+
body: Type.Optional(BODY),
|
|
139
|
+
}),
|
|
140
|
+
run: (i) => {
|
|
141
|
+
const description = optionalString(i, "description");
|
|
142
|
+
const body = optionalString(i, "body");
|
|
143
|
+
return registry.update(requiredString(i, "name"), {
|
|
144
|
+
...(description !== undefined ? { description } : {}),
|
|
145
|
+
...(body !== undefined ? { body } : {}),
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "skill_delete",
|
|
151
|
+
label: "Delete skill",
|
|
152
|
+
description:
|
|
153
|
+
"Delete a agent-written skill; it is detached from every agent and group.",
|
|
154
|
+
parameters: Type.Object({ name: SKILL_NAME }),
|
|
155
|
+
run: (i) => registry.delete(requiredString(i, "name")),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "skill_group_set",
|
|
159
|
+
label: "Set skill group",
|
|
160
|
+
description:
|
|
161
|
+
"Create or replace a skill group, a named list that only filters skill_list. Agents carry skills, not groups.",
|
|
162
|
+
parameters: Type.Object({
|
|
163
|
+
name: Type.String({ pattern: "^[a-z0-9-]{1,64}$" }),
|
|
164
|
+
description: Type.String({ minLength: 1, maxLength: 200 }),
|
|
165
|
+
skills: SKILL_NAMES,
|
|
166
|
+
}),
|
|
167
|
+
run: (i) =>
|
|
168
|
+
registry.setGroup(
|
|
169
|
+
requiredString(i, "name"),
|
|
170
|
+
requiredString(i, "description"),
|
|
171
|
+
stringList(i, "skills") ?? [],
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "skill_group_delete",
|
|
176
|
+
label: "Delete skill group",
|
|
177
|
+
description: "Delete a skill group; its skills are untouched.",
|
|
178
|
+
parameters: Type.Object({ name: Type.String() }),
|
|
179
|
+
run: (i) => registry.deleteGroup(requiredString(i, "name")),
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "agent_skills",
|
|
183
|
+
label: "Change agent skills",
|
|
184
|
+
description:
|
|
185
|
+
"Add and remove skills an agent carries, yours included; it applies from that agent's next turn. Built-in skills stay. An unknown name refuses the whole call.",
|
|
186
|
+
parameters: Type.Object({
|
|
187
|
+
agent: Type.String({
|
|
188
|
+
description: "An agent's name, from agent_list.",
|
|
189
|
+
}),
|
|
190
|
+
add: Type.Optional(SKILL_NAMES),
|
|
191
|
+
remove: Type.Optional(SKILL_NAMES),
|
|
192
|
+
}),
|
|
193
|
+
run: async (i) => {
|
|
194
|
+
const agent = requiredString(i, "agent");
|
|
195
|
+
checkAgent(agent);
|
|
196
|
+
const carried = await registry.attach(
|
|
197
|
+
agent,
|
|
198
|
+
stringList(i, "add") ?? [],
|
|
199
|
+
stringList(i, "remove") ?? [],
|
|
200
|
+
);
|
|
201
|
+
return `${agent} now carries: ${carried.join(", ") || "only the built-in skills"}.`;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
AgentError,
|
|
206
|
+
);
|
|
207
|
+
}
|