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
|
@@ -3,7 +3,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createCanvas, loadImage } from "canvas";
|
|
5
5
|
import type { ImageDrawer } from "../contract/providers.ts";
|
|
6
|
+
import { ProviderError } from "../errors.ts";
|
|
6
7
|
import type { HttpRoute } from "../http/listeners.ts";
|
|
8
|
+
import type { AvatarStudio } from "../services.ts";
|
|
9
|
+
import { fallbackAvatar } from "./fallback-avatar.ts";
|
|
7
10
|
|
|
8
11
|
const AVATAR_SIDE = 512;
|
|
9
12
|
const REFERENCE_SIDE = 512;
|
|
@@ -18,7 +21,8 @@ export interface AvatarStudioOptions {
|
|
|
18
21
|
publicUrl: string;
|
|
19
22
|
/** The assistant's neutral avatar: the style reference and the picture of an agent without one. */
|
|
20
23
|
referencePath: string;
|
|
21
|
-
|
|
24
|
+
/** Draws the pictures. Without one, the studio only makes each agent a picture from its name. */
|
|
25
|
+
draw?: ImageDrawer;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
/** A centered square crop scaled to at most `side` pixels, as PNG. */
|
|
@@ -44,10 +48,11 @@ async function squarePng(bytes: Uint8Array, side: number): Promise<Buffer> {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
|
-
* Agent avatars:
|
|
48
|
-
*
|
|
51
|
+
* Agent avatars: an image provider draws them in the assistant's style, and without one each agent
|
|
52
|
+
* gets a picture generated from its display name. They are kept and served by content hash, so a
|
|
53
|
+
* new picture has a new URL and Discord shows it at once.
|
|
49
54
|
*/
|
|
50
|
-
export class AvatarStudio {
|
|
55
|
+
export class FileAvatarStudio implements AvatarStudio {
|
|
51
56
|
readonly #options: AvatarStudioOptions;
|
|
52
57
|
#defaultHash: string | undefined;
|
|
53
58
|
|
|
@@ -63,6 +68,11 @@ export class AvatarStudio {
|
|
|
63
68
|
);
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
/** Whether an image provider is configured; without one, only `fallback` makes pictures. */
|
|
72
|
+
get canDraw(): boolean {
|
|
73
|
+
return this.#options.draw !== undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
/** The public URL of a picture, or of the default when there is none. */
|
|
67
77
|
url(hash: string | undefined): string {
|
|
68
78
|
const chosen = hash ?? this.#defaultHash;
|
|
@@ -76,11 +86,12 @@ export class AvatarStudio {
|
|
|
76
86
|
* so agents stay easy to tell apart.
|
|
77
87
|
*/
|
|
78
88
|
async draw(avatarPrompt: string): Promise<string> {
|
|
89
|
+
const draw = this.#drawer();
|
|
79
90
|
const reference = await squarePng(
|
|
80
91
|
readFileSync(this.#options.referencePath),
|
|
81
92
|
REFERENCE_SIDE,
|
|
82
93
|
);
|
|
83
|
-
const bytes = await
|
|
94
|
+
const bytes = await draw(
|
|
84
95
|
`Draw a square Discord avatar of the person in the reference image, redrawn as an anime character: keep their face, short dark hair, and features recognizable as the same person, but in vivid anime cel-shading with a big, exaggerated expression and a dramatic pose that shows the role. Dress them and paint the background exactly in the colours the description names; ignore the reference's green jacket and teal background. Head-and-shoulders framing that still reads at 40 px. No text, no border. The role: ${avatarPrompt}`,
|
|
85
96
|
[{ data: reference.toString("base64"), mimeType: "image/png" }],
|
|
86
97
|
);
|
|
@@ -89,16 +100,31 @@ export class AvatarStudio {
|
|
|
89
100
|
|
|
90
101
|
/** Edits a stored picture by an instruction; returns the new picture's hash. */
|
|
91
102
|
async edit(hash: string | undefined, instruction: string): Promise<string> {
|
|
103
|
+
const draw = this.#drawer();
|
|
92
104
|
const current = readFileSync(
|
|
93
105
|
this.#path(hash ?? this.#defaultHashOrThrow()),
|
|
94
106
|
);
|
|
95
|
-
const bytes = await
|
|
107
|
+
const bytes = await draw(
|
|
96
108
|
`Edit this Discord avatar: ${instruction}. Keep everything else, the style, and the square head-and-shoulders framing the same. No text, no border.`,
|
|
97
109
|
[{ data: current.toString("base64"), mimeType: "image/png" }],
|
|
98
110
|
);
|
|
99
111
|
return this.#store(bytes);
|
|
100
112
|
}
|
|
101
113
|
|
|
114
|
+
/**
|
|
115
|
+
* A picture made from an agent's display name, its name, and the assistant's icon, for a host without an
|
|
116
|
+
* image provider; the same name always gives the same picture. Returns its hash.
|
|
117
|
+
*/
|
|
118
|
+
async fallback(displayName: string, name: string): Promise<string> {
|
|
119
|
+
return this.#store(
|
|
120
|
+
await fallbackAvatar(
|
|
121
|
+
displayName,
|
|
122
|
+
readFileSync(this.#options.referencePath),
|
|
123
|
+
name,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
102
128
|
/** Avatar pictures, public on the listener that cloudflared forwards the public hostname to. */
|
|
103
129
|
route(listener: string): HttpRoute {
|
|
104
130
|
return {
|
|
@@ -127,6 +153,14 @@ export class AvatarStudio {
|
|
|
127
153
|
});
|
|
128
154
|
}
|
|
129
155
|
|
|
156
|
+
#drawer(): ImageDrawer {
|
|
157
|
+
if (!this.#options.draw)
|
|
158
|
+
throw new ProviderError(
|
|
159
|
+
"no image provider is configured, so avatars cannot be drawn",
|
|
160
|
+
);
|
|
161
|
+
return this.#options.draw;
|
|
162
|
+
}
|
|
163
|
+
|
|
130
164
|
#defaultHashOrThrow(): string {
|
|
131
165
|
if (!this.#defaultHash)
|
|
132
166
|
throw new Error("the avatar studio was not initialized");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createCanvas, loadImage } from "canvas";
|
|
3
|
+
|
|
4
|
+
const SIDE = 512;
|
|
5
|
+
/** The icon sits in a disc, with a badge overlapping its lower right. */
|
|
6
|
+
const ICON_RADIUS = 176;
|
|
7
|
+
const ICON_CENTER = { x: 256, y: 232 };
|
|
8
|
+
const BADGE = { x: 396, y: 396, radius: 64 };
|
|
9
|
+
|
|
10
|
+
const hsl = (h: number, s: number, l: number): string =>
|
|
11
|
+
`hsl(${Math.round(h) % 360},${s}%,${l}%)`;
|
|
12
|
+
|
|
13
|
+
const layer = (body: string): Buffer =>
|
|
14
|
+
Buffer.from(
|
|
15
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${SIDE}" height="${SIDE}" viewBox="0 0 ${SIDE} ${SIDE}">${body}</svg>`,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A stable picture for an agent nobody drew: the assistant's icon on a colour taken from the
|
|
20
|
+
* display name, with a badge holding the initial of the agent's name. The name is lowercase Latin
|
|
21
|
+
* letters, digits, and dashes, so the badge is always drawn from a font every server has, whatever
|
|
22
|
+
* script the display name is written in. The same display name, name, and icon always give the
|
|
23
|
+
* same bytes.
|
|
24
|
+
*/
|
|
25
|
+
export async function fallbackAvatar(
|
|
26
|
+
displayName: string,
|
|
27
|
+
icon: Uint8Array,
|
|
28
|
+
name: string,
|
|
29
|
+
): Promise<Buffer> {
|
|
30
|
+
const shown = displayName.normalize("NFC").trim();
|
|
31
|
+
const digest = createHash("sha256").update(shown).digest();
|
|
32
|
+
const byte = (i: number) => digest[i] ?? 0;
|
|
33
|
+
const hue = ((byte(0) << 8) | byte(1)) % 360;
|
|
34
|
+
const dark = hsl(hue, 58, 26);
|
|
35
|
+
|
|
36
|
+
const bubbles = [0, 1, 2]
|
|
37
|
+
.map((i) => {
|
|
38
|
+
const cx = (byte(2 + i * 3) / 255) * SIDE;
|
|
39
|
+
const cy = (byte(3 + i * 3) / 255) * SIDE;
|
|
40
|
+
const r = 90 + (byte(4 + i * 3) / 255) * 130;
|
|
41
|
+
return `<circle cx="${cx.toFixed(1)}" cy="${cy.toFixed(1)}" r="${r.toFixed(1)}" fill="#fff" fill-opacity="0.09"/>`;
|
|
42
|
+
})
|
|
43
|
+
.join("");
|
|
44
|
+
const back = layer(
|
|
45
|
+
`<defs><linearGradient id="g" x1="0" y1="0" x2="0.35" y2="1"><stop offset="0" stop-color="${hsl(hue, 62, 55)}"/><stop offset="1" stop-color="${hsl(hue + 32, 60, 33)}"/></linearGradient></defs>` +
|
|
46
|
+
`<rect width="${SIDE}" height="${SIDE}" fill="url(#g)"/>${bubbles}` +
|
|
47
|
+
`<circle cx="${ICON_CENTER.x}" cy="${ICON_CENTER.y}" r="${ICON_RADIUS + 8}" fill="#fff" fill-opacity="0.35"/>` +
|
|
48
|
+
`<circle cx="${ICON_CENTER.x}" cy="${ICON_CENTER.y}" r="${ICON_RADIUS}" fill="#fff" fill-opacity="0.55"/>`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const initial = (name.match(/[a-z0-9]/)?.[0] ?? "?").toUpperCase();
|
|
52
|
+
const mark = `<text x="${BADGE.x}" y="${BADGE.y + 28}" font-family="DejaVu Sans, Helvetica, Arial, sans-serif" font-size="80" font-weight="bold" text-anchor="middle" fill="${dark}">${initial}</text>`;
|
|
53
|
+
const front = layer(
|
|
54
|
+
`<circle cx="${BADGE.x}" cy="${BADGE.y}" r="${BADGE.radius + 8}" fill="${hsl(hue + 32, 60, 33)}"/>` +
|
|
55
|
+
`<circle cx="${BADGE.x}" cy="${BADGE.y}" r="${BADGE.radius}" fill="#fff"/>${mark}`,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const canvas = createCanvas(SIDE, SIDE);
|
|
59
|
+
const ctx = canvas.getContext("2d");
|
|
60
|
+
ctx.drawImage(await loadImage(back), 0, 0);
|
|
61
|
+
const face = await loadImage(Buffer.from(icon));
|
|
62
|
+
const crop = Math.min(face.width, face.height);
|
|
63
|
+
ctx.save();
|
|
64
|
+
ctx.beginPath();
|
|
65
|
+
ctx.arc(ICON_CENTER.x, ICON_CENTER.y, ICON_RADIUS, 0, Math.PI * 2);
|
|
66
|
+
ctx.clip();
|
|
67
|
+
ctx.drawImage(
|
|
68
|
+
face,
|
|
69
|
+
(face.width - crop) / 2,
|
|
70
|
+
(face.height - crop) / 2,
|
|
71
|
+
crop,
|
|
72
|
+
crop,
|
|
73
|
+
ICON_CENTER.x - ICON_RADIUS,
|
|
74
|
+
ICON_CENTER.y - ICON_RADIUS,
|
|
75
|
+
ICON_RADIUS * 2,
|
|
76
|
+
ICON_RADIUS * 2,
|
|
77
|
+
);
|
|
78
|
+
// Tint the icon's own backdrop with the name's colour so the disc belongs to the picture.
|
|
79
|
+
ctx.globalCompositeOperation = "multiply";
|
|
80
|
+
ctx.fillStyle = hsl(hue, 45, 93);
|
|
81
|
+
ctx.fillRect(0, 0, SIDE, SIDE);
|
|
82
|
+
ctx.restore();
|
|
83
|
+
ctx.drawImage(await loadImage(front), 0, 0);
|
|
84
|
+
return canvas.toBuffer("image/png");
|
|
85
|
+
}
|
|
@@ -6,10 +6,14 @@ import { openingTaskText } from "./agent-prompt.ts";
|
|
|
6
6
|
import { modelSetting, thinkingSetting } from "./agent-settings.ts";
|
|
7
7
|
import type { Agent } from "./agent-store.ts";
|
|
8
8
|
import type { AvatarMode } from "./agent-tools.ts";
|
|
9
|
-
import {
|
|
9
|
+
import { discordKey } from "./team-keys.ts";
|
|
10
10
|
import { groupTopic, teamCategory } from "./team-layout.ts";
|
|
11
11
|
import type { TeamContext } from "./team-options.ts";
|
|
12
12
|
|
|
13
|
+
/** Said when an avatar is to be redrawn on a host without an image provider. */
|
|
14
|
+
const NO_IMAGE_PROVIDER =
|
|
15
|
+
"No image provider is configured, so avatars cannot be redrawn; the current picture stays. A plugin fills the images slot to draw them.";
|
|
16
|
+
|
|
13
17
|
/** Creating and changing agents, their avatars, and groups. */
|
|
14
18
|
export class TeamEditing {
|
|
15
19
|
readonly #ctx: TeamContext;
|
|
@@ -74,37 +78,62 @@ export class TeamEditing {
|
|
|
74
78
|
name: string;
|
|
75
79
|
displayName: string;
|
|
76
80
|
prompt: string;
|
|
77
|
-
|
|
81
|
+
/** Required when an image provider is configured, ignored otherwise. */
|
|
82
|
+
avatarPrompt?: string;
|
|
78
83
|
task: string;
|
|
79
84
|
category?: string;
|
|
80
85
|
skills?: string[];
|
|
81
86
|
},
|
|
82
87
|
): Promise<string> {
|
|
88
|
+
const { store, studio, channels, queue, skills, logger } =
|
|
89
|
+
this.#ctx.options;
|
|
90
|
+
if (input.skills && input.skills.length > 0 && !skills)
|
|
91
|
+
throw new AgentError(
|
|
92
|
+
"Skills are off on this host, so an agent cannot carry any. Create it without skills.",
|
|
93
|
+
);
|
|
83
94
|
const speaker = this.#ctx.turns.speakerOf(caller);
|
|
84
95
|
if (!speaker)
|
|
85
96
|
throw new AgentError(
|
|
86
97
|
"agent_create can only be used during an agent turn.",
|
|
87
98
|
);
|
|
88
|
-
const
|
|
89
|
-
const {
|
|
99
|
+
const drawing = studio.canDraw !== false;
|
|
100
|
+
const {
|
|
101
|
+
category,
|
|
102
|
+
skills: carried = [],
|
|
103
|
+
avatarPrompt = "",
|
|
104
|
+
...fields
|
|
105
|
+
} = input;
|
|
106
|
+
if (drawing && avatarPrompt.trim() === "")
|
|
107
|
+
throw new AgentError("Give avatar_prompt: the avatar is drawn from it.");
|
|
90
108
|
const parent = teamCategory(category, "Agents");
|
|
91
109
|
store.checkNewName(input.name);
|
|
92
|
-
skills
|
|
110
|
+
skills?.checkRegistered(carried);
|
|
93
111
|
const channelId = await channels.createChannel(
|
|
94
112
|
input.name,
|
|
95
113
|
input.displayName,
|
|
96
114
|
parent,
|
|
97
115
|
);
|
|
98
|
-
const agent = await store.createAgent({
|
|
99
|
-
|
|
116
|
+
const agent = await store.createAgent({
|
|
117
|
+
...fields,
|
|
118
|
+
avatarPrompt: drawing ? avatarPrompt : "",
|
|
119
|
+
channelId,
|
|
120
|
+
});
|
|
121
|
+
if (skills && carried.length > 0)
|
|
122
|
+
await skills.attach(agent.name, carried, []);
|
|
100
123
|
logger.info({ agent: agent.name, by: caller.name }, "agent created");
|
|
101
124
|
this.#ctx.changed();
|
|
102
|
-
const avatar = await
|
|
103
|
-
(
|
|
125
|
+
const avatar = await (drawing
|
|
126
|
+
? this.#redraw(agent.name, "redraw")
|
|
127
|
+
: this.#generate(agent)
|
|
128
|
+
).then(
|
|
129
|
+
() =>
|
|
130
|
+
drawing
|
|
131
|
+
? "its avatar is drawn"
|
|
132
|
+
: "its avatar is generated from its display name",
|
|
104
133
|
(error: unknown) =>
|
|
105
134
|
`its avatar could not be drawn (${error instanceof Error ? error.message : String(error)}), so it uses ${assistantName()}'s for now`,
|
|
106
135
|
);
|
|
107
|
-
const home =
|
|
136
|
+
const home = discordKey(channelId);
|
|
108
137
|
const creator = store.agent(caller.name);
|
|
109
138
|
void queue
|
|
110
139
|
.run(home, () =>
|
|
@@ -138,9 +167,19 @@ export class TeamEditing {
|
|
|
138
167
|
return this.#redraw(name, mode, text);
|
|
139
168
|
}
|
|
140
169
|
|
|
170
|
+
/** Gives an agent the picture made from its display name; a host without an image provider has no other. */
|
|
171
|
+
async #generate(agent: Agent): Promise<Agent> {
|
|
172
|
+
const { store, studio } = this.#ctx.options;
|
|
173
|
+
if (!studio.fallback)
|
|
174
|
+
throw new AgentError("The avatar studio has no generated avatars.");
|
|
175
|
+
const avatarHash = await studio.fallback(agent.displayName, agent.name);
|
|
176
|
+
return store.updateAgent(agent.name, { avatarHash });
|
|
177
|
+
}
|
|
178
|
+
|
|
141
179
|
async #redraw(name: string, mode: AvatarMode, text?: string): Promise<Agent> {
|
|
142
180
|
const { store, studio, logger } = this.#ctx.options;
|
|
143
181
|
const agent = store.activeAgent(name);
|
|
182
|
+
if (studio.canDraw === false) throw new AgentError(NO_IMAGE_PROVIDER);
|
|
144
183
|
if (mode !== "redraw" && !text?.trim())
|
|
145
184
|
throw new AgentError(
|
|
146
185
|
mode === "edit"
|
|
@@ -1,12 +1,24 @@
|
|
|
1
|
+
import { channelKey, parseChannelKey } from "../contract/surface.ts";
|
|
1
2
|
import type { ChannelKey } from "../domain/conversation.ts";
|
|
2
3
|
import type { AgentTurnScope } from "../domain/ports.ts";
|
|
3
|
-
import type {
|
|
4
|
+
import type { AgentDirectory } from "../services.ts";
|
|
5
|
+
import type { Agent, AgentGroup } from "./agent-store.ts";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
/** The surface of the channels the agent server runs on. */
|
|
8
|
+
const DISCORD_SURFACE = "discord";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
/** The key of a Discord channel. */
|
|
11
|
+
export const discordKey = (channelId: string): ChannelKey =>
|
|
12
|
+
channelKey(DISCORD_SURFACE, channelId);
|
|
13
|
+
|
|
14
|
+
/** The Discord channel id of a key, or undefined when the key belongs to another surface. */
|
|
15
|
+
export function discordIdOf(channel: ChannelKey): string | undefined {
|
|
16
|
+
const { surface, id } = parseChannelKey(channel);
|
|
17
|
+
return surface === DISCORD_SURFACE ? id : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The id part of a key the caller already knows is a Discord one, because the agent server owns it. */
|
|
21
|
+
export const channelIdOf = (channel: ChannelKey) => parseChannelKey(channel).id;
|
|
10
22
|
|
|
11
23
|
/** An agent's conversation inside a group, apart from its own. */
|
|
12
24
|
export const groupSessionKey = (group: AgentGroup, agent: string): ChannelKey =>
|
|
@@ -14,7 +26,7 @@ export const groupSessionKey = (group: AgentGroup, agent: string): ChannelKey =>
|
|
|
14
26
|
|
|
15
27
|
/** An agent's own conversation. */
|
|
16
28
|
export function homeScope(agent: Agent): AgentTurnScope {
|
|
17
|
-
const home =
|
|
29
|
+
const home = discordKey(agent.channelId ?? "");
|
|
18
30
|
return { name: agent.name, session: home, home };
|
|
19
31
|
}
|
|
20
32
|
|
|
@@ -23,17 +35,18 @@ export function groupScope(agent: Agent, group: AgentGroup): AgentTurnScope {
|
|
|
23
35
|
return {
|
|
24
36
|
name: agent.name,
|
|
25
37
|
session: groupSessionKey(group, agent.name),
|
|
26
|
-
home:
|
|
38
|
+
home: discordKey(agent.channelId ?? ""),
|
|
27
39
|
group: group.name,
|
|
28
40
|
};
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
/** The agent or group that owns a channel of the agent server. */
|
|
32
44
|
export function channelOwner(
|
|
33
|
-
store: Pick<
|
|
45
|
+
store: Pick<AgentDirectory, "agentByChannel" | "groupByChannel">,
|
|
34
46
|
channel: ChannelKey,
|
|
35
47
|
): "agent" | "group" | undefined {
|
|
36
|
-
const id =
|
|
48
|
+
const id = discordIdOf(channel);
|
|
49
|
+
if (id === undefined) return undefined;
|
|
37
50
|
if (store.agentByChannel(id)) return "agent";
|
|
38
51
|
if (store.groupByChannel(id)) return "group";
|
|
39
52
|
return undefined;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AgentError } from "../domain/errors.ts";
|
|
2
2
|
import { messages } from "../i18n/index.ts";
|
|
3
|
+
import type { AgentDirectory } from "../services.ts";
|
|
3
4
|
import type { CategoryLayout } from "./agent-ports.ts";
|
|
4
|
-
import type { AgentGroup
|
|
5
|
+
import type { AgentGroup } from "./agent-store.ts";
|
|
5
6
|
|
|
6
7
|
type TeamStore = Pick<
|
|
7
|
-
|
|
8
|
+
AgentDirectory,
|
|
8
9
|
"agent" | "group" | "agentByChannel" | "groupByChannel"
|
|
9
10
|
>;
|
|
10
11
|
|
|
@@ -38,7 +39,7 @@ export function teamCategory(
|
|
|
38
39
|
/** A group channel's topic: who is in it and who hosts. */
|
|
39
40
|
export function groupTopic(
|
|
40
41
|
group: AgentGroup,
|
|
41
|
-
store: Pick<
|
|
42
|
+
store: Pick<AgentDirectory, "agent">,
|
|
42
43
|
): string {
|
|
43
44
|
const shown = (name: string) => store.agent(name)?.displayName ?? name;
|
|
44
45
|
return messages().groupTopic(
|
|
@@ -4,7 +4,7 @@ import type { AgentTurnScope } from "../domain/ports.ts";
|
|
|
4
4
|
import type { AgentGroup } from "./agent-store.ts";
|
|
5
5
|
import {
|
|
6
6
|
channelIdOf,
|
|
7
|
-
|
|
7
|
+
discordKey,
|
|
8
8
|
groupSessionKey,
|
|
9
9
|
homeScope,
|
|
10
10
|
} from "./team-keys.ts";
|
|
@@ -66,6 +66,7 @@ export class TeamLifecycle {
|
|
|
66
66
|
|
|
67
67
|
async #drawMissingAvatars(): Promise<void> {
|
|
68
68
|
const { store, studio, logger } = this.#ctx.options;
|
|
69
|
+
if (studio.canDraw === false) return this.#generateMissingAvatars();
|
|
69
70
|
for (const agent of store.agents()) {
|
|
70
71
|
if (agent.status !== "active" || agent.avatarHash) continue;
|
|
71
72
|
try {
|
|
@@ -78,6 +79,31 @@ export class TeamLifecycle {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Without an image provider, gives each active agent that has no picture the one made from
|
|
84
|
+
* its display name, and says so once instead of once per agent on every boot.
|
|
85
|
+
*/
|
|
86
|
+
async #generateMissingAvatars(): Promise<void> {
|
|
87
|
+
const { store, studio, logger } = this.#ctx.options;
|
|
88
|
+
if (!studio.fallback) return;
|
|
89
|
+
let generated = 0;
|
|
90
|
+
for (const agent of store.agents()) {
|
|
91
|
+
if (agent.status !== "active" || agent.avatarHash) continue;
|
|
92
|
+
try {
|
|
93
|
+
const avatarHash = await studio.fallback(agent.displayName, agent.name);
|
|
94
|
+
await store.updateAgent(agent.name, { avatarHash });
|
|
95
|
+
generated++;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.warn({ agent: agent.name, err: error }, "avatar not generated");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (generated > 0)
|
|
101
|
+
logger.info(
|
|
102
|
+
{ generated },
|
|
103
|
+
"no image provider is configured; agents without a picture got one generated from their display name",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
/** Starts an agent's or every group member's conversation over; call inside the channel's queue. */
|
|
82
108
|
async startFresh(channel: ChannelKey): Promise<void> {
|
|
83
109
|
const { store } = this.#ctx.options;
|
|
@@ -149,7 +175,7 @@ export class TeamLifecycle {
|
|
|
149
175
|
const agent = store.agentByChannel(channelId);
|
|
150
176
|
if (agent) {
|
|
151
177
|
const left = await store.archiveAgent(agent.name);
|
|
152
|
-
const home =
|
|
178
|
+
const home = discordKey(channelId);
|
|
153
179
|
for (const schedule of await schedules.forChannel(home))
|
|
154
180
|
await schedules.remove(schedule.id, home);
|
|
155
181
|
await runtime.startFresh(home);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ChannelKey } from "../domain/conversation.ts";
|
|
2
2
|
import type { ThinkingSetting } from "../models.ts";
|
|
3
|
-
import type { SkillRegistry } from "../modules/skills/skill-registry.ts";
|
|
4
3
|
import type { PromptSection } from "../plugin.ts";
|
|
4
|
+
import type { AvatarStudio, SkillRegistry } from "../services.ts";
|
|
5
5
|
import type { ToolSelection } from "../sessions.ts";
|
|
6
6
|
import type { AgentChannels, AgentModels } from "./agent-ports.ts";
|
|
7
7
|
import type { AgentGroup, AgentSeed } from "./agent-store.ts";
|
|
8
|
-
import type { AvatarStudio } from "./avatar-studio.ts";
|
|
9
8
|
import type { TeamTurns, TeamTurnsOptions } from "./team-turns.ts";
|
|
10
9
|
|
|
11
10
|
export interface AgentTeamOptions
|
|
@@ -19,7 +18,12 @@ export interface AgentTeamOptions
|
|
|
19
18
|
/** The tools plugins defined for agents, read before each agent turn. */
|
|
20
19
|
pluginTools?: () => readonly string[];
|
|
21
20
|
channels: AgentChannels;
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* `canDraw: false` marks a host without an image provider: agents are offered no drawing and
|
|
23
|
+
* get `fallback` pictures. A studio that leaves `canDraw` out draws.
|
|
24
|
+
*/
|
|
25
|
+
studio: Pick<AvatarStudio, "url" | "draw" | "edit"> &
|
|
26
|
+
Partial<Pick<AvatarStudio, "canDraw" | "fallback">>;
|
|
23
27
|
/** The tools and groups the plugins give every agent, read before each agent turn. */
|
|
24
28
|
pluginSelection: () => ToolSelection;
|
|
25
29
|
models: AgentModels;
|
|
@@ -35,8 +39,8 @@ export interface AgentTeamOptions
|
|
|
35
39
|
sharedPrompt: string;
|
|
36
40
|
/** The shared prompt for speakers other than the owner, when `sharedPrompt` speaks to the owner. */
|
|
37
41
|
guestPrompt?: string;
|
|
38
|
-
/** The skill registry: what each agent carries
|
|
39
|
-
skills
|
|
42
|
+
/** The skill registry: what each agent carries. Absent when the skills addon is off. */
|
|
43
|
+
skills?: SkillRegistry;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/** What the team's collaborators share: its options, its turns, and its change signal. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingSetting } from "../models.ts";
|
|
2
2
|
import type { ContextUse } from "./agent-ports.ts";
|
|
3
|
-
import { channelIdOf,
|
|
3
|
+
import { channelIdOf, discordKey } from "./team-keys.ts";
|
|
4
4
|
import type { TeamContext } from "./team-options.ts";
|
|
5
5
|
|
|
6
6
|
/** An active agent as the dashboard shows it. */
|
|
@@ -44,10 +44,10 @@ export async function teamStatus(ctx: TeamContext): Promise<TeamStatus> {
|
|
|
44
44
|
const agents: AgentStatus[] = [];
|
|
45
45
|
for (const agent of store.agents()) {
|
|
46
46
|
if (agent.status !== "active") continue;
|
|
47
|
-
const home = agent.channelId ?
|
|
47
|
+
const home = agent.channelId ? discordKey(agent.channelId) : undefined;
|
|
48
48
|
const workingIn = turns.workingIn(agent.name);
|
|
49
49
|
const queued = home ? queue.size(home) : 0;
|
|
50
|
-
const context = home ? runtime.contextUsage(home) : undefined;
|
|
50
|
+
const context = home ? runtime.contextUsage?.(home) : undefined;
|
|
51
51
|
const lastActive = turns.lastActive(agent.name);
|
|
52
52
|
agents.push({
|
|
53
53
|
name: agent.name,
|
|
@@ -72,7 +72,7 @@ export async function teamStatus(ctx: TeamContext): Promise<TeamStatus> {
|
|
|
72
72
|
channelId: group.channelId,
|
|
73
73
|
members: group.members.map((m) => store.agent(m)?.displayName ?? m),
|
|
74
74
|
host: store.agent(group.host)?.displayName ?? group.host,
|
|
75
|
-
busy: queue.size(
|
|
75
|
+
busy: queue.size(discordKey(group.channelId)),
|
|
76
76
|
...(lastActive ? { lastActive } : {}),
|
|
77
77
|
};
|
|
78
78
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AgentError } from "../domain/errors.ts";
|
|
2
2
|
import { assistantName } from "../i18n/index.ts";
|
|
3
3
|
import type { ThinkingSetting } from "../models.ts";
|
|
4
|
+
import type { AgentDirectory } from "../services.ts";
|
|
4
5
|
import { zonedStamp } from "../time.ts";
|
|
5
6
|
import type { ChannelMessage } from "./agent-ports.ts";
|
|
6
7
|
import { describeThinking } from "./agent-settings.ts";
|
|
7
|
-
import type { Agent
|
|
8
|
+
import type { Agent } from "./agent-store.ts";
|
|
8
9
|
|
|
9
10
|
/** One message as channel_read shows it: id, local time, author, text, attachments. */
|
|
10
11
|
export function messageLine(message: ChannelMessage): string {
|
|
@@ -26,7 +27,7 @@ export function channelIdFromRef(channel: string): string {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/** Every agent and group as agent_list shows them, before the category layout. */
|
|
29
|
-
export function teamListText(store: Pick<
|
|
30
|
+
export function teamListText(store: Pick<AgentDirectory, "agents" | "groups">) {
|
|
30
31
|
const agents = store.agents().map((a) => {
|
|
31
32
|
const channel = a.channelId ? `<#${a.channelId}>` : "no channel";
|
|
32
33
|
return `- ${a.name} "${a.displayName}" — ${channel}, ${a.status}`;
|
|
@@ -40,11 +41,12 @@ export function teamListText(store: Pick<AgentStore, "agents" | "groups">) {
|
|
|
40
41
|
return `Agents:\n${agents.join("\n") || "none"}\n\nGroups:\n${groups.join("\n") || "none"}`;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/** One agent as agent_get shows it; unset settings name the assistant's. */
|
|
44
|
+
/** One agent as agent_get shows it; unset settings name the assistant's, and no skills line while the skills addon is off. */
|
|
44
45
|
export function agentDetails(
|
|
45
46
|
agent: Agent,
|
|
46
47
|
defaults: { model: string; thinking: ThinkingSetting },
|
|
47
|
-
skills: string,
|
|
48
|
+
skills: string | undefined,
|
|
49
|
+
avatars = true,
|
|
48
50
|
): string {
|
|
49
51
|
return [
|
|
50
52
|
`Name: ${agent.name}`,
|
|
@@ -53,8 +55,8 @@ export function agentDetails(
|
|
|
53
55
|
`Channel: ${agent.channelId ? `<#${agent.channelId}>` : "none"}`,
|
|
54
56
|
`Model: ${agent.model ?? `${assistantName()}'s (${defaults.model})`}`,
|
|
55
57
|
`Thinking: ${agent.thinking ?? `${assistantName()}'s (${describeThinking(defaults.thinking)})`}`,
|
|
56
|
-
`Skills: ${skills}
|
|
57
|
-
`Avatar prompt: ${agent.avatarPrompt}
|
|
58
|
+
...(skills === undefined ? [] : [`Skills: ${skills}`]),
|
|
59
|
+
...(avatars ? [`Avatar prompt: ${agent.avatarPrompt}`] : []),
|
|
58
60
|
`Prompt:\n${agent.prompt}`,
|
|
59
61
|
].join("\n");
|
|
60
62
|
}
|
|
@@ -13,12 +13,12 @@ import type { AgentTurnScope } from "../domain/ports.ts";
|
|
|
13
13
|
import type { OwnerIdentity } from "../identity.ts";
|
|
14
14
|
import type { Logger } from "../log.ts";
|
|
15
15
|
import type { EventSink } from "../plugin.ts";
|
|
16
|
+
import type { AvatarStudio } from "../services.ts";
|
|
16
17
|
import type { TurnSelection } from "../sessions.ts";
|
|
17
18
|
import type { Speaker } from "../speakers.ts";
|
|
18
19
|
import type { ToolTiers } from "../tool-tiers.ts";
|
|
19
20
|
import type { AgentChannels, AgentTurnRunner } from "./agent-ports.ts";
|
|
20
|
-
import type { Agent,
|
|
21
|
-
import type { AvatarStudio } from "./avatar-studio.ts";
|
|
21
|
+
import type { Agent, PgAgentStore } from "./agent-store.ts";
|
|
22
22
|
import type { RelevanceScorer } from "./group-round.ts";
|
|
23
23
|
|
|
24
24
|
/** Agent messages without an owner message in between (spec behavior 13). */
|
|
@@ -43,7 +43,7 @@ export interface TeamTurnsOptions {
|
|
|
43
43
|
entryChannelId: string;
|
|
44
44
|
/** Who the agents work for, as their prompts and group history name them. */
|
|
45
45
|
owner: OwnerIdentity;
|
|
46
|
-
store:
|
|
46
|
+
store: PgAgentStore;
|
|
47
47
|
channels: Pick<AgentChannels, "post">;
|
|
48
48
|
studio: Pick<AvatarStudio, "url">;
|
|
49
49
|
/** Set once the runtime exists, which itself needs the team's tools. */
|
|
@@ -76,7 +76,7 @@ export interface TurnExtra {
|
|
|
76
76
|
attachments?: TurnAttachments;
|
|
77
77
|
confirmed?: boolean;
|
|
78
78
|
steerable?: boolean;
|
|
79
|
-
/** The owner started it by writing, or it delivers a report, so it may ask
|
|
79
|
+
/** The owner started it by writing, or it delivers a report, so it may ask them on cards. */
|
|
80
80
|
interactive?: boolean;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -17,8 +17,8 @@ import type { Agent } from "./agent-store.ts";
|
|
|
17
17
|
import { GroupTurns } from "./group-turns.ts";
|
|
18
18
|
import {
|
|
19
19
|
channelIdOf,
|
|
20
|
-
channelKey,
|
|
21
20
|
channelOwner,
|
|
21
|
+
discordKey,
|
|
22
22
|
homeScope,
|
|
23
23
|
} from "./team-keys.ts";
|
|
24
24
|
import {
|
|
@@ -83,7 +83,7 @@ export class TeamTurns {
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* The owner's message in an agent's channel; call inside the channel's queue. `text`
|
|
86
|
-
* carries the reference, `replyText` is what
|
|
86
|
+
* carries the reference, `replyText` is what they typed.
|
|
87
87
|
*/
|
|
88
88
|
async answerOwner(
|
|
89
89
|
channel: ChannelKey,
|
|
@@ -168,6 +168,7 @@ export class TeamTurns {
|
|
|
168
168
|
this.#active(agent.name, scope.group);
|
|
169
169
|
const turn = {
|
|
170
170
|
agent: agent.name,
|
|
171
|
+
kind: "agent",
|
|
171
172
|
channel: postTo,
|
|
172
173
|
speaker: chain.speaker,
|
|
173
174
|
...(scope.group ? { group: scope.group } : {}),
|
|
@@ -267,7 +268,7 @@ export class TeamTurns {
|
|
|
267
268
|
|
|
268
269
|
/**
|
|
269
270
|
* Posts text under the caller's name in the channel of its turn: the group's in a group
|
|
270
|
-
* round, otherwise its own, as a change report is
|
|
271
|
+
* round, otherwise its own, as a change report is.
|
|
271
272
|
*/
|
|
272
273
|
async postAs(caller: AgentTurnScope, text: string): Promise<void> {
|
|
273
274
|
const agent = this.#options.store.activeAgent(caller.name);
|
|
@@ -281,7 +282,7 @@ export class TeamTurns {
|
|
|
281
282
|
const group = scope.group
|
|
282
283
|
? this.#options.store.group(scope.group)
|
|
283
284
|
: undefined;
|
|
284
|
-
return group ?
|
|
285
|
+
return group ? discordKey(group.channelId) : scope.home;
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
/** The assistant's own notice, such as a new release, posted in the coordinator's channel under its name. */
|
|
@@ -290,7 +291,7 @@ export class TeamTurns {
|
|
|
290
291
|
const coordinator = store.agentByChannel(entryChannelId);
|
|
291
292
|
if (!coordinator)
|
|
292
293
|
throw new AgentError("no active agent in the entry channel");
|
|
293
|
-
await this.#post(
|
|
294
|
+
await this.#post(discordKey(entryChannelId), coordinator, {
|
|
294
295
|
chunks: splitReply(text),
|
|
295
296
|
});
|
|
296
297
|
}
|