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
|
@@ -2,6 +2,7 @@ import type { SQL } from "bun";
|
|
|
2
2
|
import type { Migration } from "../db/migrations.ts";
|
|
3
3
|
import { AgentError } from "../domain/errors.ts";
|
|
4
4
|
import type { ThinkingLevel } from "../models.ts";
|
|
5
|
+
import type { AgentDirectory } from "../services.ts";
|
|
5
6
|
import {
|
|
6
7
|
type Agent,
|
|
7
8
|
type AgentGroup,
|
|
@@ -51,7 +52,7 @@ export {
|
|
|
51
52
|
* Agents, groups, and group messages, in the database. Agents and groups are also held
|
|
52
53
|
* in memory, because every inbound message asks and only this process changes them.
|
|
53
54
|
*/
|
|
54
|
-
export class
|
|
55
|
+
export class PgAgentStore implements AgentDirectory {
|
|
55
56
|
readonly #sql: SQL;
|
|
56
57
|
readonly #agents = new Map<string, Agent>();
|
|
57
58
|
readonly #groups = new Map<string, AgentGroup>();
|
|
@@ -74,8 +75,8 @@ export class AgentStore {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/** The store over the host's migrated pool, holding the rows of one guild. */
|
|
77
|
-
static async attach(sql: SQL, guildId: string): Promise<
|
|
78
|
-
const store = new
|
|
78
|
+
static async attach(sql: SQL, guildId: string): Promise<PgAgentStore> {
|
|
79
|
+
const store = new PgAgentStore(sql, guildId);
|
|
79
80
|
await store.#reload();
|
|
80
81
|
return store;
|
|
81
82
|
}
|
|
@@ -178,7 +179,8 @@ export class AgentStore {
|
|
|
178
179
|
checkAgentName(agent.name);
|
|
179
180
|
checkDisplayName(agent.displayName);
|
|
180
181
|
checkPrompt(agent.prompt);
|
|
181
|
-
|
|
182
|
+
// An agent of a host without an image provider has no avatar prompt.
|
|
183
|
+
if (agent.avatarPrompt !== "") checkAvatarPrompt(agent.avatarPrompt);
|
|
182
184
|
this.#checkFree(agent.name);
|
|
183
185
|
const rows: AgentRow[] = await this.#sql`
|
|
184
186
|
INSERT INTO agents (guild_id, name, display_name, prompt, avatar_prompt, channel_id)
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { beforeEach } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { SQL } from "bun";
|
|
6
|
+
import type { ContextUse } from "../contract/runtime.ts";
|
|
7
|
+
import { NO_ATTACHMENTS } from "../domain/attachment.ts";
|
|
8
|
+
import type {
|
|
9
|
+
ChannelKey,
|
|
10
|
+
PendingConfirmation,
|
|
11
|
+
TurnResult,
|
|
12
|
+
} from "../domain/conversation.ts";
|
|
13
|
+
import { AgentRunError } from "../domain/errors.ts";
|
|
14
|
+
import type { TurnRequest } from "../domain/ports.ts";
|
|
15
|
+
import { silentLogger } from "../log.ts";
|
|
16
|
+
import { StoredSkillRegistry as SkillRegistry } from "../modules/skills/skill-registry.ts";
|
|
17
|
+
import { SkillStore } from "../modules/skills/skill-store.ts";
|
|
18
|
+
import type { PromptSection } from "../plugin.ts";
|
|
19
|
+
import { type ChannelQueue, channelQueue } from "../routing/channel-queue.ts";
|
|
20
|
+
import {
|
|
21
|
+
openTestStore,
|
|
22
|
+
TEST_GUILD,
|
|
23
|
+
type TestStore,
|
|
24
|
+
testDatabaseUrl,
|
|
25
|
+
} from "../testing/database.ts";
|
|
26
|
+
import { useTestLocale } from "../testing/locale.ts";
|
|
27
|
+
import { OWNER_SPEAKER, TEST_OWNER } from "../testing/owner.ts";
|
|
28
|
+
import type {
|
|
29
|
+
AgentCategory,
|
|
30
|
+
AgentChannels,
|
|
31
|
+
AgentPost,
|
|
32
|
+
AgentTurnRunner,
|
|
33
|
+
CategoryLayout,
|
|
34
|
+
ChannelMessage,
|
|
35
|
+
} from "./agent-ports.ts";
|
|
36
|
+
import type { AgentSeed } from "./agent-rules.ts";
|
|
37
|
+
import { PgAgentStore as AgentStore } from "./agent-store.ts";
|
|
38
|
+
import { DiscordAgentTeam as AgentTeam } from "./agent-team.ts";
|
|
39
|
+
import { discordKey } from "./team-keys.ts";
|
|
40
|
+
|
|
41
|
+
export const ENTRY = "1000";
|
|
42
|
+
export const USABLE = [
|
|
43
|
+
"claude-bridge/claude-opus-5-5",
|
|
44
|
+
"openai-codex/gpt-6-sol",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export class FakeChannels implements AgentChannels {
|
|
48
|
+
posts: { channelId: string; post: AgentPost }[] = [];
|
|
49
|
+
created: string[] = [];
|
|
50
|
+
deleted = new Set<string>();
|
|
51
|
+
#next = 2000;
|
|
52
|
+
async post(channelId: string, post: AgentPost) {
|
|
53
|
+
this.posts.push({ channelId, post });
|
|
54
|
+
}
|
|
55
|
+
/** The server's categories in order, each with its channels in order. */
|
|
56
|
+
categories: CategoryLayout[] = [];
|
|
57
|
+
#nextCategory = 9000;
|
|
58
|
+
arranged: { layout: CategoryLayout[]; remove: string[] }[] = [];
|
|
59
|
+
categoryOf(channelId: string) {
|
|
60
|
+
return this.categories.find((c) => c.channelIds.includes(channelId))?.name;
|
|
61
|
+
}
|
|
62
|
+
/** Moves a channel to the end of the category, made when missing. */
|
|
63
|
+
put(channelId: string, category: AgentCategory) {
|
|
64
|
+
for (const c of this.categories)
|
|
65
|
+
c.channelIds = c.channelIds.filter((id) => id !== channelId);
|
|
66
|
+
let found = this.categories.find((c) => c.name === category);
|
|
67
|
+
if (!found) {
|
|
68
|
+
found = {
|
|
69
|
+
id: String(this.#nextCategory++),
|
|
70
|
+
name: category,
|
|
71
|
+
channelIds: [],
|
|
72
|
+
};
|
|
73
|
+
this.categories.push(found);
|
|
74
|
+
}
|
|
75
|
+
found.channelIds.push(channelId);
|
|
76
|
+
}
|
|
77
|
+
async createChannel(name: string, topic: string, category: AgentCategory) {
|
|
78
|
+
this.created.push(name);
|
|
79
|
+
const id = String(this.#next++);
|
|
80
|
+
this.topics.set(id, topic);
|
|
81
|
+
this.put(id, category);
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
async placeIn(channelId: string, category: AgentCategory) {
|
|
85
|
+
if (this.categoryOf(channelId) === category) return false;
|
|
86
|
+
this.put(channelId, category);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
async layout() {
|
|
90
|
+
return structuredClone(this.categories);
|
|
91
|
+
}
|
|
92
|
+
async arrange(layout: CategoryLayout[], remove: string[]) {
|
|
93
|
+
this.arranged.push({ layout: structuredClone(layout), remove });
|
|
94
|
+
this.categories = layout.map((c) => ({
|
|
95
|
+
id: c.id ?? String(this.#nextCategory++),
|
|
96
|
+
name: c.name,
|
|
97
|
+
channelIds: [...c.channelIds],
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
history: ChannelMessage[] = [];
|
|
101
|
+
reads: { channelId: string; limit: number; around?: string }[] = [];
|
|
102
|
+
async read(channelId: string, options: { limit: number; around?: string }) {
|
|
103
|
+
this.reads.push({ channelId, ...options });
|
|
104
|
+
return this.history;
|
|
105
|
+
}
|
|
106
|
+
topics = new Map<string, string>();
|
|
107
|
+
async setTopic(channelId: string, topic: string) {
|
|
108
|
+
this.topics.set(channelId, topic);
|
|
109
|
+
}
|
|
110
|
+
async exists(channelId: string) {
|
|
111
|
+
return !this.deleted.has(channelId);
|
|
112
|
+
}
|
|
113
|
+
webhooksRemoved: string[] = [];
|
|
114
|
+
async removeWebhook(channelId: string) {
|
|
115
|
+
this.webhooksRemoved.push(channelId);
|
|
116
|
+
}
|
|
117
|
+
texts(channelId: string) {
|
|
118
|
+
return this.posts
|
|
119
|
+
.filter((p) => p.channelId === channelId)
|
|
120
|
+
.map((p) => `${p.post.name}: ${p.post.chunks.join("")}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Answers every turn with a scripted reply; records what each turn received. */
|
|
125
|
+
export class FakeRuntime implements AgentTurnRunner {
|
|
126
|
+
turns: TurnRequest[] = [];
|
|
127
|
+
reply: (request: TurnRequest) => string = (r) => `${r.agent?.name} ok`;
|
|
128
|
+
held = new Map<ChannelKey, PendingConfirmation>();
|
|
129
|
+
fresh: ChannelKey[] = [];
|
|
130
|
+
/** Runs inside a turn, like a tool call the model makes. */
|
|
131
|
+
during?: (request: TurnRequest) => unknown;
|
|
132
|
+
/** The next turn ends as if the owner pressed stop. */
|
|
133
|
+
stopNext = false;
|
|
134
|
+
async runTurn(request: TurnRequest): Promise<TurnResult> {
|
|
135
|
+
this.turns.push(request);
|
|
136
|
+
await this.during?.(request);
|
|
137
|
+
if (this.stopNext) {
|
|
138
|
+
this.stopNext = false;
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
error: new AgentRunError("stopped by the owner"),
|
|
142
|
+
stopped: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { ok: true, text: this.reply(request) };
|
|
146
|
+
}
|
|
147
|
+
async heldActions(session: ChannelKey) {
|
|
148
|
+
return this.held.get(session);
|
|
149
|
+
}
|
|
150
|
+
async startFresh(session: ChannelKey) {
|
|
151
|
+
this.fresh.push(session);
|
|
152
|
+
}
|
|
153
|
+
usage = new Map<ChannelKey, ContextUse>();
|
|
154
|
+
contextUsage(session: ChannelKey) {
|
|
155
|
+
return this.usage.get(session);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// CI runs the setup below about ten times slower than a laptop, and bun's 5 s hook limit
|
|
160
|
+
// left a timed-out hook running while the next test dropped its tables.
|
|
161
|
+
export const HOOK_TIMEOUT_MS = 30_000;
|
|
162
|
+
|
|
163
|
+
// Runs against a real PostgreSQL, only when ROUNDTABLE_TEST_DATABASE_URL is set.
|
|
164
|
+
let store: TestStore<AgentStore>;
|
|
165
|
+
let channels: FakeChannels;
|
|
166
|
+
let runtime: FakeRuntime;
|
|
167
|
+
export interface TeamFixture {
|
|
168
|
+
store: TestStore<AgentStore>;
|
|
169
|
+
channels: FakeChannels;
|
|
170
|
+
runtime: FakeRuntime;
|
|
171
|
+
queue: ChannelQueue;
|
|
172
|
+
team: AgentTeam;
|
|
173
|
+
skillStore: TestStore<SkillStore>;
|
|
174
|
+
skills: SkillRegistry;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The fixture of the running test; set by `useTeamFixture` before each test. */
|
|
178
|
+
export const fx = {} as TeamFixture;
|
|
179
|
+
|
|
180
|
+
/** What the team reads from its plugins and its scorer; tests set it. */
|
|
181
|
+
export const script: {
|
|
182
|
+
/** The relevance scores the scorer answers with. */
|
|
183
|
+
scores: Record<string, number> | undefined;
|
|
184
|
+
/** The prompt sections plugins add. */
|
|
185
|
+
prompt: PromptSection[];
|
|
186
|
+
/** The tools plugins defined for agents. */
|
|
187
|
+
tools: string[];
|
|
188
|
+
} = { scores: undefined, prompt: [], tools: [] };
|
|
189
|
+
let queue: ChannelQueue;
|
|
190
|
+
let team: AgentTeam;
|
|
191
|
+
let skillStore: TestStore<SkillStore>;
|
|
192
|
+
let skills: SkillRegistry;
|
|
193
|
+
export const removedSchedules: number[] = [];
|
|
194
|
+
export const stops: string[] = [];
|
|
195
|
+
/** The events the team reported, in order, as `turnStarted infra`, `turnEnded infra ok`, or `changed`. */
|
|
196
|
+
export const eventLog: string[] = [];
|
|
197
|
+
|
|
198
|
+
/** What a plugin adds to every agent's tools: a tool of its own and a group of another server's. */
|
|
199
|
+
const PLUGIN_SELECTION = { tools: ["memory_add"], groups: ["workspace"] };
|
|
200
|
+
|
|
201
|
+
/** The team a fresh database starts with: a coordinator in the entry channel, then two specialists. */
|
|
202
|
+
function seeds(entryChannelId: string): AgentSeed[] {
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
name: "coordinator",
|
|
206
|
+
displayName: "Coordinator",
|
|
207
|
+
channelId: entryChannelId,
|
|
208
|
+
prompt:
|
|
209
|
+
"You are the chief of staff of the owner's agent team. You hand work that belongs to a specialist to that agent with message_agent, one owner per task, and propose a new agent when a lasting responsibility has no owner.",
|
|
210
|
+
avatarPrompt: "A chief of staff mid-command, pointing the way.",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "doctor",
|
|
214
|
+
displayName: "Doctor",
|
|
215
|
+
prompt:
|
|
216
|
+
"You keep the owner's agents healthy: read an agent's prompt with agent_get, find the instruction that caused a misbehavior, and fix it with agent_update.",
|
|
217
|
+
avatarPrompt: "A bot doctor holding up a giant stethoscope.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "infra",
|
|
221
|
+
displayName: "Infra",
|
|
222
|
+
prompt:
|
|
223
|
+
"You manage the owner's servers. Check before you change: read state first, state what you will change and why, then act.",
|
|
224
|
+
avatarPrompt: "An infra engineer battling a server rack.",
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Waits until every queued channel task has settled and every active agent has its avatar,
|
|
231
|
+
* which startup draws in the background; a fixed sleep was too short on a slow runner.
|
|
232
|
+
*/
|
|
233
|
+
export async function settle(): Promise<void> {
|
|
234
|
+
const deadline = Date.now() + 10_000;
|
|
235
|
+
const quiet = () =>
|
|
236
|
+
queue.busy().length === 0 &&
|
|
237
|
+
store.agents().every((a) => a.status !== "active" || a.avatarHash);
|
|
238
|
+
while (!quiet() && Date.now() < deadline) await Bun.sleep(5);
|
|
239
|
+
// Work the last task queued is still finishing.
|
|
240
|
+
await Bun.sleep(20);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Registers the fixture's per-test setup in the describe it is called from. */
|
|
244
|
+
export function useTeamFixture(): void {
|
|
245
|
+
beforeEach(async () => {
|
|
246
|
+
// The expected texts are the neutral English catalog's.
|
|
247
|
+
useTestLocale();
|
|
248
|
+
await store?.close();
|
|
249
|
+
await skillStore?.close();
|
|
250
|
+
const admin = new SQL(testDatabaseUrl);
|
|
251
|
+
for (const table of [
|
|
252
|
+
"agents",
|
|
253
|
+
"agent_groups",
|
|
254
|
+
"agent_group_messages",
|
|
255
|
+
"agent_group_cursors",
|
|
256
|
+
"skills",
|
|
257
|
+
"skill_groups",
|
|
258
|
+
"agent_skills",
|
|
259
|
+
])
|
|
260
|
+
await admin.unsafe(`DROP TABLE IF EXISTS ${table}`);
|
|
261
|
+
await admin.close();
|
|
262
|
+
store = await openTestStore(AgentStore, TEST_GUILD);
|
|
263
|
+
const skillDir = mkdtempSync(join(tmpdir(), "roundtable-team-skills-"));
|
|
264
|
+
mkdirSync(join(skillDir, "repos/acme/kit/.git"), { recursive: true });
|
|
265
|
+
mkdirSync(join(skillDir, "repos/acme/kit/skills/kit-do"), {
|
|
266
|
+
recursive: true,
|
|
267
|
+
});
|
|
268
|
+
writeFileSync(
|
|
269
|
+
join(skillDir, "repos/acme/kit/skills/kit-do/SKILL.md"),
|
|
270
|
+
"---\nname: kit-do\ndescription: Use when changing source.\n---\n",
|
|
271
|
+
);
|
|
272
|
+
mkdirSync(join(skillDir, "builtin/writing-skills"), { recursive: true });
|
|
273
|
+
writeFileSync(
|
|
274
|
+
join(skillDir, "builtin/writing-skills/SKILL.md"),
|
|
275
|
+
"---\nname: writing-skills\ndescription: Use when creating, editing, or reviewing a skill.\n---\n",
|
|
276
|
+
);
|
|
277
|
+
skillStore = await openTestStore(SkillStore, TEST_GUILD);
|
|
278
|
+
skills = new SkillRegistry({
|
|
279
|
+
store: skillStore,
|
|
280
|
+
reposDir: join(skillDir, "repos"),
|
|
281
|
+
writtenDir: join(skillDir, "skills"),
|
|
282
|
+
builtinDir: join(skillDir, "builtin"),
|
|
283
|
+
logger: silentLogger(),
|
|
284
|
+
});
|
|
285
|
+
skills.init();
|
|
286
|
+
await skills.link("acme/kit", "skills");
|
|
287
|
+
channels = new FakeChannels();
|
|
288
|
+
runtime = new FakeRuntime();
|
|
289
|
+
script.scores = undefined;
|
|
290
|
+
script.prompt = [];
|
|
291
|
+
script.tools = [];
|
|
292
|
+
queue = channelQueue();
|
|
293
|
+
removedSchedules.length = 0;
|
|
294
|
+
stops.length = 0;
|
|
295
|
+
eventLog.length = 0;
|
|
296
|
+
team = new AgentTeam({
|
|
297
|
+
owner: TEST_OWNER,
|
|
298
|
+
shellUser: "mcops",
|
|
299
|
+
guildId: "1",
|
|
300
|
+
entryChannelId: ENTRY,
|
|
301
|
+
seeds: () => seeds(ENTRY),
|
|
302
|
+
store,
|
|
303
|
+
channels,
|
|
304
|
+
studio: {
|
|
305
|
+
url: (hash) => `https://x/avatars/${hash ?? "default"}.png`,
|
|
306
|
+
draw: async () => "a".repeat(64),
|
|
307
|
+
edit: async () => "b".repeat(64),
|
|
308
|
+
},
|
|
309
|
+
runtime: () => runtime,
|
|
310
|
+
pluginSelection: () => PLUGIN_SELECTION,
|
|
311
|
+
confirmations: { approves: async (_p, reply) => reply.includes("yes") },
|
|
312
|
+
scorer: { score: async () => script.scores },
|
|
313
|
+
events: {
|
|
314
|
+
turnStarted: (turn) => eventLog.push(`turnStarted ${turn.agent}`),
|
|
315
|
+
turnEnded: (turn) =>
|
|
316
|
+
eventLog.push(`turnEnded ${turn.agent} ${turn.result}`),
|
|
317
|
+
changed: () => eventLog.push("changed"),
|
|
318
|
+
},
|
|
319
|
+
promptSections: () => script.prompt,
|
|
320
|
+
pluginTools: () => script.tools,
|
|
321
|
+
models: {
|
|
322
|
+
defaults: {
|
|
323
|
+
model: "claude-bridge/claude-opus-5-5",
|
|
324
|
+
thinking: "medium",
|
|
325
|
+
},
|
|
326
|
+
usable: async () => USABLE,
|
|
327
|
+
},
|
|
328
|
+
schedules: {
|
|
329
|
+
forChannel: async (channel) =>
|
|
330
|
+
channel === discordKey("2000") ? [{ id: 7 }] : [],
|
|
331
|
+
remove: async (id) => removedSchedules.push(id),
|
|
332
|
+
},
|
|
333
|
+
queue,
|
|
334
|
+
startTyping: () => () => undefined,
|
|
335
|
+
showStop: (channel) => {
|
|
336
|
+
stops.push(`show ${channel}`);
|
|
337
|
+
return () => stops.push(`hide ${channel}`);
|
|
338
|
+
},
|
|
339
|
+
workDir: "/tmp/agents/work",
|
|
340
|
+
sharedPrompt: "Shared rules.",
|
|
341
|
+
skills,
|
|
342
|
+
logger: silentLogger(),
|
|
343
|
+
});
|
|
344
|
+
await team.start();
|
|
345
|
+
Object.assign(fx, {
|
|
346
|
+
store,
|
|
347
|
+
channels,
|
|
348
|
+
runtime,
|
|
349
|
+
queue,
|
|
350
|
+
team,
|
|
351
|
+
skillStore,
|
|
352
|
+
skills,
|
|
353
|
+
});
|
|
354
|
+
await settle();
|
|
355
|
+
}, HOOK_TIMEOUT_MS);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const opsGroup = () => {
|
|
359
|
+
const group = store.group("ops");
|
|
360
|
+
if (!group) throw new Error("no ops group");
|
|
361
|
+
return group;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export const agentChannel = (name: string) =>
|
|
365
|
+
discordKey(store.agent(name)?.channelId ?? "");
|
|
366
|
+
|
|
367
|
+
/** Runs `action` during the coordinator's turn, as a tool the model calls does. */
|
|
368
|
+
export async function duringTurn<T>(action: () => Promise<T>): Promise<T> {
|
|
369
|
+
let outcome: { value: T } | { error: unknown } | undefined;
|
|
370
|
+
runtime.during = async () => {
|
|
371
|
+
// Only the first turn is the model's; the agent it creates starts one of its own.
|
|
372
|
+
runtime.during = undefined;
|
|
373
|
+
outcome = await action().then(
|
|
374
|
+
(value) => ({ value }),
|
|
375
|
+
(error: unknown) => ({ error }),
|
|
376
|
+
);
|
|
377
|
+
};
|
|
378
|
+
await team.answerOwner(
|
|
379
|
+
agentChannel("coordinator"),
|
|
380
|
+
OWNER_SPEAKER,
|
|
381
|
+
"go",
|
|
382
|
+
"go",
|
|
383
|
+
NO_ATTACHMENTS,
|
|
384
|
+
);
|
|
385
|
+
runtime.during = undefined;
|
|
386
|
+
if (!outcome || "error" in outcome)
|
|
387
|
+
throw (outcome as { error: unknown })?.error;
|
|
388
|
+
return outcome.value;
|
|
389
|
+
}
|
|
@@ -5,11 +5,8 @@ import { AgentError, ScheduleError } from "../domain/errors.ts";
|
|
|
5
5
|
import type { AgentTurnScope } from "../domain/ports.ts";
|
|
6
6
|
import type { ThinkingSetting } from "../models.ts";
|
|
7
7
|
import { SHELL_TOOLS } from "../modules/host-shell/shell-policy.ts";
|
|
8
|
-
import type { ResolvedSkill } from "../modules/skills/skill-
|
|
9
|
-
import {
|
|
10
|
-
SKILL_TOOLS,
|
|
11
|
-
skillToolsExtension,
|
|
12
|
-
} from "../modules/skills/skill-tools.ts";
|
|
8
|
+
import type { ResolvedSkill } from "../modules/skills/skill-rules.ts";
|
|
9
|
+
import type { AgentTeam } from "../services.ts";
|
|
13
10
|
import type { TurnSelection } from "../sessions.ts";
|
|
14
11
|
import { type Speaker, THE_SPEAKER } from "../speakers.ts";
|
|
15
12
|
import { agentSystemPrompt } from "./agent-prompt.ts";
|
|
@@ -17,12 +14,13 @@ import type { Agent } from "./agent-store.ts";
|
|
|
17
14
|
import {
|
|
18
15
|
AGENT_TOOLS,
|
|
19
16
|
type AgentOps,
|
|
17
|
+
AVATAR_TOOL,
|
|
20
18
|
type AvatarMode,
|
|
21
19
|
agentToolsExtension,
|
|
22
20
|
MESSAGE_AGENT_TOOL,
|
|
23
21
|
} from "./agent-tools.ts";
|
|
24
22
|
import { TeamEditing } from "./team-editing.ts";
|
|
25
|
-
import {
|
|
23
|
+
import { channelOwner, discordKey } from "./team-keys.ts";
|
|
26
24
|
import {
|
|
27
25
|
layoutText,
|
|
28
26
|
planArrangement,
|
|
@@ -49,7 +47,7 @@ const READ_LIMIT = 50;
|
|
|
49
47
|
* This team creates, changes, arranges, and archives agents and groups; its TeamTurns runs
|
|
50
48
|
* their turns, group rounds, and messages between agents.
|
|
51
49
|
*/
|
|
52
|
-
export class
|
|
50
|
+
export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
53
51
|
readonly #options: AgentTeamOptions;
|
|
54
52
|
readonly #turns: TeamTurns;
|
|
55
53
|
readonly #listeners: (() => void)[] = [];
|
|
@@ -101,7 +99,7 @@ export class AgentTeam implements AgentOps {
|
|
|
101
99
|
return channelOwner(this.#options.store, channel);
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
/** The tools every agent turn runs with: the plugins', plus the shell
|
|
102
|
+
/** The tools every agent turn runs with: the plugins', plus the shell and agent tools. */
|
|
105
103
|
selection(scope?: AgentTurnScope): TurnSelection {
|
|
106
104
|
const plugged = this.#options.pluginSelection();
|
|
107
105
|
return {
|
|
@@ -112,9 +110,10 @@ export class AgentTeam implements AgentOps {
|
|
|
112
110
|
...SHELL_TOOLS,
|
|
113
111
|
// Group members hand off by mentioning each other, so a group seat has no message_agent.
|
|
114
112
|
...AGENT_TOOLS.filter(
|
|
115
|
-
(tool) =>
|
|
113
|
+
(tool) =>
|
|
114
|
+
!(scope?.group && tool === MESSAGE_AGENT_TOOL) &&
|
|
115
|
+
!(tool === AVATAR_TOOL && !this.#avatars()),
|
|
116
116
|
),
|
|
117
|
-
...SKILL_TOOLS,
|
|
118
117
|
...(this.#options.pluginTools?.() ?? []),
|
|
119
118
|
]),
|
|
120
119
|
],
|
|
@@ -147,6 +146,7 @@ export class AgentTeam implements AgentOps {
|
|
|
147
146
|
workDir,
|
|
148
147
|
owner,
|
|
149
148
|
shellUser,
|
|
149
|
+
avatars: this.#avatars(),
|
|
150
150
|
...(group
|
|
151
151
|
? {
|
|
152
152
|
group: {
|
|
@@ -190,22 +190,22 @@ export class AgentTeam implements AgentOps {
|
|
|
190
190
|
return current ? [current, ...usable.filter((m) => m !== current)] : usable;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
/**
|
|
193
|
+
/** Whether agents can be offered drawing: an image provider is configured. */
|
|
194
|
+
#avatars(): boolean {
|
|
195
|
+
return this.#options.studio.canDraw !== false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The agent tools of one agent session. */
|
|
194
199
|
extension(scope: AgentTurnScope): ExtensionFactory {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
store.activeAgent(name);
|
|
200
|
+
return agentToolsExtension(this, scope, THE_SPEAKER, {
|
|
201
|
+
avatars: this.#avatars(),
|
|
202
|
+
skills: this.#options.skills !== undefined,
|
|
199
203
|
});
|
|
200
|
-
return async (pi) => {
|
|
201
|
-
await agentTools(pi);
|
|
202
|
-
await skillTools(pi);
|
|
203
|
-
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
/** The skill files an agent's sessions load, built-in ones included. */
|
|
206
|
+
/** The skill files an agent's sessions load, built-in ones included; none while the skills addon is off. */
|
|
207
207
|
skillsOf(name: string): ResolvedSkill[] {
|
|
208
|
-
return this.#options.skills
|
|
208
|
+
return this.#options.skills?.carried(name).skills ?? [];
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** Another active agent's channel, for schedule_list. */
|
|
@@ -213,7 +213,7 @@ export class AgentTeam implements AgentOps {
|
|
|
213
213
|
try {
|
|
214
214
|
const agent = this.#options.store.activeAgent(name);
|
|
215
215
|
if (!agent.channelId) throw new AgentError(`${name} has no channel yet`);
|
|
216
|
-
return
|
|
216
|
+
return discordKey(agent.channelId);
|
|
217
217
|
} catch (error) {
|
|
218
218
|
throw new ScheduleError(
|
|
219
219
|
error instanceof Error ? error.message : String(error),
|
|
@@ -254,7 +254,7 @@ export class AgentTeam implements AgentOps {
|
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
256
|
* The owner's message in an agent's channel; call inside the channel's queue. `text`
|
|
257
|
-
* carries the reference, `replyText` is what
|
|
257
|
+
* carries the reference, `replyText` is what they typed.
|
|
258
258
|
*/
|
|
259
259
|
answerOwner(
|
|
260
260
|
channel: ChannelKey,
|
|
@@ -380,7 +380,8 @@ export class AgentTeam implements AgentOps {
|
|
|
380
380
|
return agentDetails(
|
|
381
381
|
agent,
|
|
382
382
|
models.defaults,
|
|
383
|
-
skills
|
|
383
|
+
skills?.describeCarried(agent.name),
|
|
384
|
+
this.#avatars(),
|
|
384
385
|
);
|
|
385
386
|
}
|
|
386
387
|
|
|
@@ -408,7 +409,7 @@ export class AgentTeam implements AgentOps {
|
|
|
408
409
|
name: string;
|
|
409
410
|
displayName: string;
|
|
410
411
|
prompt: string;
|
|
411
|
-
avatarPrompt
|
|
412
|
+
avatarPrompt?: string;
|
|
412
413
|
task: string;
|
|
413
414
|
category?: string;
|
|
414
415
|
skills?: string[];
|
|
@@ -23,12 +23,15 @@ import {
|
|
|
23
23
|
/** The tool that hands a request to another agent; a group seat has none. */
|
|
24
24
|
export const MESSAGE_AGENT_TOOL = "message_agent";
|
|
25
25
|
|
|
26
|
+
/** The tool that redraws an avatar; a host without an image provider does not offer it. */
|
|
27
|
+
export const AVATAR_TOOL = "agent_avatar";
|
|
28
|
+
|
|
26
29
|
export const AGENT_TOOLS = [
|
|
27
30
|
"agent_list",
|
|
28
31
|
"agent_get",
|
|
29
32
|
"agent_update",
|
|
30
33
|
"agent_create",
|
|
31
|
-
|
|
34
|
+
AVATAR_TOOL,
|
|
32
35
|
MESSAGE_AGENT_TOOL,
|
|
33
36
|
"group_create",
|
|
34
37
|
"group_update",
|
|
@@ -58,7 +61,8 @@ export interface AgentOps {
|
|
|
58
61
|
name: string;
|
|
59
62
|
displayName: string;
|
|
60
63
|
prompt: string;
|
|
61
|
-
|
|
64
|
+
/** Absent on a host without an image provider. */
|
|
65
|
+
avatarPrompt?: string;
|
|
62
66
|
task: string;
|
|
63
67
|
category?: string;
|
|
64
68
|
skills?: string[];
|
|
@@ -120,10 +124,19 @@ const MEMBERS = Type.Array(Type.String(), {
|
|
|
120
124
|
|
|
121
125
|
type AgentToolDef = TextToolDef<(typeof AGENT_TOOLS)[number]>;
|
|
122
126
|
|
|
127
|
+
/** What the agent tools offer, by what the host can do. */
|
|
128
|
+
export interface AgentToolOptions {
|
|
129
|
+
/** Whether an image provider is configured; without one there is no avatar prompt and no agent_avatar. */
|
|
130
|
+
avatars: boolean;
|
|
131
|
+
/** Whether the skills addon is on; without it agent_create takes no skills. */
|
|
132
|
+
skills: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
123
135
|
function toolDefs(
|
|
124
136
|
ops: AgentOps,
|
|
125
137
|
scope: AgentTurnScope,
|
|
126
138
|
owner: OwnerIdentity,
|
|
139
|
+
{ avatars, skills: skillsOn }: AgentToolOptions,
|
|
127
140
|
): AgentToolDef[] {
|
|
128
141
|
const o = ownerWords(owner);
|
|
129
142
|
const defs: AgentToolDef[] = [
|
|
@@ -182,8 +195,7 @@ function toolDefs(
|
|
|
182
195
|
{
|
|
183
196
|
name: "agent_create",
|
|
184
197
|
label: "Create agent",
|
|
185
|
-
description:
|
|
186
|
-
"Create a new agent with its own channel and a drawn avatar; it starts at once on the opening task in its channel. Its name is permanent.",
|
|
198
|
+
description: `Create a new agent with its own channel and ${avatars ? "a drawn avatar" : "an avatar generated from its display name"}; it starts at once on the opening task in its channel. Its name is permanent.`,
|
|
187
199
|
parameters: Type.Object({
|
|
188
200
|
name: Type.String({
|
|
189
201
|
pattern: "^[a-z0-9-]{1,32}$",
|
|
@@ -192,19 +204,23 @@ function toolDefs(
|
|
|
192
204
|
}),
|
|
193
205
|
display_name: DISPLAY,
|
|
194
206
|
prompt: PROMPT,
|
|
195
|
-
avatar_prompt: avatarPrompt(),
|
|
207
|
+
...(avatars ? { avatar_prompt: avatarPrompt() } : {}),
|
|
196
208
|
task: Type.String({
|
|
197
209
|
minLength: 1,
|
|
198
210
|
maxLength: 4000,
|
|
199
211
|
description: "Its opening task, self-contained.",
|
|
200
212
|
}),
|
|
201
213
|
category: Type.Optional(category("Agents")),
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
214
|
+
...(skillsOn
|
|
215
|
+
? {
|
|
216
|
+
skills: Type.Optional(
|
|
217
|
+
Type.Array(Type.String(), {
|
|
218
|
+
description:
|
|
219
|
+
"Skills it carries, from skill_list; pick those its role needs. Change them later with agent_skills.",
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
222
|
+
}
|
|
223
|
+
: {}),
|
|
208
224
|
}),
|
|
209
225
|
run: (i) => {
|
|
210
226
|
const category = optionalString(i, "category");
|
|
@@ -213,7 +229,9 @@ function toolDefs(
|
|
|
213
229
|
name: requiredString(i, "name"),
|
|
214
230
|
displayName: requiredString(i, "display_name"),
|
|
215
231
|
prompt: requiredString(i, "prompt"),
|
|
216
|
-
|
|
232
|
+
...(avatars
|
|
233
|
+
? { avatarPrompt: requiredString(i, "avatar_prompt") }
|
|
234
|
+
: {}),
|
|
217
235
|
task: requiredString(i, "task"),
|
|
218
236
|
...(category ? { category } : {}),
|
|
219
237
|
...(skills && skills.length > 0 ? { skills } : {}),
|
|
@@ -221,7 +239,7 @@ function toolDefs(
|
|
|
221
239
|
},
|
|
222
240
|
},
|
|
223
241
|
{
|
|
224
|
-
name:
|
|
242
|
+
name: AVATAR_TOOL,
|
|
225
243
|
label: "Redraw avatar",
|
|
226
244
|
description:
|
|
227
245
|
"Redraw an agent's avatar: redraw from its stored avatar prompt; new_prompt with a new avatar prompt, which replaces the stored one; or edit the current picture by an instruction.",
|
|
@@ -373,7 +391,7 @@ function toolDefs(
|
|
|
373
391
|
run: (i) =>
|
|
374
392
|
ops.message(scope, requiredString(i, "to"), requiredString(i, "text")),
|
|
375
393
|
});
|
|
376
|
-
return defs;
|
|
394
|
+
return avatars ? defs : defs.filter((def) => def.name !== AVATAR_TOOL);
|
|
377
395
|
}
|
|
378
396
|
|
|
379
397
|
/** Registers the agent tools for one agent session. */
|
|
@@ -381,6 +399,7 @@ export function agentToolsExtension(
|
|
|
381
399
|
ops: AgentOps,
|
|
382
400
|
scope: AgentTurnScope,
|
|
383
401
|
owner: OwnerIdentity,
|
|
402
|
+
options: AgentToolOptions,
|
|
384
403
|
): ExtensionFactory {
|
|
385
|
-
return textToolsExtension(toolDefs(ops, scope, owner), AgentError);
|
|
404
|
+
return textToolsExtension(toolDefs(ops, scope, owner, options), AgentError);
|
|
386
405
|
}
|