pi-roundtable 0.22.5 → 0.23.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 +61 -1
- package/README.md +35 -9
- package/README.zh-TW.md +51 -13
- package/docs/migrating-0.8.md +2 -2
- package/docs/plugins.md +351 -28
- package/examples/discord-adapter.ts +9 -1
- package/examples/seeds.test.ts +2 -2
- package/examples/seeds.ts +1 -2
- package/package.json +6 -2
- package/src/characters/catalog.ts +1055 -0
- package/src/characters/characters.css +68 -0
- package/src/characters/colors.ts +108 -0
- package/src/characters/compose.ts +281 -0
- package/src/characters/css.ts +63 -0
- package/src/characters/index.ts +33 -0
- package/src/characters/parse.ts +196 -0
- package/src/characters/parts-face.ts +354 -0
- package/src/characters/parts-hair.ts +56 -0
- package/src/characters/parts-hats.ts +57 -0
- package/src/characters/parts-outfits.ts +81 -0
- package/src/characters/parts-props.ts +137 -0
- package/src/characters/spec.ts +224 -0
- package/src/characters/suggest.ts +101 -0
- package/src/cli/checks/characters.ts +23 -0
- package/src/cli/checks/images.ts +5 -3
- package/src/cli/doctor.ts +2 -0
- package/src/core/agents/agent-claim.ts +30 -11
- package/src/core/agents/agent-dashboard.ts +1 -1
- package/src/core/agents/agent-messages.ts +43 -15
- package/src/core/agents/agent-prompt.ts +2 -4
- package/src/core/agents/agent-rules.ts +42 -3
- package/src/core/agents/agent-schema.ts +37 -15
- package/src/core/agents/agent-store.ts +57 -13
- package/src/core/agents/agent-team-fixture.ts +90 -14
- package/src/core/agents/agent-team.ts +83 -50
- package/src/core/agents/agent-tools.ts +94 -41
- package/src/core/agents/avatar-studio.ts +40 -4
- package/src/core/agents/character-png.ts +40 -0
- package/src/core/agents/group-messages.ts +7 -4
- package/src/core/agents/group-turns.ts +27 -14
- package/src/core/agents/team-editing.ts +99 -58
- package/src/core/agents/team-keys.ts +177 -11
- package/src/core/agents/team-lifecycle.ts +151 -84
- package/src/core/agents/team-options.ts +13 -6
- package/src/core/agents/team-status.ts +31 -13
- package/src/core/agents/team-text.ts +13 -3
- package/src/core/agents/team-turn-types.ts +25 -7
- package/src/core/agents/team-turns.ts +143 -28
- package/src/core/attachments/attachment-fetcher.ts +162 -64
- package/src/core/attachments/prompt-block.ts +7 -1
- package/src/core/attachments/tool-attachment.ts +20 -1
- package/src/core/attachments/turn-attachments.ts +18 -2
- package/src/core/builtin/agent-server.ts +103 -40
- package/src/core/builtin/discord.ts +4 -17
- package/src/core/builtin/modules.ts +6 -5
- package/src/core/builtin/threads.ts +37 -0
- package/src/core/config/agent-team.ts +43 -0
- package/src/core/config/config.ts +50 -59
- package/src/core/config/limits.ts +93 -0
- package/src/core/config/ops.ts +34 -0
- package/src/core/config/schema.ts +26 -0
- package/src/core/contract/channels.ts +20 -0
- package/src/core/contract/surface.ts +77 -7
- package/src/core/define-roundtable.ts +70 -42
- package/src/core/discord/agent-commands.ts +108 -86
- package/src/core/discord/agent-panel.ts +11 -2
- package/src/core/discord/connection.ts +1 -1
- package/src/core/discord/discord-surface.ts +10 -1
- package/src/core/discord/dispatch-thread-host.ts +1 -1
- package/src/core/domain/conversation.ts +24 -0
- package/src/core/holds.ts +221 -15
- package/src/core/host-registration.ts +2 -1
- package/src/core/http/listeners.ts +63 -3
- package/src/core/i18n/agent-panel.ts +14 -21
- package/src/core/interactions/prompts.ts +12 -0
- package/src/core/modes/autonomy.ts +23 -0
- package/src/core/modes/conversation-modes.ts +213 -0
- package/src/core/modes/modes-plugin.ts +24 -0
- package/src/core/modules/delegation/delegator.ts +4 -4
- package/src/core/modules/host-shell/shell-arithmetic.ts +81 -0
- package/src/core/modules/host-shell/shell-lexer-types.ts +53 -0
- package/src/core/modules/host-shell/shell-lexer.ts +493 -0
- package/src/core/modules/host-shell/shell-paths.ts +85 -4
- package/src/core/modules/host-shell/shell-policy.ts +341 -332
- package/src/core/modules/host-shell/shell-programs.ts +298 -0
- package/src/core/modules/host-shell/shell-quoting.ts +237 -0
- package/src/core/modules/host-shell/shell-scripts.ts +114 -0
- package/src/core/modules/host-shell/shell-wrappers.ts +104 -0
- package/src/core/modules/schedules/precheck-tools.ts +17 -0
- package/src/core/modules/schedules/schedules.ts +9 -3
- package/src/core/plugin.ts +8 -1
- package/src/core/presence/direct-channels.ts +30 -0
- package/src/core/presentation/reply-body.ts +25 -0
- package/src/core/registry/contributions.ts +31 -7
- package/src/core/registry/providers.ts +1 -1
- package/src/core/routing/conversation-turns.ts +2 -15
- package/src/core/routing/surface-port.ts +5 -1
- package/src/core/runtime/extensions/approval-card.ts +1 -5
- package/src/core/runtime/extensions/approval-fields.ts +3 -7
- package/src/core/runtime/extensions/confirmation-gate-extension.ts +13 -2
- package/src/core/runtime/extensions/confirmation-gate.ts +41 -40
- package/src/core/runtime/extensions/gate-refusals.ts +23 -0
- package/src/core/runtime/extensions/held-calls.ts +16 -1
- package/src/core/runtime/extensions/hold-describer.ts +58 -8
- package/src/core/runtime/extensions/hold-lookups.ts +19 -19
- package/src/core/runtime/extensions/yolo.ts +105 -0
- package/src/core/runtime/pi-agent-runtime.ts +3 -5
- package/src/core/runtime/runtime-plugin.ts +42 -1
- package/src/core/runtime/runtime-types.ts +11 -0
- package/src/core/runtime/turn-budget.ts +32 -5
- package/src/core/runtime/turn-limits.ts +14 -3
- package/src/core/services.ts +61 -9
- package/src/core/shared/attachment-reader.ts +6 -0
- package/src/core/shared/unix-server.ts +10 -1
- package/src/core/testing/modules.ts +12 -8
- package/src/core/testing/surface-threads.ts +58 -0
- package/src/core/testing/thread-host.ts +17 -5
- package/src/core/threads/dispatch-threads.ts +299 -0
- package/src/core/threads/host-threads.ts +19 -0
- package/src/core/tool-set.snapshot.json +5 -0
- package/src/index.ts +27 -1
- package/src/kit/characters.ts +5 -0
- package/src/kit/index.ts +2 -0
- package/src/kit/shell.ts +1 -0
- package/src/kit/support.ts +1 -1
- package/src/kit/threads.ts +1 -1
- package/src/testing.ts +4 -0
- package/templates/agents.ts +1 -1
- package/src/core/discord/dispatch-threads.ts +0 -194
|
@@ -4,10 +4,13 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { SQL } from "bun";
|
|
6
6
|
import type { ContextUse } from "../contract/runtime.ts";
|
|
7
|
+
import type { ChatSurface } from "../contract/surface.ts";
|
|
7
8
|
import { NO_ATTACHMENTS } from "../domain/attachment.ts";
|
|
8
9
|
import type {
|
|
9
10
|
ChannelKey,
|
|
11
|
+
OutboundReply,
|
|
10
12
|
PendingConfirmation,
|
|
13
|
+
PostAuthor,
|
|
11
14
|
TurnResult,
|
|
12
15
|
} from "../domain/conversation.ts";
|
|
13
16
|
import { AgentRunError } from "../domain/errors.ts";
|
|
@@ -36,7 +39,10 @@ import type {
|
|
|
36
39
|
} from "./agent-ports.ts";
|
|
37
40
|
import type { AgentSeed } from "./agent-rules.ts";
|
|
38
41
|
import { PgAgentStore as AgentStore } from "./agent-store.ts";
|
|
39
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
DiscordAgentTeam as AgentTeam,
|
|
44
|
+
type AgentTeamOptions,
|
|
45
|
+
} from "./agent-team.ts";
|
|
40
46
|
import { discordKey } from "./team-keys.ts";
|
|
41
47
|
|
|
42
48
|
export const ENTRY = "1000";
|
|
@@ -124,6 +130,50 @@ export class FakeChannels implements AgentChannels {
|
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
/**
|
|
134
|
+
* The surfaces a team replies through in rooms. `rooms` names the surfaces that host rooms and
|
|
135
|
+
* starts empty, so the Discord suites run as if no surface did; a room suite sets it.
|
|
136
|
+
*/
|
|
137
|
+
class FakeSurfaces {
|
|
138
|
+
rooms: string[] = [];
|
|
139
|
+
replies: { channel: ChannelKey; reply: OutboundReply }[] = [];
|
|
140
|
+
/** Interim posts as `channel name/agent: text`, edits included. */
|
|
141
|
+
interims: string[] = [];
|
|
142
|
+
roomSurfaces() {
|
|
143
|
+
return this.rooms;
|
|
144
|
+
}
|
|
145
|
+
/** A room's surface carries no files, as a surface that does not declare `supportsFiles`. */
|
|
146
|
+
of(_channel: ChannelKey): ChatSurface | undefined {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
async sendReply(channel: ChannelKey, reply: OutboundReply) {
|
|
150
|
+
this.replies.push({ channel, reply });
|
|
151
|
+
}
|
|
152
|
+
interim(channel: ChannelKey, as?: PostAuthor) {
|
|
153
|
+
if (!as) return undefined;
|
|
154
|
+
return {
|
|
155
|
+
post: async (text: string) => {
|
|
156
|
+
this.interims.push(`${channel} ${as.name}/${as.agent}: ${text}`);
|
|
157
|
+
return {
|
|
158
|
+
edit: async (change: string) => {
|
|
159
|
+
this.interims.push(
|
|
160
|
+
`${channel} ${as.name}/${as.agent}: edit ${change}`,
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** What the room was told, as `agent: text`. */
|
|
168
|
+
texts(channel: ChannelKey) {
|
|
169
|
+
return this.replies.flatMap((r) =>
|
|
170
|
+
r.channel === channel
|
|
171
|
+
? [`${r.reply.author?.agent}: ${r.reply.chunks.join("")}`]
|
|
172
|
+
: [],
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
127
177
|
/** Answers every turn with a scripted reply; records what each turn received. */
|
|
128
178
|
export class FakeRuntime implements AgentTurnRunner {
|
|
129
179
|
turns: TurnRequest[] = [];
|
|
@@ -166,10 +216,12 @@ export const HOOK_TIMEOUT_MS = 30_000;
|
|
|
166
216
|
// Runs against a real PostgreSQL, only when ROUNDTABLE_TEST_DATABASE_URL is set.
|
|
167
217
|
let store: TestStore<AgentStore>;
|
|
168
218
|
let channels: FakeChannels;
|
|
219
|
+
let surfaces: FakeSurfaces;
|
|
169
220
|
let runtime: FakeRuntime;
|
|
170
221
|
export interface TeamFixture {
|
|
171
222
|
store: TestStore<AgentStore>;
|
|
172
223
|
channels: FakeChannels;
|
|
224
|
+
surfaces: FakeSurfaces;
|
|
173
225
|
runtime: FakeRuntime;
|
|
174
226
|
queue: ChannelQueue;
|
|
175
227
|
team: AgentTeam;
|
|
@@ -206,29 +258,29 @@ export const eventLog: string[] = [];
|
|
|
206
258
|
const PLUGIN_SELECTION = { tools: ["memory_add"], groups: ["workspace"] };
|
|
207
259
|
|
|
208
260
|
/** The team a fresh database starts with: a coordinator in the entry channel, then two specialists. */
|
|
209
|
-
function seeds(entryChannelId: string): AgentSeed[] {
|
|
261
|
+
function seeds(entryChannelId: string | undefined): AgentSeed[] {
|
|
210
262
|
return [
|
|
211
263
|
{
|
|
212
264
|
name: "coordinator",
|
|
213
265
|
displayName: "Coordinator",
|
|
214
|
-
channelId: entryChannelId,
|
|
266
|
+
...(entryChannelId ? { channelId: entryChannelId } : {}),
|
|
215
267
|
prompt:
|
|
216
268
|
"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.",
|
|
217
|
-
|
|
269
|
+
character: { class: "navigator", palette: "amber" },
|
|
218
270
|
},
|
|
219
271
|
{
|
|
220
272
|
name: "doctor",
|
|
221
273
|
displayName: "Doctor",
|
|
222
274
|
prompt:
|
|
223
275
|
"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.",
|
|
224
|
-
|
|
276
|
+
character: { class: "healer", palette: "rose" },
|
|
225
277
|
},
|
|
226
278
|
{
|
|
227
279
|
name: "infra",
|
|
228
280
|
displayName: "Infra",
|
|
229
281
|
prompt:
|
|
230
282
|
"You manage the owner's servers. Check before you change: read state first, state what you will change and why, then act.",
|
|
231
|
-
|
|
283
|
+
character: { class: "engineer", palette: "purpure" },
|
|
232
284
|
},
|
|
233
285
|
];
|
|
234
286
|
}
|
|
@@ -247,8 +299,19 @@ export async function settle(): Promise<void> {
|
|
|
247
299
|
await Bun.sleep(20);
|
|
248
300
|
}
|
|
249
301
|
|
|
250
|
-
/**
|
|
251
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Registers the fixture's per-test setup in the describe it is called from. With `local`, the team
|
|
304
|
+
* runs as on a host without Discord: no channels and no entry channel, the coordinator named by
|
|
305
|
+
* the configuration, and the rooms of surface `app`.
|
|
306
|
+
*/
|
|
307
|
+
export function useTeamFixture(
|
|
308
|
+
options: {
|
|
309
|
+
local?: boolean;
|
|
310
|
+
/** The studio the team renders with, called each test; the default fakes pictures. */
|
|
311
|
+
studio?: () => AgentTeamOptions["studio"];
|
|
312
|
+
} = {},
|
|
313
|
+
): void {
|
|
314
|
+
const { local = false } = options;
|
|
252
315
|
beforeEach(async () => {
|
|
253
316
|
// The expected texts are the neutral English catalog's.
|
|
254
317
|
useTestLocale();
|
|
@@ -292,6 +355,8 @@ export function useTeamFixture(): void {
|
|
|
292
355
|
skills.init();
|
|
293
356
|
await skills.link("acme/kit", "skills");
|
|
294
357
|
channels = new FakeChannels();
|
|
358
|
+
surfaces = new FakeSurfaces();
|
|
359
|
+
if (local) surfaces.rooms = ["app"];
|
|
295
360
|
runtime = new FakeRuntime();
|
|
296
361
|
script.scores = undefined;
|
|
297
362
|
script.prompt = [];
|
|
@@ -304,13 +369,19 @@ export function useTeamFixture(): void {
|
|
|
304
369
|
owner: TEST_OWNER,
|
|
305
370
|
toolTiers: FIXTURE_TIERS,
|
|
306
371
|
shellUser: "mcops",
|
|
307
|
-
guildId: "1",
|
|
308
|
-
|
|
309
|
-
|
|
372
|
+
guildId: local ? "local" : "1",
|
|
373
|
+
...(local
|
|
374
|
+
? { coordinator: "coordinator" }
|
|
375
|
+
: { entryChannelId: ENTRY, channels }),
|
|
376
|
+
seeds: () => seeds(local ? undefined : ENTRY),
|
|
310
377
|
store,
|
|
311
|
-
|
|
312
|
-
studio: {
|
|
378
|
+
surfaces,
|
|
379
|
+
studio: options.studio?.() ?? {
|
|
313
380
|
url: (hash) => `https://x/avatars/${hash ?? "default"}.png`,
|
|
381
|
+
render: async (spec) =>
|
|
382
|
+
new Bun.CryptoHasher("sha256")
|
|
383
|
+
.update(JSON.stringify(spec))
|
|
384
|
+
.digest("hex"),
|
|
314
385
|
draw: async () => "a".repeat(64),
|
|
315
386
|
edit: async () => "b".repeat(64),
|
|
316
387
|
},
|
|
@@ -335,7 +406,11 @@ export function useTeamFixture(): void {
|
|
|
335
406
|
},
|
|
336
407
|
schedules: {
|
|
337
408
|
forChannel: async (channel) =>
|
|
338
|
-
channel === discordKey("2000")
|
|
409
|
+
channel === discordKey("2000")
|
|
410
|
+
? [{ id: 7 }]
|
|
411
|
+
: channel === "app:agent.doctor"
|
|
412
|
+
? [{ id: 8 }]
|
|
413
|
+
: [],
|
|
339
414
|
remove: async (id) => removedSchedules.push(id),
|
|
340
415
|
},
|
|
341
416
|
queue,
|
|
@@ -353,6 +428,7 @@ export function useTeamFixture(): void {
|
|
|
353
428
|
Object.assign(fx, {
|
|
354
429
|
store,
|
|
355
430
|
channels,
|
|
431
|
+
surfaces,
|
|
356
432
|
runtime,
|
|
357
433
|
queue,
|
|
358
434
|
team,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { CharacterInput } from "../../characters/index.ts";
|
|
2
3
|
import type { TurnAttachments } from "../domain/attachment.ts";
|
|
3
4
|
import type { ChannelKey, TurnResult } from "../domain/conversation.ts";
|
|
4
5
|
import { AgentError, ScheduleError } from "../domain/errors.ts";
|
|
@@ -6,7 +7,7 @@ import type { AgentTurnScope } from "../domain/ports.ts";
|
|
|
6
7
|
import type { ThinkingSetting } from "../models.ts";
|
|
7
8
|
import { SHELL_TOOLS } from "../modules/host-shell/shell-policy.ts";
|
|
8
9
|
import type { ResolvedSkill } from "../modules/skills/skill-rules.ts";
|
|
9
|
-
import type { AgentTeam } from "../services.ts";
|
|
10
|
+
import type { AgentChange, AgentTeam } from "../services.ts";
|
|
10
11
|
import type { TurnSelection } from "../sessions.ts";
|
|
11
12
|
import { type Speaker, THE_SPEAKER } from "../speakers.ts";
|
|
12
13
|
import { agentSystemPrompt } from "./agent-prompt.ts";
|
|
@@ -14,13 +15,20 @@ import type { Agent } from "./agent-store.ts";
|
|
|
14
15
|
import {
|
|
15
16
|
AGENT_TOOLS,
|
|
16
17
|
type AgentOps,
|
|
17
|
-
AVATAR_TOOL,
|
|
18
18
|
type AvatarMode,
|
|
19
19
|
agentToolsExtension,
|
|
20
20
|
MESSAGE_AGENT_TOOL,
|
|
21
21
|
} from "./agent-tools.ts";
|
|
22
22
|
import { TeamEditing } from "./team-editing.ts";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
agentRoomKey,
|
|
25
|
+
channelOwner,
|
|
26
|
+
coordinatorOf,
|
|
27
|
+
discordIdOf,
|
|
28
|
+
discordKey,
|
|
29
|
+
keysOf,
|
|
30
|
+
roomSurfaceOf,
|
|
31
|
+
} from "./team-keys.ts";
|
|
24
32
|
import {
|
|
25
33
|
layoutText,
|
|
26
34
|
planArrangement,
|
|
@@ -33,6 +41,7 @@ import {
|
|
|
33
41
|
agentDetails,
|
|
34
42
|
channelIdFromRef,
|
|
35
43
|
messageLine,
|
|
44
|
+
NO_CHANNELS,
|
|
36
45
|
teamListText,
|
|
37
46
|
} from "./team-text.ts";
|
|
38
47
|
import { TeamTurns } from "./team-turns.ts";
|
|
@@ -94,9 +103,36 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
94
103
|
return teamStatus(this.#context);
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* The agent or group that owns a channel of the agent server: by Discord channel, or by room on
|
|
108
|
+
* a surface that hosts rooms.
|
|
109
|
+
*/
|
|
98
110
|
owns(channel: ChannelKey): "agent" | "group" | undefined {
|
|
99
|
-
|
|
111
|
+
const { store, surfaces } = this.#options;
|
|
112
|
+
return channelOwner(
|
|
113
|
+
store,
|
|
114
|
+
channel,
|
|
115
|
+
discordIdOf(channel) === undefined
|
|
116
|
+
? (surfaces?.roomSurfaces() ?? [])
|
|
117
|
+
: [],
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The name of the coordinator: the agent in the entry channel, or, on a host without Discord,
|
|
123
|
+
* the one the configuration names; undefined while none is active.
|
|
124
|
+
*/
|
|
125
|
+
coordinator(): string | undefined {
|
|
126
|
+
return coordinatorOf(this.#options.store, this.#options)?.name;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Every key an agent or group is addressed by: its Discord channel when it has one, then its
|
|
131
|
+
* room on each surface that hosts rooms; empty for an unknown name.
|
|
132
|
+
*/
|
|
133
|
+
roomsOf(name: string): ChannelKey[] {
|
|
134
|
+
const { store, surfaces } = this.#options;
|
|
135
|
+
return keysOf(store, name, surfaces?.roomSurfaces() ?? []);
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
/** The tools every agent turn runs with: the plugins', plus the shell and agent tools. */
|
|
@@ -110,9 +146,7 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
110
146
|
...SHELL_TOOLS,
|
|
111
147
|
// Group members hand off by mentioning each other, so a group seat has no message_agent.
|
|
112
148
|
...AGENT_TOOLS.filter(
|
|
113
|
-
(tool) =>
|
|
114
|
-
!(scope?.group && tool === MESSAGE_AGENT_TOOL) &&
|
|
115
|
-
!(tool === AVATAR_TOOL && !this.#avatars()),
|
|
149
|
+
(tool) => !(scope?.group && tool === MESSAGE_AGENT_TOOL),
|
|
116
150
|
),
|
|
117
151
|
...(this.#options.pluginTools?.() ?? []),
|
|
118
152
|
]),
|
|
@@ -128,7 +162,6 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
128
162
|
workDir,
|
|
129
163
|
sharedPrompt,
|
|
130
164
|
guestPrompt,
|
|
131
|
-
entryChannelId,
|
|
132
165
|
owner,
|
|
133
166
|
shellUser,
|
|
134
167
|
scratchDir,
|
|
@@ -136,7 +169,7 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
136
169
|
const agent = store.agent(scope.name);
|
|
137
170
|
if (!agent) throw new AgentError(`no agent ${scope.name}`);
|
|
138
171
|
const group = scope.group ? store.group(scope.group) : undefined;
|
|
139
|
-
const coordinator = store
|
|
172
|
+
const coordinator = coordinatorOf(store, this.#options);
|
|
140
173
|
const speaker = this.#turns.speakerOf(scope);
|
|
141
174
|
const base = agentSystemPrompt({
|
|
142
175
|
agent,
|
|
@@ -148,8 +181,7 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
148
181
|
owner,
|
|
149
182
|
shellUser,
|
|
150
183
|
...(scratchDir ? { scratchDir } : {}),
|
|
151
|
-
|
|
152
|
-
images: this.#avatars(),
|
|
184
|
+
images: this.#images(),
|
|
153
185
|
...(group
|
|
154
186
|
? {
|
|
155
187
|
group: {
|
|
@@ -193,15 +225,14 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
193
225
|
return current ? [current, ...usable.filter((m) => m !== current)] : usable;
|
|
194
226
|
}
|
|
195
227
|
|
|
196
|
-
/** Whether agents can be offered
|
|
197
|
-
#
|
|
228
|
+
/** Whether agents can be offered generate_image: an image provider is configured. */
|
|
229
|
+
#images(): boolean {
|
|
198
230
|
return this.#options.studio.canDraw !== false;
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
/** The agent tools of one agent session. */
|
|
202
234
|
extension(scope: AgentTurnScope): ExtensionFactory {
|
|
203
235
|
return agentToolsExtension(this, scope, THE_SPEAKER, {
|
|
204
|
-
avatars: this.#avatars(),
|
|
205
236
|
skills: this.#options.skills !== undefined,
|
|
206
237
|
});
|
|
207
238
|
}
|
|
@@ -211,12 +242,23 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
211
242
|
return this.#options.skills?.carried(name).skills ?? [];
|
|
212
243
|
}
|
|
213
244
|
|
|
214
|
-
/**
|
|
215
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Another active agent's channel, for schedule_list: its room on the surface `near` is on when
|
|
247
|
+
* that surface hosts rooms, otherwise its Discord channel, otherwise its room on the first
|
|
248
|
+
* surface that hosts rooms.
|
|
249
|
+
*/
|
|
250
|
+
channelOf(name: string, near?: ChannelKey): ChannelKey {
|
|
216
251
|
try {
|
|
217
252
|
const agent = this.#options.store.activeAgent(name);
|
|
218
|
-
|
|
219
|
-
|
|
253
|
+
const rooms = this.#options.surfaces?.roomSurfaces() ?? [];
|
|
254
|
+
const surface = near === undefined ? undefined : roomSurfaceOf(near);
|
|
255
|
+
if (surface !== undefined && rooms.includes(surface))
|
|
256
|
+
return agentRoomKey(surface, name);
|
|
257
|
+
if (agent.channelId) return discordKey(agent.channelId);
|
|
258
|
+
const room = rooms[0];
|
|
259
|
+
if (room === undefined)
|
|
260
|
+
throw new AgentError(`${name} has no channel yet`);
|
|
261
|
+
return agentRoomKey(room, name);
|
|
220
262
|
} catch (error) {
|
|
221
263
|
throw new ScheduleError(
|
|
222
264
|
error instanceof Error ? error.message : String(error),
|
|
@@ -228,8 +270,8 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
228
270
|
|
|
229
271
|
/**
|
|
230
272
|
* Inserts the seed agents, gives every active agent a channel, archives agents and groups
|
|
231
|
-
* whose channels were deleted while the assistant was down, and
|
|
232
|
-
* background.
|
|
273
|
+
* whose channels were deleted while the assistant was down, and dresses agents without a
|
|
274
|
+
* character in the background.
|
|
233
275
|
*/
|
|
234
276
|
start(): Promise<void> {
|
|
235
277
|
return this.#lifecycle.start();
|
|
@@ -331,8 +373,10 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
331
373
|
around: string | undefined,
|
|
332
374
|
limit: number | undefined,
|
|
333
375
|
): Promise<string> {
|
|
376
|
+
const { channels } = this.#options;
|
|
377
|
+
if (!channels) throw new AgentError(NO_CHANNELS);
|
|
334
378
|
const id = channelIdFromRef(channel);
|
|
335
|
-
const messages = await
|
|
379
|
+
const messages = await channels.read(id, {
|
|
336
380
|
limit: Math.min(Math.max(limit ?? 20, 1), READ_LIMIT),
|
|
337
381
|
...(around ? { around } : {}),
|
|
338
382
|
});
|
|
@@ -344,12 +388,11 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
344
388
|
// ── AgentOps ───────────────────────────────────────────────────────────
|
|
345
389
|
|
|
346
390
|
async list(): Promise<string> {
|
|
347
|
-
return `${teamListText(this.#options.store)}\n\n${await this.#layoutText()}`;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async #layoutText(): Promise<string> {
|
|
351
391
|
const { store, channels } = this.#options;
|
|
352
|
-
|
|
392
|
+
const list = teamListText(store);
|
|
393
|
+
return channels
|
|
394
|
+
? `${list}\n\n${layoutText(await channels.layout(), store)}`
|
|
395
|
+
: list;
|
|
353
396
|
}
|
|
354
397
|
|
|
355
398
|
/**
|
|
@@ -361,6 +404,7 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
361
404
|
entries: { category: string; names: string[] }[],
|
|
362
405
|
): Promise<string> {
|
|
363
406
|
const { store, channels, logger } = this.#options;
|
|
407
|
+
if (!channels) throw new AgentError(NO_CHANNELS);
|
|
364
408
|
const listed = resolveArrangement(entries, store);
|
|
365
409
|
const { layout, remove } = planArrangement(listed, await channels.layout());
|
|
366
410
|
await channels.arrange(layout, remove);
|
|
@@ -372,7 +416,7 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
372
416
|
remove.length > 0
|
|
373
417
|
? ` ${remove.length} empty team categories were deleted.`
|
|
374
418
|
: "";
|
|
375
|
-
return `Arranged the channels.${removed}\n\n${await
|
|
419
|
+
return `Arranged the channels.${removed}\n\n${layoutText(await channels.layout(), store)}`;
|
|
376
420
|
}
|
|
377
421
|
|
|
378
422
|
get(name: string): string {
|
|
@@ -384,7 +428,6 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
384
428
|
agent,
|
|
385
429
|
models.defaults,
|
|
386
430
|
skills?.describeCarried(agent.name),
|
|
387
|
-
this.#avatars(),
|
|
388
431
|
);
|
|
389
432
|
}
|
|
390
433
|
|
|
@@ -394,38 +437,28 @@ export class DiscordAgentTeam implements AgentOps, AgentTeam {
|
|
|
394
437
|
* Changes an agent's display name, prompt, model, or thinking level; `default` returns the
|
|
395
438
|
* model or thinking level to the assistant's. A model must be one the host runs.
|
|
396
439
|
*/
|
|
397
|
-
update(
|
|
398
|
-
name: string,
|
|
399
|
-
change: {
|
|
400
|
-
displayName?: string;
|
|
401
|
-
prompt?: string;
|
|
402
|
-
model?: string;
|
|
403
|
-
thinking?: string;
|
|
404
|
-
},
|
|
405
|
-
): Promise<string> {
|
|
440
|
+
update(name: string, change: AgentChange): Promise<string> {
|
|
406
441
|
return this.#editing.update(name, change);
|
|
407
442
|
}
|
|
408
443
|
|
|
409
444
|
create(
|
|
410
445
|
caller: AgentTurnScope,
|
|
411
|
-
input:
|
|
412
|
-
name: string;
|
|
413
|
-
displayName: string;
|
|
414
|
-
prompt: string;
|
|
415
|
-
avatarPrompt?: string;
|
|
416
|
-
task: string;
|
|
417
|
-
category?: string;
|
|
418
|
-
skills?: string[];
|
|
419
|
-
},
|
|
446
|
+
input: Parameters<TeamEditing["create"]>[1],
|
|
420
447
|
): Promise<string> {
|
|
421
448
|
return this.#editing.create(caller, input);
|
|
422
449
|
}
|
|
423
450
|
|
|
424
|
-
|
|
425
|
-
|
|
451
|
+
/** Changes an agent's look; see `AgentTeam.dress`. */
|
|
452
|
+
dress(name: string, input: CharacterInput): Promise<Agent> {
|
|
453
|
+
return this.#editing.dress(name, input);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** The agent tool that changes a look; see `AgentTeam.dress`. */
|
|
457
|
+
avatar(name: string, look: CharacterInput): Promise<string> {
|
|
458
|
+
return this.#editing.avatar(name, look);
|
|
426
459
|
}
|
|
427
460
|
|
|
428
|
-
/**
|
|
461
|
+
/** @deprecated Use `dress`; removed in 1.0. Draws and stores a new picture. */
|
|
429
462
|
redrawAvatar(name: string, mode: AvatarMode, text?: string): Promise<Agent> {
|
|
430
463
|
return this.#editing.redrawAvatar(name, mode, text);
|
|
431
464
|
}
|