pi-roundtable 0.23.1 → 0.24.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 +30 -0
- package/docs/plugins.md +39 -6
- package/package.json +1 -1
- package/src/characters/compose.ts +18 -3
- package/src/characters/index.ts +1 -0
- package/src/characters/parts-hats.ts +12 -12
- package/src/characters/parts-outfits.ts +12 -12
- package/src/characters/parts-props.ts +3 -3
- package/src/characters/skins.ts +36 -0
- package/src/cli/checks/discord.ts +4 -1
- package/src/core/agents/agent-store.ts +22 -0
- package/src/core/agents/agent-team.ts +5 -0
- package/src/core/agents/avatar-refresh.ts +77 -0
- package/src/core/agents/avatar-studio.ts +13 -2
- package/src/core/agents/team-lifecycle.ts +27 -2
- package/src/core/agents/team-options.ts +6 -0
- package/src/core/builtin/agent-server.ts +14 -0
- package/src/core/config/adapters.ts +8 -5
- package/src/core/config/agent-team.ts +36 -0
- package/src/core/config/config-schema.ts +138 -0
- package/src/core/config/config.ts +64 -133
- package/src/core/config/schema.ts +18 -0
- package/src/core/define-roundtable.ts +15 -7
- package/src/core/host-registration.ts +25 -0
- package/src/core/host.ts +3 -0
- package/src/core/plugin-removed.ts +85 -0
- package/src/core/plugin.ts +14 -77
- package/src/core/services.ts +21 -2
- package/src/core/shared/workspace-files.ts +7 -4
- package/src/index.ts +1 -0
- package/src/kit/index.ts +1 -0
- package/src/kit/workspace.ts +3 -0
- package/src/testing.ts +2 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CharacterSpec } from "../../characters/index.ts";
|
|
2
|
+
import type { Logger } from "../log.ts";
|
|
3
|
+
import type { Agent } from "./agent-store.ts";
|
|
4
|
+
|
|
5
|
+
/** What the refresh needs of the team: the agents, a render, and a way to set the picture. */
|
|
6
|
+
export interface AvatarRefreshDeps {
|
|
7
|
+
agents(): readonly Agent[];
|
|
8
|
+
/** Renders a look to its picture and returns the content hash; the same look gives the same hash. */
|
|
9
|
+
render(character: CharacterSpec): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Sets the picture only while the agent is active and still wears `character`; false when it
|
|
12
|
+
* does not, which leaves the newer look's picture alone.
|
|
13
|
+
*/
|
|
14
|
+
setAvatar(
|
|
15
|
+
name: string,
|
|
16
|
+
character: CharacterSpec,
|
|
17
|
+
avatarHash: string,
|
|
18
|
+
): Promise<boolean>;
|
|
19
|
+
/** Set when the team stops: no further render, write or log happens. */
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
logger: Logger;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AvatarRefreshResult {
|
|
25
|
+
rendered: number;
|
|
26
|
+
unchanged: number;
|
|
27
|
+
/** Archived, or given another look, while it rendered. */
|
|
28
|
+
skipped: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders every active agent that has a character again and sets the picture when its hash
|
|
34
|
+
* differs from the stored one, so a restart with nothing changed writes nothing. One agent's
|
|
35
|
+
* failure is counted and does not stop the others. An agent archived or given another look while
|
|
36
|
+
* it rendered is skipped, so the newer picture stays. Stops at once when the signal aborts, with
|
|
37
|
+
* no further write or log. Logs one summary line.
|
|
38
|
+
*/
|
|
39
|
+
export async function refreshAvatars(
|
|
40
|
+
deps: AvatarRefreshDeps,
|
|
41
|
+
): Promise<AvatarRefreshResult> {
|
|
42
|
+
const { agents, render, setAvatar, signal, logger } = deps;
|
|
43
|
+
const result: AvatarRefreshResult = {
|
|
44
|
+
rendered: 0,
|
|
45
|
+
unchanged: 0,
|
|
46
|
+
skipped: 0,
|
|
47
|
+
failed: 0,
|
|
48
|
+
};
|
|
49
|
+
const reasons: string[] = [];
|
|
50
|
+
for (const agent of agents()) {
|
|
51
|
+
if (signal?.aborted) return result;
|
|
52
|
+
if (agent.status !== "active" || !agent.character) continue;
|
|
53
|
+
try {
|
|
54
|
+
const avatarHash = await render(agent.character);
|
|
55
|
+
if (signal?.aborted) return result;
|
|
56
|
+
if (avatarHash === agent.avatarHash) {
|
|
57
|
+
result.unchanged++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (await setAvatar(agent.name, agent.character, avatarHash))
|
|
61
|
+
result.rendered++;
|
|
62
|
+
else result.skipped++;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (signal?.aborted) return result;
|
|
65
|
+
result.failed++;
|
|
66
|
+
reasons.push(
|
|
67
|
+
`${agent.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (signal?.aborted) return result;
|
|
72
|
+
const summary = { ...result, ...(reasons.length > 0 ? { reasons } : {}) };
|
|
73
|
+
const line = `avatars refreshed: re-rendered ${result.rendered}, unchanged ${result.unchanged}, skipped ${result.skipped}, failed ${result.failed}`;
|
|
74
|
+
if (result.failed > 0) logger.warn(summary, line);
|
|
75
|
+
else logger.info(summary, line);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import type { CharacterSpec } from "../../characters/index.ts";
|
|
4
|
+
import type { CharacterSpec, Hex } from "../../characters/index.ts";
|
|
5
5
|
import { resolveCharacter } from "../../characters/index.ts";
|
|
6
6
|
import { squarePng } from "../attachments/square-png.ts";
|
|
7
7
|
import type { ImageDrawer } from "../contract/providers.ts";
|
|
@@ -35,6 +35,8 @@ export interface AvatarStudioOptions {
|
|
|
35
35
|
neutralPath?: string;
|
|
36
36
|
/** Told the error code of a reference that was replaced by `neutralPath`. */
|
|
37
37
|
onReferenceFailed?: (code: string) => void;
|
|
38
|
+
/** The skin tones the pictures are drawn with; default the package's own. */
|
|
39
|
+
skins?: readonly Hex[];
|
|
38
40
|
/** Rasterizes a character to a PNG; canvas with librsvg by default. A seam for tests. */
|
|
39
41
|
rasterize?: typeof characterPng;
|
|
40
42
|
/** Draws the pictures. Without one, the studio only makes each agent a picture from its name. */
|
|
@@ -59,6 +61,12 @@ export class FileAvatarStudio implements AvatarStudio {
|
|
|
59
61
|
mkdirSync(options.dir, { recursive: true });
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/** The host's skin tones as a compose option; nothing when it has none, which draws the package's own. */
|
|
65
|
+
#skins(): { skins?: readonly Hex[] } {
|
|
66
|
+
const { skins } = this.#options;
|
|
67
|
+
return skins ? { skins } : {};
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
/**
|
|
63
71
|
* Stores the assistant's neutral avatar, the picture of an agent that has none yet. First
|
|
64
72
|
* rasterizes a probe character: the agents' pictures need canvas with SVG support (librsvg),
|
|
@@ -68,6 +76,7 @@ export class FileAvatarStudio implements AvatarStudio {
|
|
|
68
76
|
try {
|
|
69
77
|
await this.#rasterize(resolveCharacter({ class: "wizard" }), {
|
|
70
78
|
size: 32,
|
|
79
|
+
...this.#skins(),
|
|
71
80
|
});
|
|
72
81
|
} catch (error) {
|
|
73
82
|
throw new ConfigError(
|
|
@@ -111,7 +120,9 @@ export class FileAvatarStudio implements AvatarStudio {
|
|
|
111
120
|
|
|
112
121
|
/** Renders a character as the agent's picture and stores it by content hash; the same spec gives the same hash. */
|
|
113
122
|
async render(spec: CharacterSpec): Promise<string> {
|
|
114
|
-
return this.#keep(
|
|
123
|
+
return this.#keep(
|
|
124
|
+
await this.#rasterize(spec, { size: AVATAR_SIDE, ...this.#skins() }),
|
|
125
|
+
);
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
/**
|
|
@@ -9,6 +9,7 @@ import type { AgentTurnScope } from "../domain/ports.ts";
|
|
|
9
9
|
import type { AgentChannels } from "./agent-ports.ts";
|
|
10
10
|
import { overBase } from "./agent-rules.ts";
|
|
11
11
|
import type { Agent, AgentGroup } from "./agent-store.ts";
|
|
12
|
+
import { refreshAvatars } from "./avatar-refresh.ts";
|
|
12
13
|
import {
|
|
13
14
|
agentOfChannel,
|
|
14
15
|
coordinatorOf,
|
|
@@ -27,17 +28,24 @@ import type { TeamContext } from "./team-options.ts";
|
|
|
27
28
|
/** The team's startup, fresh starts, and archiving of agents and groups. */
|
|
28
29
|
export class TeamLifecycle {
|
|
29
30
|
readonly #ctx: TeamContext;
|
|
31
|
+
readonly #stopped = new AbortController();
|
|
30
32
|
|
|
31
33
|
constructor(ctx: TeamContext) {
|
|
32
34
|
this.#ctx = ctx;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/** Ends the background avatar refresh: nothing is rendered, written or logged after it. */
|
|
38
|
+
stop(): void {
|
|
39
|
+
this.#stopped.abort();
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
// ── Startup ────────────────────────────────────────────────────────────
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* Inserts the seed agents, gives every active agent a channel, archives agents and groups
|
|
39
46
|
* whose channels were deleted while the assistant was down, and dresses agents without a
|
|
40
|
-
* character in the background
|
|
47
|
+
* character in the background, then, with `refreshAvatars`, renders every agent's picture
|
|
48
|
+
* again. Without Discord channels the agents live in rooms, which need no setup, so
|
|
41
49
|
* none is made or checked.
|
|
42
50
|
*/
|
|
43
51
|
async start(): Promise<void> {
|
|
@@ -46,8 +54,25 @@ export class TeamLifecycle {
|
|
|
46
54
|
if (added.length > 0) logger.info({ added }, "seed agents added");
|
|
47
55
|
if (channels) await this.#startChannels(channels);
|
|
48
56
|
this.#ctx.changed();
|
|
49
|
-
void this.#dressMissing().then((dressed) => {
|
|
57
|
+
void this.#dressMissing().then(async (dressed) => {
|
|
58
|
+
if (this.#stopped.signal.aborted) return;
|
|
50
59
|
if (dressed) this.#ctx.changed();
|
|
60
|
+
if (!this.#ctx.options.refreshAvatars) return;
|
|
61
|
+
// Never throws: a failure is counted per agent, and the start has already finished.
|
|
62
|
+
const { store, studio, logger } = this.#ctx.options;
|
|
63
|
+
const result = await refreshAvatars({
|
|
64
|
+
agents: () => store.agents(),
|
|
65
|
+
render: (character) => studio.render(character),
|
|
66
|
+
setAvatar: (name, character, avatarHash) =>
|
|
67
|
+
store.setAvatarIfLook(name, character, avatarHash),
|
|
68
|
+
signal: this.#stopped.signal,
|
|
69
|
+
logger,
|
|
70
|
+
}).catch((error: unknown) => {
|
|
71
|
+
logger.warn({ err: error }, "avatars not refreshed");
|
|
72
|
+
return undefined;
|
|
73
|
+
});
|
|
74
|
+
if (result && !this.#stopped.signal.aborted && result.rendered > 0)
|
|
75
|
+
this.#ctx.changed();
|
|
51
76
|
});
|
|
52
77
|
}
|
|
53
78
|
|
|
@@ -31,6 +31,12 @@ export interface AgentTeamOptions
|
|
|
31
31
|
*/
|
|
32
32
|
studio: Pick<AvatarStudio, "url" | "render" | "draw" | "edit"> &
|
|
33
33
|
Partial<Pick<AvatarStudio, "canDraw">>;
|
|
34
|
+
/**
|
|
35
|
+
* Render every active agent that has a character again when the team starts, and set the
|
|
36
|
+
* picture of those whose hash changed; default false. Runs in the background after the agents
|
|
37
|
+
* are dressed.
|
|
38
|
+
*/
|
|
39
|
+
refreshAvatars?: boolean;
|
|
34
40
|
/** The tools and groups the plugins give every agent, read before each agent turn. */
|
|
35
41
|
pluginSelection: () => ToolSelection;
|
|
36
42
|
models: AgentModels;
|
|
@@ -101,6 +101,11 @@ export interface AgentServerOptions {
|
|
|
101
101
|
* `/avatars/<hash>.png`, which the surface that serves the pictures resolves against its origin.
|
|
102
102
|
*/
|
|
103
103
|
avatarUrl?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Render every agent's picture again at start (`characters.refreshAvatars`) and set it where
|
|
106
|
+
* its hash differs from the stored one; default false.
|
|
107
|
+
*/
|
|
108
|
+
refreshAvatars?: boolean;
|
|
104
109
|
/** The assistant's neutral avatar: the style reference and the picture of an agent without one. */
|
|
105
110
|
avatarReference: string;
|
|
106
111
|
/** The picture used instead when `avatarReference` cannot be read as one; the failure is logged. */
|
|
@@ -202,6 +207,7 @@ export function agentServerPlugin(
|
|
|
202
207
|
if (!connection) hostsRooms = () => surfaces.roomSurfaces().length > 0;
|
|
203
208
|
const studio = new FileAvatarStudio({
|
|
204
209
|
dir: join(options.dataDir, "avatars"),
|
|
210
|
+
skins: context.env.characterSkins,
|
|
205
211
|
...(options.avatarUrl ? { publicUrl: options.avatarUrl } : {}),
|
|
206
212
|
referencePath: options.avatarReference,
|
|
207
213
|
...(options.avatarNeutral
|
|
@@ -255,6 +261,7 @@ export function agentServerPlugin(
|
|
|
255
261
|
? { channels: connection.agentChannels(options.guildId) }
|
|
256
262
|
: {}),
|
|
257
263
|
studio,
|
|
264
|
+
...(options.refreshAvatars ? { refreshAvatars: true } : {}),
|
|
258
265
|
runtime: () => running,
|
|
259
266
|
confirmations,
|
|
260
267
|
scorer: new RelevanceScorer(judge, logger, options.owner),
|
|
@@ -313,6 +320,12 @@ export function agentServerPlugin(
|
|
|
313
320
|
runtime: running,
|
|
314
321
|
approvals: confirmations,
|
|
315
322
|
avatars: studio,
|
|
323
|
+
workspace: {
|
|
324
|
+
workDir: options.workDir,
|
|
325
|
+
...(agentSessions.scratchDir
|
|
326
|
+
? { scratchDir: agentSessions.scratchDir }
|
|
327
|
+
: {}),
|
|
328
|
+
},
|
|
316
329
|
});
|
|
317
330
|
const attachmentDir = (channel: ChannelKey) =>
|
|
318
331
|
ownerAttachmentDir(options.dataDir, channel);
|
|
@@ -348,6 +361,7 @@ export function agentServerPlugin(
|
|
|
348
361
|
: []),
|
|
349
362
|
{
|
|
350
363
|
name: AGENT_TEAM_SERVICE,
|
|
364
|
+
stop: () => built.stop(),
|
|
351
365
|
// The dashboard shows the team, so it starts once the team has; the error
|
|
352
366
|
// reporter connects whether or not the team did, since it reports the failure.
|
|
353
367
|
startInBackground: async () => {
|
|
@@ -18,8 +18,8 @@ export interface DiscordConfig {
|
|
|
18
18
|
token: string;
|
|
19
19
|
/** The agent server's guild id. */
|
|
20
20
|
guild: string;
|
|
21
|
-
/** The channel the coordinator lives in. */
|
|
22
|
-
entryChannel
|
|
21
|
+
/** The channel the coordinator lives in. Required unless the configuration says `agentServer: false`, which has no coordinator. */
|
|
22
|
+
entryChannel?: string;
|
|
23
23
|
/** The root slash command, without the slash; default the lowercase assistant name. */
|
|
24
24
|
rootCommand?: string;
|
|
25
25
|
/**
|
|
@@ -92,7 +92,8 @@ export interface DiscordConfig {
|
|
|
92
92
|
export interface ResolvedDiscord {
|
|
93
93
|
token: string;
|
|
94
94
|
guild: string;
|
|
95
|
-
|
|
95
|
+
/** Undefined only with `agentServer: false`. */
|
|
96
|
+
entryChannel?: string;
|
|
96
97
|
rootCommand: string;
|
|
97
98
|
admin: boolean;
|
|
98
99
|
refusalHint?: string;
|
|
@@ -117,7 +118,9 @@ export function resolvedDiscord(
|
|
|
117
118
|
return {
|
|
118
119
|
token: discord.token,
|
|
119
120
|
guild: discord.guild,
|
|
120
|
-
|
|
121
|
+
...(discord.entryChannel === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { entryChannel: discord.entryChannel }),
|
|
121
124
|
rootCommand,
|
|
122
125
|
admin: discord.admin ?? true,
|
|
123
126
|
...(discord.refusalHint === undefined
|
|
@@ -170,7 +173,7 @@ const channelContextShape = shape({
|
|
|
170
173
|
export const discordShape = shape({
|
|
171
174
|
token: text,
|
|
172
175
|
guild: text,
|
|
173
|
-
entryChannel: text,
|
|
176
|
+
entryChannel: optional(text),
|
|
174
177
|
rootCommand: optional(text),
|
|
175
178
|
admin: optional(bool),
|
|
176
179
|
refusalHint: optional(text),
|
|
@@ -41,3 +41,39 @@ export function checkAgentTeamKeys(
|
|
|
41
41
|
"config skills: the skills are the agents', and this host has none: without discord they need agents. Configure agents or discord, or remove skills.",
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/** The keys of the configuration, as written, that only the agent server uses. */
|
|
46
|
+
interface AgentOnlyKeys {
|
|
47
|
+
agents?: readonly unknown[];
|
|
48
|
+
skills?: unknown;
|
|
49
|
+
ops?: { agent?: string; conversation?: string };
|
|
50
|
+
discord?: {
|
|
51
|
+
agentMemory?: unknown;
|
|
52
|
+
agentSpeakers?: unknown;
|
|
53
|
+
attachmentDownload?: unknown;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Refuses, for `agentServer: false`, what only the agent server uses, as the configuration
|
|
59
|
+
* writes it (not as defaults fill it): the seeds need the server to create them. An empty `agents`
|
|
60
|
+
* list and `skills: false` say nothing, so they pass.
|
|
61
|
+
*/
|
|
62
|
+
export function checkAgentServerOff(config: AgentOnlyKeys): void {
|
|
63
|
+
const used: [string, boolean][] = [
|
|
64
|
+
["agents", (config.agents?.length ?? 0) > 0],
|
|
65
|
+
["skills", config.skills !== undefined && config.skills !== false],
|
|
66
|
+
["ops.agent", config.ops?.agent !== undefined],
|
|
67
|
+
["discord.agentMemory", config.discord?.agentMemory !== undefined],
|
|
68
|
+
["discord.agentSpeakers", config.discord?.agentSpeakers !== undefined],
|
|
69
|
+
[
|
|
70
|
+
"discord.attachmentDownload",
|
|
71
|
+
config.discord?.attachmentDownload !== undefined,
|
|
72
|
+
],
|
|
73
|
+
];
|
|
74
|
+
const found = used.find(([, set]) => set);
|
|
75
|
+
if (found)
|
|
76
|
+
throw new ConfigError(
|
|
77
|
+
`config ${found[0]}: only the agent server uses it, and agentServer is false. Remove ${found[0]}, or remove agentServer to keep the agent server.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { InterimTextMode } from "../domain/interim.ts";
|
|
2
|
+
import { isLocale, type Locale } from "../i18n/index.ts";
|
|
3
|
+
import { type EffortPolicy, THINKING_LEVELS } from "../models.ts";
|
|
4
|
+
import type { RoundtablePlugin } from "../plugin.ts";
|
|
5
|
+
import type { Tier } from "../speakers.ts";
|
|
6
|
+
import { accessShape } from "./access.ts";
|
|
7
|
+
import { discordShape } from "./adapters.ts";
|
|
8
|
+
import { attachmentsShape } from "./attachments.ts";
|
|
9
|
+
import type { Pronouns } from "./config.ts";
|
|
10
|
+
import { limitsShape } from "./limits.ts";
|
|
11
|
+
import {
|
|
12
|
+
bool,
|
|
13
|
+
characterInput,
|
|
14
|
+
guarded,
|
|
15
|
+
integer,
|
|
16
|
+
list,
|
|
17
|
+
number,
|
|
18
|
+
oneOf,
|
|
19
|
+
optional,
|
|
20
|
+
record,
|
|
21
|
+
shape,
|
|
22
|
+
skinTones,
|
|
23
|
+
text,
|
|
24
|
+
} from "./schema.ts";
|
|
25
|
+
import { skillsField } from "./skills-config.ts";
|
|
26
|
+
|
|
27
|
+
/** The shape `roundtable.config.ts` is checked against: every key, its type and its bounds. */
|
|
28
|
+
const tierMembers = shape({
|
|
29
|
+
users: optional(list(text)),
|
|
30
|
+
roles: optional(list(text)),
|
|
31
|
+
everyone: optional(bool),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const isPolicy = (value: unknown): value is EffortPolicy =>
|
|
35
|
+
typeof value === "function";
|
|
36
|
+
const isPlugin = (value: unknown): value is RoundtablePlugin =>
|
|
37
|
+
typeof value === "object" &&
|
|
38
|
+
value !== null &&
|
|
39
|
+
typeof (value as { name?: unknown }).name === "string" &&
|
|
40
|
+
typeof (value as { setup?: unknown }).setup === "function";
|
|
41
|
+
|
|
42
|
+
export const configSchema = shape({
|
|
43
|
+
name: optional(text),
|
|
44
|
+
owner: optional(
|
|
45
|
+
shape({
|
|
46
|
+
id: text,
|
|
47
|
+
name: text,
|
|
48
|
+
pronouns: optional(oneOf<Pronouns>("he", "she", "they")),
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
discord: optional(discordShape),
|
|
52
|
+
agentServer: optional(
|
|
53
|
+
guarded(
|
|
54
|
+
"false, to leave the agent server out (leave the key out to keep it)",
|
|
55
|
+
(value): value is false => value === false,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
database: shape({ url: text }),
|
|
59
|
+
dataDir: text,
|
|
60
|
+
attachments: attachmentsShape,
|
|
61
|
+
agentDir: optional(text),
|
|
62
|
+
model: text,
|
|
63
|
+
thinking: optional(oneOf(...THINKING_LEVELS)),
|
|
64
|
+
judge: optional(
|
|
65
|
+
shape({ model: optional(text), threshold: optional(number(0, 1)) }),
|
|
66
|
+
),
|
|
67
|
+
effortPolicy: optional(
|
|
68
|
+
guarded("a function from a turn to its effort rule", isPolicy),
|
|
69
|
+
),
|
|
70
|
+
delegation: optional(
|
|
71
|
+
shape({
|
|
72
|
+
model: optional(text),
|
|
73
|
+
thinking: optional(oneOf(...THINKING_LEVELS)),
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
locale: optional(
|
|
77
|
+
guarded("a locale, en or zh-TW", (value): value is Locale =>
|
|
78
|
+
isLocale(value),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
timeZone: optional(text),
|
|
82
|
+
speakers: optional(
|
|
83
|
+
shape({ admins: optional(tierMembers), members: optional(tierMembers) }),
|
|
84
|
+
),
|
|
85
|
+
access: optional(accessShape),
|
|
86
|
+
background: optional(
|
|
87
|
+
shape({
|
|
88
|
+
perPrincipal: optional(
|
|
89
|
+
shape({
|
|
90
|
+
schedules: optional(integer(1, 10_000)),
|
|
91
|
+
delegations: optional(integer(1, 1_000)),
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
toolTiers: optional(record(oneOf<Tier>("owner", "admin", "member"))),
|
|
97
|
+
prompts: optional(shape({ shared: text, guest: optional(text) })),
|
|
98
|
+
agents: optional(
|
|
99
|
+
list(
|
|
100
|
+
shape({
|
|
101
|
+
name: text,
|
|
102
|
+
displayName: text,
|
|
103
|
+
prompt: text,
|
|
104
|
+
character: optional(characterInput),
|
|
105
|
+
avatarPrompt: optional(text),
|
|
106
|
+
channelId: optional(text),
|
|
107
|
+
}),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
coordinator: optional(text),
|
|
111
|
+
characters: optional(
|
|
112
|
+
shape({ skins: optional(skinTones), refreshAvatars: optional(bool) }),
|
|
113
|
+
),
|
|
114
|
+
http: optional(
|
|
115
|
+
shape({
|
|
116
|
+
publicUrl: optional(text),
|
|
117
|
+
port: optional(integer(1, 65535)),
|
|
118
|
+
hostname: optional(text),
|
|
119
|
+
socketPath: optional(text),
|
|
120
|
+
socketMode: optional(integer(0, 0o777)),
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
avatar: optional(text),
|
|
124
|
+
workDir: optional(text),
|
|
125
|
+
scratchDir: optional(text),
|
|
126
|
+
skills: optional(skillsField),
|
|
127
|
+
memory: optional(bool),
|
|
128
|
+
ops: optional(shape({ agent: optional(text), conversation: optional(text) })),
|
|
129
|
+
drainSeconds: optional(integer(1, 3_600)),
|
|
130
|
+
...limitsShape,
|
|
131
|
+
interimText: optional(oneOf<InterimTextMode>("on", "off")),
|
|
132
|
+
interimPrimaryChars: optional(integer(1, 100_000)),
|
|
133
|
+
plugins: optional(
|
|
134
|
+
list(
|
|
135
|
+
guarded("a plugin, an object with a name and a setup function", isPlugin),
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
});
|