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
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
type ToolSelection,
|
|
20
20
|
type TransientTask,
|
|
21
21
|
} from "../sessions.ts";
|
|
22
|
+
import { activeToolsExtension } from "../shared/active-tools.ts";
|
|
22
23
|
import { packageDir } from "../shared/package-dir.ts";
|
|
23
|
-
import { profileToolsExtension } from "../shared/profile-tools.ts";
|
|
24
24
|
import { readAttachmentExtension } from "../shared/read-attachment-tool.ts";
|
|
25
25
|
import { addressee, type Speaker, THE_SPEAKER } from "../speakers.ts";
|
|
26
26
|
import {
|
|
@@ -83,6 +83,19 @@ export class SessionFactory {
|
|
|
83
83
|
return this.#linked;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The system prompt of a non-agent conversation of the kind: a plugin's persona, else for
|
|
88
|
+
* "owner" none. A kind nobody wrote a persona for is refused, never given the owner's.
|
|
89
|
+
*/
|
|
90
|
+
personaOf(kind: string): string {
|
|
91
|
+
const contributed = this.link().persona(kind);
|
|
92
|
+
if (contributed !== undefined) return contributed;
|
|
93
|
+
if (kind === "owner") return "";
|
|
94
|
+
throw new ConfigError(
|
|
95
|
+
`no persona is registered for the conversation kind "${kind}". A plugin adds one with \`personas: [{ kind: "${kind}", prompt() { ... } }]\`, or its claim must start conversations of a kind that has one.`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
get plan(): SessionPlan {
|
|
87
100
|
return this.link().plan;
|
|
88
101
|
}
|
|
@@ -196,8 +209,11 @@ export class SessionFactory {
|
|
|
196
209
|
slot: PromptSlot,
|
|
197
210
|
attachmentDir: string,
|
|
198
211
|
agent: AgentTurnScope | undefined,
|
|
212
|
+
kind: string,
|
|
199
213
|
): Promise<ChannelSession> {
|
|
200
|
-
const { agentDir, model, thinking,
|
|
214
|
+
const { agentDir, model, thinking, logger } = this.#options;
|
|
215
|
+
// An agent's prompt is set before each run; the other kinds are refused here, before a session is built.
|
|
216
|
+
const persona = agent ? undefined : this.personaOf(kind);
|
|
201
217
|
const skills = this.skillsOf(agent);
|
|
202
218
|
const state = {
|
|
203
219
|
tools: [] as readonly string[],
|
|
@@ -218,7 +234,7 @@ export class SessionFactory {
|
|
|
218
234
|
this.plan.compaction?.engine,
|
|
219
235
|
);
|
|
220
236
|
const context: SessionContext = {
|
|
221
|
-
kind: agent ? "agent" :
|
|
237
|
+
kind: agent ? "agent" : kind,
|
|
222
238
|
homeChannel: channel,
|
|
223
239
|
turnChannel: agent && agents ? agents.turnChannel(agent) : channel,
|
|
224
240
|
compaction: {
|
|
@@ -253,10 +269,10 @@ export class SessionFactory {
|
|
|
253
269
|
() => addressee(this.#deps.speaker(channel), this.#options.owner),
|
|
254
270
|
),
|
|
255
271
|
selfCompactGuard: selfCompactGuardExtension(),
|
|
256
|
-
|
|
272
|
+
activeTools: activeToolsExtension(() => state.tools),
|
|
257
273
|
}),
|
|
258
274
|
// An agent's prompt is set before each run by the agent-prompt extension.
|
|
259
|
-
appendSystemPrompt:
|
|
275
|
+
appendSystemPrompt: persona === undefined ? [] : [persona],
|
|
260
276
|
});
|
|
261
277
|
await resourceLoader.reload();
|
|
262
278
|
|
|
@@ -14,15 +14,13 @@ export interface TextToolDef<Name extends string = string> {
|
|
|
14
14
|
run(input: ToolInput, signal?: AbortSignal): Promise<string> | string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
type ErrorClass = abstract new (...args: never[]) => Error;
|
|
18
|
-
|
|
19
17
|
/**
|
|
20
18
|
* Registers text tools. A thrown `refusal` becomes an error result the model reads; any other
|
|
21
19
|
* error fails the call.
|
|
22
20
|
*/
|
|
23
21
|
export function textToolsExtension(
|
|
24
22
|
defs: readonly TextToolDef[],
|
|
25
|
-
refusal:
|
|
23
|
+
refusal: abstract new (...args: never[]) => Error,
|
|
26
24
|
): ExtensionFactory {
|
|
27
25
|
return (pi) => {
|
|
28
26
|
for (const def of defs) {
|
package/src/core/services.ts
CHANGED
|
@@ -1,107 +1,255 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import type { Agent, AgentGroup } from "./agents/agent-rules.ts";
|
|
2
|
+
import type { AvatarMode } from "./agents/agent-tools.ts";
|
|
3
|
+
import type { TeamStatus } from "./agents/team-status.ts";
|
|
4
|
+
import type { ScheduledOutcome } from "./contract/channels.ts";
|
|
5
|
+
import type { AgentRuntime } from "./contract/runtime.ts";
|
|
6
|
+
import { type ServiceKey, serviceKey } from "./contract/services.ts";
|
|
7
|
+
import type { PendingConfirmation } from "./domain/conversation.ts";
|
|
8
|
+
import type { HttpRoute } from "./http/listeners.ts";
|
|
9
|
+
import type { ThinkingSetting } from "./models.ts";
|
|
10
|
+
import type {
|
|
11
|
+
DelegationJob,
|
|
12
|
+
DelegationOutcome,
|
|
13
|
+
} from "./modules/delegation/delegator.ts";
|
|
14
|
+
import type {
|
|
15
|
+
Memory,
|
|
16
|
+
MemoryKind,
|
|
17
|
+
PromptMemory,
|
|
18
|
+
} from "./modules/memory/owner-memory-store.ts";
|
|
19
|
+
import type {
|
|
20
|
+
NewSchedule,
|
|
21
|
+
Schedule,
|
|
22
|
+
ScheduleChange,
|
|
23
|
+
} from "./modules/schedules/schedule-store.ts";
|
|
24
|
+
import type {
|
|
25
|
+
SkillCatalogEntry,
|
|
26
|
+
SkillSet,
|
|
27
|
+
} from "./modules/skills/skill-rules.ts";
|
|
28
|
+
import type { AgentTurnScope, ChannelKey } from "./sessions.ts";
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
memory: OwnerMemoryStore;
|
|
23
|
-
schedules: ScheduleStore;
|
|
24
|
-
/** Held actions, kept across a restart. */
|
|
25
|
-
confirmations: PendingConfirmationStore;
|
|
26
|
-
/** The agent server's agents and groups. */
|
|
27
|
-
agents: AgentStore;
|
|
28
|
-
skills: SkillStore;
|
|
29
|
-
}
|
|
30
|
+
// The services the built-in plugins provide, and the ports they keep to. Each port is an
|
|
31
|
+
// interface, so an object with the same methods satisfies it without being one of the built-in
|
|
32
|
+
// classes; a plugin replaces a built-in by providing its own under the same key.
|
|
30
33
|
|
|
31
|
-
/** The
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
/** The agent server: its team, its directory, the runtime that runs every agent turn. Provided by the agent-server plugin. */
|
|
35
|
+
export const AGENTS: ServiceKey<AgentServer> =
|
|
36
|
+
serviceKey<AgentServer>("roundtable.agents");
|
|
37
|
+
/** Scheduled turns, stored. Provided by the `schedule-store` plugin. */
|
|
38
|
+
export const SCHEDULES: ServiceKey<ScheduleStore> = serviceKey<ScheduleStore>(
|
|
39
|
+
"roundtable.schedules",
|
|
40
|
+
);
|
|
41
|
+
/** Turns nobody wrote: a due schedule's, a delegated task's report, a logged error's. Provided by the modules plugin. */
|
|
42
|
+
export const BACKGROUND_TURNS: ServiceKey<BackgroundTurns> =
|
|
43
|
+
serviceKey<BackgroundTurns>("roundtable.background-turns");
|
|
44
|
+
/** Tasks run in the background, each reporting back in its channel. Provided by the modules plugin. */
|
|
45
|
+
export const DELEGATION: ServiceKey<Delegator> = serviceKey<Delegator>(
|
|
46
|
+
"roundtable.delegation",
|
|
47
|
+
);
|
|
48
|
+
/**
|
|
49
|
+
* The remembered facts of each speaker. Provided by the `memory` plugin, an addon: read it with
|
|
50
|
+
* `find` when your plugin works without memory, and with `get` when it cannot.
|
|
51
|
+
*/
|
|
52
|
+
export const MEMORY: ServiceKey<MemoryStore> = serviceKey<MemoryStore>(
|
|
53
|
+
"roundtable.memory",
|
|
54
|
+
{
|
|
55
|
+
absent:
|
|
56
|
+
"The memory addon is switched off (config memory: false). Switch it on, or provide the service from a plugin of your own.",
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
/**
|
|
60
|
+
* The skills agents carry. Provided by the `skills` plugin, an addon: read it with `find` when
|
|
61
|
+
* your plugin works without skills, and with `get` when it cannot.
|
|
62
|
+
*/
|
|
63
|
+
export const SKILLS: ServiceKey<SkillRegistry> = serviceKey<SkillRegistry>(
|
|
64
|
+
"roundtable.skills",
|
|
65
|
+
{
|
|
66
|
+
absent:
|
|
67
|
+
"The skills addon is switched off (config skills: false). Switch it on, or provide the service from a plugin of your own.",
|
|
68
|
+
},
|
|
69
|
+
);
|
|
41
70
|
|
|
42
|
-
/** What the agent server builds. */
|
|
43
|
-
export interface
|
|
71
|
+
/** What the agent server builds, for the plugins set up after it. */
|
|
72
|
+
export interface AgentServer {
|
|
44
73
|
team: AgentTeam;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
|
|
74
|
+
/** Read-only lookups of agents and groups; edits go through the team. */
|
|
75
|
+
directory: AgentDirectory;
|
|
76
|
+
/** The runtime every agent-server turn runs on: the `runtime` provider's, or Pi's when no plugin fills the slot. */
|
|
77
|
+
runtime: AgentRuntime;
|
|
78
|
+
/** Decides whether the owner's reply approves the actions the assistant held for them. */
|
|
79
|
+
approvals: {
|
|
80
|
+
approves(pending: PendingConfirmation, reply: string): Promise<boolean>;
|
|
81
|
+
};
|
|
82
|
+
/** Agents' pictures. */
|
|
83
|
+
avatars: AvatarStudio;
|
|
49
84
|
}
|
|
50
85
|
|
|
51
|
-
/**
|
|
52
|
-
export interface
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
/** Agent pictures: drawn by an image provider, or made from the agent's name, kept and served by content hash. */
|
|
87
|
+
export interface AvatarStudio {
|
|
88
|
+
/** Whether an image provider is configured; without one, only `fallback` makes pictures. */
|
|
89
|
+
readonly canDraw: boolean;
|
|
90
|
+
/** The public URL of a picture, or of the default when there is none. */
|
|
91
|
+
url(hash: string | undefined): string;
|
|
92
|
+
/** Draws a new picture from an avatar prompt; returns its hash. */
|
|
93
|
+
draw(avatarPrompt: string): Promise<string>;
|
|
94
|
+
/** Edits a stored picture by an instruction; returns the new picture's hash. */
|
|
95
|
+
edit(hash: string | undefined, instruction: string): Promise<string>;
|
|
96
|
+
/** A picture made from an agent's display name and name, for a host without an image provider; returns its hash. */
|
|
97
|
+
fallback(displayName: string, name: string): Promise<string>;
|
|
98
|
+
/** The pictures, public on the named listener. */
|
|
99
|
+
route(listener: string): HttpRoute;
|
|
100
|
+
/** Serves `/avatars/<hash>.png`; undefined for any other path. */
|
|
101
|
+
serve(pathname: string): Response | undefined;
|
|
60
102
|
}
|
|
61
103
|
|
|
62
|
-
/**
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
/** The read half of the agent store: every inbound message asks, and only this process changes them. */
|
|
105
|
+
export interface AgentDirectory {
|
|
106
|
+
agents(): Agent[];
|
|
107
|
+
agent(name: string): Agent | undefined;
|
|
108
|
+
/** The agent, or throws AgentError when it is unknown or archived. */
|
|
109
|
+
activeAgent(name: string): Agent;
|
|
110
|
+
agentByChannel(channelId: string): Agent | undefined;
|
|
111
|
+
groups(): AgentGroup[];
|
|
112
|
+
group(name: string): AgentGroup | undefined;
|
|
113
|
+
groupByChannel(channelId: string): AgentGroup | undefined;
|
|
114
|
+
}
|
|
66
115
|
|
|
67
|
-
/**
|
|
68
|
-
export
|
|
69
|
-
|
|
116
|
+
/** What an agent's settings may change, as `update` takes it. */
|
|
117
|
+
export interface AgentChange {
|
|
118
|
+
displayName?: string;
|
|
119
|
+
prompt?: string;
|
|
120
|
+
model?: string;
|
|
121
|
+
thinking?: string;
|
|
122
|
+
}
|
|
70
123
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
124
|
+
/** The agent team as plugins use it: who is in it, what model they run, and their channels. */
|
|
125
|
+
export interface AgentTeam {
|
|
126
|
+
readonly guildId: string;
|
|
127
|
+
/** Called when a turn starts or ends and when an agent or group changes. */
|
|
128
|
+
onChange(listener: () => void): void;
|
|
129
|
+
/** Every active agent and group, with what each is doing now. */
|
|
130
|
+
status(): Promise<TeamStatus>;
|
|
131
|
+
/** The agent or group that owns a channel of the agent server. */
|
|
132
|
+
owns(channel: ChannelKey): "agent" | "group" | undefined;
|
|
133
|
+
/** The model and thinking setting of an agent's next run: its own, or the assistant's. */
|
|
134
|
+
modelOf(name: string): { model: string; thinking: ThinkingSetting };
|
|
135
|
+
/** The assistant's model and thinking setting, which agents without their own follow. */
|
|
136
|
+
defaultModel(): { model: string; thinking: ThinkingSetting };
|
|
137
|
+
/** Models an agent can be set to: every one the host runs, `current` first when given. */
|
|
138
|
+
usableModels(current?: string): Promise<string[]>;
|
|
139
|
+
/** An active agent's channel; throws ScheduleError for an unknown agent, as the schedule tools catch it. */
|
|
140
|
+
channelOf(name: string): ChannelKey;
|
|
141
|
+
/** The channel the scope's turns run in: the group's in a group round, otherwise its own. */
|
|
142
|
+
turnChannel(scope: AgentTurnScope): ChannelKey;
|
|
143
|
+
/** Posts text under the caller's name in the channel of its turn. */
|
|
144
|
+
postAs(caller: AgentTurnScope, text: string): Promise<void>;
|
|
145
|
+
/** The assistant's own notice, posted in the coordinator's channel under its name. */
|
|
146
|
+
announce(text: string): Promise<void>;
|
|
147
|
+
/** Changes an agent's display name, prompt, model, or thinking level; a model must be one the host runs. */
|
|
148
|
+
update(name: string, change: AgentChange): Promise<string>;
|
|
149
|
+
/** Draws and stores a new picture for an agent. */
|
|
150
|
+
redrawAvatar(name: string, mode: AvatarMode, text?: string): Promise<Agent>;
|
|
151
|
+
}
|
|
79
152
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
153
|
+
/** Scheduled turns, stored. A one-time schedule is deleted once it fires. */
|
|
154
|
+
export interface ScheduleStore {
|
|
155
|
+
create(schedule: NewSchedule): Promise<Schedule>;
|
|
156
|
+
get(id: number): Promise<Schedule | undefined>;
|
|
157
|
+
forChannel(channel: ChannelKey): Promise<Schedule[]>;
|
|
158
|
+
all(): Promise<Schedule[]>;
|
|
159
|
+
/** Applies a change to one of the channel's schedules; undefined when it has no such schedule. */
|
|
160
|
+
update(
|
|
161
|
+
channel: ChannelKey,
|
|
162
|
+
id: number,
|
|
163
|
+
change: ScheduleChange,
|
|
164
|
+
): Promise<Schedule | undefined>;
|
|
165
|
+
/** Deletes a schedule, only from the given channel when one is given. */
|
|
166
|
+
remove(id: number, channel?: ChannelKey): Promise<Schedule | undefined>;
|
|
167
|
+
due(now: Date): Promise<Schedule[]>;
|
|
168
|
+
/** Takes a due schedule before it runs: moves it to its next run, or deletes it when there is none; false when another claim won. */
|
|
169
|
+
claim(
|
|
170
|
+
schedule: Schedule,
|
|
171
|
+
next: Date | undefined,
|
|
172
|
+
now: Date,
|
|
173
|
+
): Promise<boolean>;
|
|
174
|
+
/** Records how a run ended. */
|
|
175
|
+
recordStatus(id: number, status: string): Promise<void>;
|
|
176
|
+
}
|
|
87
177
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
178
|
+
/** Turns nobody wrote, each answered in its channel by the claim that owns it. */
|
|
179
|
+
export interface BackgroundTurns {
|
|
180
|
+
/** A due schedule's turn, run as its creator's. */
|
|
181
|
+
runScheduled(schedule: Schedule, firedAt: Date): Promise<ScheduledOutcome>;
|
|
182
|
+
/** A delegated task's report, answered in its channel under the same rules as a schedule. */
|
|
183
|
+
runDelegated(job: DelegationJob, result: DelegationOutcome): Promise<void>;
|
|
184
|
+
/** The process's own logged error, reported to an agent in its channel as a report turn. */
|
|
185
|
+
runErrorReport(channel: ChannelKey, text: string): Promise<ScheduledOutcome>;
|
|
186
|
+
}
|
|
91
187
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
188
|
+
/** What a delegated task is asked with: its job, before the delegator numbers and times it. */
|
|
189
|
+
export type DelegationRequest = Omit<
|
|
190
|
+
DelegationJob,
|
|
191
|
+
"id" | "startedAt" | "thread"
|
|
192
|
+
>;
|
|
95
193
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
194
|
+
/** Tasks run in the background, each handing its result back to its channel. Jobs live in memory. */
|
|
195
|
+
export interface Delegator {
|
|
196
|
+
/** Starts a job and returns at once; throws DelegationError on bad input or a full channel. */
|
|
197
|
+
start(request: DelegationRequest): DelegationJob;
|
|
198
|
+
/** The channel of each job still running, one entry per job. */
|
|
199
|
+
runningChannels(): ChannelKey[];
|
|
200
|
+
/** Resolves once every started job has reported. */
|
|
201
|
+
idle(): Promise<void>;
|
|
202
|
+
}
|
|
99
203
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
204
|
+
/** Remembered facts, each speaker's own; every agent shares the memory of whoever is speaking. */
|
|
205
|
+
export interface MemoryStore {
|
|
206
|
+
/** One speaker's memory; it never reads or changes another's. */
|
|
207
|
+
forSpeaker(speakerId: string): SpeakerMemory;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** One speaker's remembered facts. */
|
|
211
|
+
export interface SpeakerMemory {
|
|
212
|
+
list(): Promise<Memory[]>;
|
|
213
|
+
/** What every turn carries: all core facts, and events that have not passed yet. */
|
|
214
|
+
forPrompt(today: string): Promise<PromptMemory>;
|
|
215
|
+
add(fact: string, kind?: MemoryKind, eventDate?: string): Promise<Memory>;
|
|
216
|
+
/** Memories containing any query term, most terms matched first, then newest first. */
|
|
217
|
+
search(query: string, limit?: number): Promise<Memory[]>;
|
|
218
|
+
/** Replaces one memory's text, kind, and date; undefined when the id is unknown. */
|
|
219
|
+
update(
|
|
220
|
+
id: number,
|
|
221
|
+
change: { fact: string; kind: MemoryKind; eventDate?: string },
|
|
222
|
+
): Promise<Memory | undefined>;
|
|
223
|
+
/** Deletes one memory; false when the id is unknown. */
|
|
224
|
+
removeById(id: number): Promise<boolean>;
|
|
225
|
+
/** Deletes every memory containing the text; returns the removed facts. */
|
|
226
|
+
remove(text: string): Promise<string[]>;
|
|
227
|
+
}
|
|
103
228
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
229
|
+
/** The skills agents carry: built in, linked from repositories, or written by an agent. */
|
|
230
|
+
export interface SkillRegistry {
|
|
231
|
+
/** The built-in skills plus the named registered ones, resolved to their files. */
|
|
232
|
+
resolve(names: readonly string[]): SkillSet;
|
|
233
|
+
/** What an agent carries, built-in skills included. */
|
|
234
|
+
carried(agent: string): SkillSet;
|
|
235
|
+
/** The registered skills an agent carries, as the dashboard lists them. */
|
|
236
|
+
carriedNames(agent: string): string[];
|
|
237
|
+
/** An agent's skills as `agent_get` shows them. */
|
|
238
|
+
describeCarried(agent: string): string;
|
|
239
|
+
/** Every skill, built-in ones first. */
|
|
240
|
+
catalog(): SkillCatalogEntry[];
|
|
241
|
+
/** Every skill, or those of a group or matching a query, as the `skill_list` tool shows them. */
|
|
242
|
+
list(filter?: { group?: string; query?: string }): string;
|
|
243
|
+
/** The skills linked from a managed repository. */
|
|
244
|
+
linkedFrom(repo: string): string[];
|
|
245
|
+
/** Throws AgentError when a name is not registered; built-in names are carried already. */
|
|
246
|
+
checkRegistered(names: readonly string[]): void;
|
|
247
|
+
/** Links the skill at a repository path, or every child folder with a SKILL.md. */
|
|
248
|
+
link(repo: string, path: string): Promise<string>;
|
|
249
|
+
/** Adds and removes an agent's registered skills together, or changes nothing; returns what it carries after. */
|
|
250
|
+
attach(
|
|
251
|
+
agent: string,
|
|
252
|
+
add: readonly string[],
|
|
253
|
+
remove: readonly string[],
|
|
254
|
+
): Promise<string[]>;
|
|
107
255
|
}
|
package/src/core/sessions.ts
CHANGED
|
@@ -43,7 +43,8 @@ export interface TransientTask {
|
|
|
43
43
|
|
|
44
44
|
/** What one Pi session is for, as a session tool's factory sees it. */
|
|
45
45
|
export interface SessionContext {
|
|
46
|
-
|
|
46
|
+
/** "agent" for an agent's session; otherwise the kind of conversation, which the claim that owns the channel names. */
|
|
47
|
+
kind: string;
|
|
47
48
|
/** Where the session's schedules and delegated reports belong: the agent's own channel, or the owner's. */
|
|
48
49
|
homeChannel: ChannelKey;
|
|
49
50
|
/** Where the session's turns run: a group's channel for an agent's seat in it, else the home channel. */
|
|
@@ -94,7 +95,7 @@ const CORE_EXTENSIONS = [
|
|
|
94
95
|
"confirmation-gate",
|
|
95
96
|
"ask-user",
|
|
96
97
|
"self-compact-guard",
|
|
97
|
-
"
|
|
98
|
+
"active-tools",
|
|
98
99
|
] as const;
|
|
99
100
|
|
|
100
101
|
/** Session tools grouped by phase, each group in registration order. */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pins each run's active tools to the
|
|
4
|
+
* Pins each run's active tools to the turn's selection. Inline factories load after path
|
|
5
5
|
* extensions, so this handler runs last and undoes additions such as pi-web-access
|
|
6
6
|
* re-adding web_enable or claude-bridge's AskClaude.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function activeToolsExtension(
|
|
9
9
|
currentTools: () => readonly string[],
|
|
10
10
|
): ExtensionFactory {
|
|
11
11
|
return (pi) => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { freeze } from "../freeze.ts";
|
|
2
3
|
|
|
3
|
-
/** Hands web research to the cheaper worker model;
|
|
4
|
+
/** Hands web research to the cheaper worker model; shared by conversation sessions. */
|
|
4
5
|
export const DELEGATE_TOOL = "delegate_task" as const;
|
|
5
6
|
|
|
6
|
-
export const DELEGATE_TOOL_SPEC = {
|
|
7
|
+
export const DELEGATE_TOOL_SPEC = freeze({
|
|
7
8
|
name: DELEGATE_TOOL,
|
|
8
9
|
label: "Delegate task",
|
|
9
10
|
description:
|
|
@@ -12,4 +13,4 @@ export const DELEGATE_TOOL_SPEC = {
|
|
|
12
13
|
title: Type.String({ description: "A short name for the task." }),
|
|
13
14
|
task: Type.String({ description: "The self-contained task." }),
|
|
14
15
|
}),
|
|
15
|
-
};
|
|
16
|
+
});
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { type TSchema, Type } from "typebox";
|
|
2
|
-
import {
|
|
3
|
-
import { timeZone } from "../time.ts";
|
|
2
|
+
import { catalogFor, type Locale } from "../i18n/index.ts";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Schedule tools,
|
|
5
|
+
* Schedule tools, shared by conversation sessions. Each acts on the schedules of
|
|
7
6
|
* the channel it runs in; the host decides the channel and speaker, never the model.
|
|
8
7
|
*/
|
|
9
|
-
export const SCHEDULE_TOOLS = [
|
|
8
|
+
export const SCHEDULE_TOOLS = Object.freeze([
|
|
10
9
|
"schedule_create",
|
|
11
10
|
"schedule_list",
|
|
12
11
|
"schedule_update",
|
|
13
12
|
"schedule_cancel",
|
|
14
|
-
] as const;
|
|
13
|
+
] as const);
|
|
15
14
|
export type ScheduleToolName = (typeof SCHEDULE_TOOLS)[number];
|
|
16
15
|
|
|
17
16
|
export function isScheduleTool(value: string): value is ScheduleToolName {
|
|
18
17
|
return (SCHEDULE_TOOLS as readonly string[]).includes(value);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const timing = () => ({
|
|
20
|
+
const timing = (zone: string) => ({
|
|
22
21
|
in_minutes: Type.Optional(
|
|
23
22
|
Type.Integer({
|
|
24
23
|
minimum: 1,
|
|
@@ -28,12 +27,12 @@ const timing = () => ({
|
|
|
28
27
|
),
|
|
29
28
|
at: Type.Optional(
|
|
30
29
|
Type.String({
|
|
31
|
-
description: `Run once at this ${
|
|
30
|
+
description: `Run once at this ${zone}, "YYYY-MM-DD HH:MM". Give one of in_minutes, at, or time.`,
|
|
32
31
|
}),
|
|
33
32
|
),
|
|
34
33
|
time: Type.Optional(
|
|
35
34
|
Type.String({
|
|
36
|
-
description: `Repeat at this ${
|
|
35
|
+
description: `Repeat at this ${zone} of day, "HH:MM".`,
|
|
37
36
|
}),
|
|
38
37
|
),
|
|
39
38
|
every_days: Type.Optional(
|
|
@@ -70,24 +69,34 @@ export interface ScheduleToolSpec {
|
|
|
70
69
|
parameters: TSchema;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
/** The wording of the tools' descriptions: a locale's catalog, and the zone the times are in. */
|
|
73
|
+
export interface ScheduleToolWording {
|
|
74
|
+
locale: Locale;
|
|
75
|
+
/** An IANA time zone, such as `Asia/Taipei`. */
|
|
76
|
+
timeZone: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function scheduleToolSpecs(
|
|
80
|
+
wording: ScheduleToolWording,
|
|
81
|
+
): readonly ScheduleToolSpec[] {
|
|
82
|
+
const zone = catalogFor(wording.locale).zoneTime(wording.timeZone);
|
|
74
83
|
return [
|
|
75
84
|
{
|
|
76
85
|
name: "schedule_create",
|
|
77
86
|
label: "Create schedule",
|
|
78
|
-
description: `Schedule a task for yourself in this channel: at the set ${
|
|
87
|
+
description: `Schedule a task for yourself in this channel: at the set ${zone} you are woken with the prompt and your answer is posted here. Use it for reminders, follow-ups, and recurring checks someone asks for. Write the prompt as a complete instruction to your future self, including who it is for and what to report; it runs without the current conversation in view.`,
|
|
79
88
|
parameters: Type.Object({
|
|
80
89
|
title: Type.String({ description: "A short name for the schedule." }),
|
|
81
90
|
prompt: Type.String({
|
|
82
91
|
description: "What to do when it runs, self-contained.",
|
|
83
92
|
}),
|
|
84
|
-
...timing(),
|
|
93
|
+
...timing(zone),
|
|
85
94
|
}),
|
|
86
95
|
},
|
|
87
96
|
{
|
|
88
97
|
name: "schedule_list",
|
|
89
98
|
label: "List schedules",
|
|
90
|
-
description: `List this channel's schedules with their next run, and the current ${
|
|
99
|
+
description: `List this channel's schedules with their next run, and the current ${zone}. Give id to read one schedule's full prompt.`,
|
|
91
100
|
parameters: Type.Object({
|
|
92
101
|
id: Type.Optional(Type.Integer({ description: "Schedule id." })),
|
|
93
102
|
}),
|
|
@@ -103,7 +112,7 @@ export function scheduleToolSpecs(): readonly ScheduleToolSpec[] {
|
|
|
103
112
|
prompt: Type.Optional(
|
|
104
113
|
Type.String({ description: "The whole new prompt." }),
|
|
105
114
|
),
|
|
106
|
-
...timing(),
|
|
115
|
+
...timing(zone),
|
|
107
116
|
}),
|
|
108
117
|
},
|
|
109
118
|
{
|
package/src/core/speakers.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { freeze } from "./freeze.ts";
|
|
2
2
|
import type { OwnerIdentity } from "./identity.ts";
|
|
3
3
|
|
|
4
4
|
/** How much a speaker may do: `owner` above `admin` above `member`. */
|
|
5
|
-
export const TIERS = ["member", "admin", "owner"] as const;
|
|
5
|
+
export const TIERS = Object.freeze(["member", "admin", "owner"] as const);
|
|
6
6
|
export type Tier = (typeof TIERS)[number];
|
|
7
7
|
|
|
8
8
|
/** Whether `tier` is `least` or above. */
|
|
@@ -42,14 +42,16 @@ export function addressee(
|
|
|
42
42
|
* the description is fixed when the session opens, so it names no one, and the prompt says who
|
|
43
43
|
* the speaker is.
|
|
44
44
|
*/
|
|
45
|
-
export const THE_SPEAKER: OwnerIdentity
|
|
45
|
+
export const THE_SPEAKER: Readonly<Omit<OwnerIdentity, "pronouns">> & {
|
|
46
|
+
readonly pronouns: Readonly<OwnerIdentity["pronouns"]>;
|
|
47
|
+
} = freeze({
|
|
46
48
|
name: "the speaker",
|
|
47
49
|
pronouns: {
|
|
48
50
|
subject: "the speaker",
|
|
49
51
|
object: "the speaker",
|
|
50
52
|
possessive: "the speaker's",
|
|
51
53
|
},
|
|
52
|
-
};
|
|
54
|
+
});
|
|
53
55
|
|
|
54
56
|
/** The turn's text under the speaker's name, so a conversation several speakers share says who wrote what. */
|
|
55
57
|
export function attributed(speaker: Speaker, text: string): string {
|
|
@@ -107,37 +109,3 @@ export function speakerPolicy(map: SpeakerMap): SpeakerPolicy {
|
|
|
107
109
|
},
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Reads a tier's members from text: comma-separated `user:<id>`, `role:<id>`, or `@everyone`;
|
|
113
|
-
* a bare id is a user. `allowEveryone` is false for tiers that cannot be everyone.
|
|
114
|
-
*/
|
|
115
|
-
export function parseTierMembers(
|
|
116
|
-
text: string | undefined,
|
|
117
|
-
name: string,
|
|
118
|
-
allowEveryone: boolean,
|
|
119
|
-
): TierMembers | undefined {
|
|
120
|
-
const items = (text ?? "")
|
|
121
|
-
.split(",")
|
|
122
|
-
.map((item) => item.trim())
|
|
123
|
-
.filter((item) => item !== "");
|
|
124
|
-
if (items.length === 0) return undefined;
|
|
125
|
-
const users: string[] = [];
|
|
126
|
-
const roles: string[] = [];
|
|
127
|
-
let everyone = false;
|
|
128
|
-
for (const item of items) {
|
|
129
|
-
if (item === "@everyone") {
|
|
130
|
-
if (!allowEveryone)
|
|
131
|
-
throw new ConfigError(`${name} cannot include @everyone`);
|
|
132
|
-
everyone = true;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const [kind, id] = item.includes(":") ? item.split(":", 2) : ["user", item];
|
|
136
|
-
if ((kind !== "user" && kind !== "role") || !/^\d{17,20}$/.test(id ?? ""))
|
|
137
|
-
throw new ConfigError(
|
|
138
|
-
`${name} must list user:<id>, role:<id>, or @everyone, got ${item}`,
|
|
139
|
-
);
|
|
140
|
-
(kind === "role" ? roles : users).push(id as string);
|
|
141
|
-
}
|
|
142
|
-
return { users, roles, everyone };
|
|
143
|
-
}
|