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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ConfigError } from "../domain/errors.ts";
|
|
2
|
+
import { type Autonomy, DEFAULT_LONG_RUNNING_MS } from "../modes/autonomy.ts";
|
|
3
|
+
import { integer, list, optional, shape, text } from "./schema.ts";
|
|
4
|
+
|
|
5
|
+
/** The configuration's `turnLimits` and `autonomy`: how long a turn works, and what runs without a person. */
|
|
6
|
+
export interface LimitsConfig {
|
|
7
|
+
/**
|
|
8
|
+
* How long one turn may work before it asks the owner on a card whether to go on, or is
|
|
9
|
+
* stopped when it cannot show a card. Time spent waiting on the owner's cards does not count.
|
|
10
|
+
*/
|
|
11
|
+
turnLimits?: {
|
|
12
|
+
/** The budget of a turn, renewed by each message steered into it; 60 to 86400, default 600 (10 min). */
|
|
13
|
+
timeoutSeconds?: number;
|
|
14
|
+
/** The most one turn works in all, however often it is steered; at least `timeoutSeconds`, default three times it. */
|
|
15
|
+
maxSeconds?: number;
|
|
16
|
+
/** How long the card that asks whether to go on waits for an answer, in wall time; 30 to 3600, default 300. */
|
|
17
|
+
cardSeconds?: number;
|
|
18
|
+
};
|
|
19
|
+
/** What a conversation may do without a person, once its owner switches the mode on. */
|
|
20
|
+
autonomy?: {
|
|
21
|
+
/** Yolo mode: the server approves held calls instead of asking, within these limits. */
|
|
22
|
+
yolo?: {
|
|
23
|
+
/**
|
|
24
|
+
* Hold classes yolo approves although it cards them by default, such as `shell.rm`, and the
|
|
25
|
+
* names of rules that report no classes, such as `tool:repo_push`, whose calls it then approves.
|
|
26
|
+
*/
|
|
27
|
+
allow?: readonly string[];
|
|
28
|
+
/** Hold classes or rule names yolo never approves, such as `shell.push` or `tool:repo_push`. */
|
|
29
|
+
card?: readonly string[];
|
|
30
|
+
};
|
|
31
|
+
/** Long-running mode: a turn that reaches its time limit goes on by itself, up to this. */
|
|
32
|
+
longRunning?: {
|
|
33
|
+
/** The most one turn works in all before it asks or stops; 600 to 604800, default 14400 (4 h). */
|
|
34
|
+
maxSeconds?: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The two keys' shapes, spread into the configuration's. */
|
|
40
|
+
export const limitsShape = {
|
|
41
|
+
turnLimits: optional(
|
|
42
|
+
shape({
|
|
43
|
+
timeoutSeconds: optional(integer(60, 86_400)),
|
|
44
|
+
maxSeconds: optional(integer(60, 604_800)),
|
|
45
|
+
cardSeconds: optional(integer(30, 3_600)),
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
autonomy: optional(
|
|
49
|
+
shape({
|
|
50
|
+
yolo: optional(
|
|
51
|
+
shape({ allow: optional(list(text)), card: optional(list(text)) }),
|
|
52
|
+
),
|
|
53
|
+
longRunning: optional(
|
|
54
|
+
shape({ maxSeconds: optional(integer(600, 604_800)) }),
|
|
55
|
+
),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** The limits with their defaults, in ms; `maxMs` is never below `timeoutMs`. */
|
|
61
|
+
export interface ResolvedLimits {
|
|
62
|
+
turnLimits: { timeoutMs: number; maxMs: number; cardMs: number };
|
|
63
|
+
autonomy: Autonomy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The configuration's limits with every default filled, refusing a cap below the timeout. */
|
|
67
|
+
export function limitsOf(config: LimitsConfig): ResolvedLimits {
|
|
68
|
+
const { turnLimits, autonomy } = config;
|
|
69
|
+
const timeoutSeconds = turnLimits?.timeoutSeconds ?? 600;
|
|
70
|
+
const maxSeconds = turnLimits?.maxSeconds ?? 3 * timeoutSeconds;
|
|
71
|
+
if (maxSeconds < timeoutSeconds)
|
|
72
|
+
throw new ConfigError(
|
|
73
|
+
`config turnLimits.maxSeconds: expected at least turnLimits.timeoutSeconds (${timeoutSeconds}), got ${maxSeconds}. Raise maxSeconds, or lower timeoutSeconds.`,
|
|
74
|
+
);
|
|
75
|
+
const longRunning = autonomy?.longRunning?.maxSeconds;
|
|
76
|
+
return {
|
|
77
|
+
turnLimits: {
|
|
78
|
+
timeoutMs: timeoutSeconds * 1_000,
|
|
79
|
+
maxMs: maxSeconds * 1_000,
|
|
80
|
+
cardMs: (turnLimits?.cardSeconds ?? 300) * 1_000,
|
|
81
|
+
},
|
|
82
|
+
autonomy: {
|
|
83
|
+
yolo: {
|
|
84
|
+
allow: autonomy?.yolo?.allow ?? [],
|
|
85
|
+
card: autonomy?.yolo?.card ?? [],
|
|
86
|
+
},
|
|
87
|
+
longRunningMaxMs:
|
|
88
|
+
longRunning === undefined
|
|
89
|
+
? DEFAULT_LONG_RUNNING_MS
|
|
90
|
+
: longRunning * 1_000,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ConfigError } from "../domain/errors.ts";
|
|
2
|
+
import type { ChannelKey } from "../sessions.ts";
|
|
3
|
+
|
|
4
|
+
/** Where the errors go: one agent, or one conversation. */
|
|
5
|
+
export type ResolvedOps = { agent: string } | { conversation: ChannelKey };
|
|
6
|
+
|
|
7
|
+
/** Where the errors go: one of an agent or a conversation; an agent only where the agents live. */
|
|
8
|
+
export function opsOf(
|
|
9
|
+
ops: { agent?: string; conversation?: string } | undefined,
|
|
10
|
+
withDiscord: boolean,
|
|
11
|
+
): ResolvedOps | undefined {
|
|
12
|
+
if (!ops) return undefined;
|
|
13
|
+
const { agent, conversation } = ops;
|
|
14
|
+
if (agent !== undefined && conversation !== undefined)
|
|
15
|
+
throw new ConfigError(
|
|
16
|
+
"config ops: name an agent or a conversation, not both. Keep the one the reports go to.",
|
|
17
|
+
);
|
|
18
|
+
if (agent !== undefined) {
|
|
19
|
+
if (!withDiscord)
|
|
20
|
+
throw new ConfigError(
|
|
21
|
+
'config ops.agent: the agents live in Discord, which is not configured. Report to a conversation a plugin\'s chat surface serves instead, with ops: { conversation: "<surface>:<id>" }; the web chat takes no error reports.',
|
|
22
|
+
);
|
|
23
|
+
return { agent };
|
|
24
|
+
}
|
|
25
|
+
if (conversation === undefined)
|
|
26
|
+
throw new ConfigError(
|
|
27
|
+
'config ops: name an agent or a conversation, such as ops: { agent: "infra" } or ops: { conversation: "<surface>:<id>" }.',
|
|
28
|
+
);
|
|
29
|
+
if (!/^[^:]+:.+$/.test(conversation))
|
|
30
|
+
throw new ConfigError(
|
|
31
|
+
`config ops.conversation: expected a conversation key <surface>:<id>, got ${JSON.stringify(conversation)}. Write the surface's prefix, a colon, and the conversation's id.`,
|
|
32
|
+
);
|
|
33
|
+
return { conversation: conversation as ChannelKey };
|
|
34
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CharacterError,
|
|
3
|
+
type CharacterInput,
|
|
4
|
+
resolveCharacter,
|
|
5
|
+
} from "../../characters/index.ts";
|
|
1
6
|
import { ConfigError } from "../domain/errors.ts";
|
|
2
7
|
import { nearest } from "../registry/contributions.ts";
|
|
3
8
|
|
|
@@ -129,6 +134,27 @@ export const guarded = <T>(
|
|
|
129
134
|
},
|
|
130
135
|
});
|
|
131
136
|
|
|
137
|
+
/** The fields of an agent's look, checked as `resolveCharacter` does; a bad field is named in the error. */
|
|
138
|
+
export const characterInput: Field<CharacterInput> = {
|
|
139
|
+
describe: "an object of character fields, such as { class: 'knight' }",
|
|
140
|
+
check: (value, path) => {
|
|
141
|
+
try {
|
|
142
|
+
// Over a base so that a look without `class` is checked too.
|
|
143
|
+
resolveCharacter(
|
|
144
|
+
value as CharacterInput,
|
|
145
|
+
resolveCharacter({ class: "wizard" }),
|
|
146
|
+
);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof CharacterError)
|
|
149
|
+
throw new ConfigError(
|
|
150
|
+
`config ${path}.${error.field}: ${error.message}. Fix the character in roundtable.config.ts.`,
|
|
151
|
+
);
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
return value as CharacterInput;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
132
158
|
/** `false`, or whatever `item` accepts: a feature that is switched off, or configured. */
|
|
133
159
|
export const orOff = <T>(item: Field<T>): Field<T | false> => ({
|
|
134
160
|
describe: `false, or ${item.describe}`,
|
|
@@ -12,6 +12,26 @@ export interface AttachmentRef {
|
|
|
12
12
|
size: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* A file on the host's disk that a surface hands the core in place of its bytes, such as an upload
|
|
17
|
+
* the surface stored. The core copies it into the conversation's attachment store as a stream and
|
|
18
|
+
* leaves the original where it is.
|
|
19
|
+
*/
|
|
20
|
+
export interface AttachmentFile {
|
|
21
|
+
/** Absolute path of a regular file the surface owns; a relative path, a symlink or another kind of file is refused with `EINVAL`. */
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What a surface hands the core for an attachment it made: the bytes, or, for a file too large to
|
|
27
|
+
* hold in memory, a file on disk or a stream the core stores as it comes. A file or stream is
|
|
28
|
+
* held to the surface's `maxAttachmentBytes`, and never buffered whole.
|
|
29
|
+
*/
|
|
30
|
+
export type AttachmentContent =
|
|
31
|
+
| Uint8Array
|
|
32
|
+
| AttachmentFile
|
|
33
|
+
| ReadableStream<Uint8Array>;
|
|
34
|
+
|
|
15
35
|
/** The facts a surface reports about one incoming message; it never decides who may talk. */
|
|
16
36
|
export interface InboundMessage {
|
|
17
37
|
channel: ChannelKey;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { OutboundReply } from "../domain/conversation.ts";
|
|
2
|
-
import type { InterimPosts } from "../domain/interim.ts";
|
|
1
|
+
import type { OutboundReply, PostAuthor } from "../domain/conversation.ts";
|
|
2
|
+
import type { InterimMessage, InterimPosts } from "../domain/interim.ts";
|
|
3
3
|
import type { TurnProgress } from "../domain/progress.ts";
|
|
4
4
|
import { PluginError } from "../errors.ts";
|
|
5
5
|
import type { PromptScope, Prompts } from "../interactions/prompts.ts";
|
|
6
6
|
import type { ChannelKey } from "../sessions.ts";
|
|
7
7
|
import type { Speaker } from "../speakers.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AttachmentContent,
|
|
10
|
+
AttachmentRef,
|
|
11
|
+
InboundMessage,
|
|
12
|
+
} from "./channels.ts";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Splits a channel key, `<surface>:<id>`, at its first colon: the surface names the chat network
|
|
@@ -33,6 +37,36 @@ export function channelKey(surface: string, id: string): ChannelKey {
|
|
|
33
37
|
return `${surface}:${id}`;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The threads a surface hosts for background dispatches (coding tasks, delegated tasks, messages
|
|
42
|
+
* between agents): a place beside a channel where one dispatch posts its progress and report. A
|
|
43
|
+
* thread's id is the surface's own and unique among its threads; the thread as a conversation
|
|
44
|
+
* channel is `channelKey(surface, id)`.
|
|
45
|
+
*/
|
|
46
|
+
export interface SurfaceThreads {
|
|
47
|
+
/**
|
|
48
|
+
* Posts the start line in `parent` and opens a thread from it. `line` gives the start line's
|
|
49
|
+
* text: called with no argument for the first post, and with the thread's `mention` once the
|
|
50
|
+
* thread exists, so a surface that can edit its post shows the link. Resolves to undefined when
|
|
51
|
+
* `parent` cannot host a thread, such as a direct message or a thread; rejects when the surface
|
|
52
|
+
* refuses. `mention` is how the thread is named in text, such as `<#id>` on Discord.
|
|
53
|
+
*/
|
|
54
|
+
open(
|
|
55
|
+
parent: ChannelKey,
|
|
56
|
+
name: string,
|
|
57
|
+
line: (mention?: string) => string,
|
|
58
|
+
): Promise<{ id: string; mention: string } | undefined>;
|
|
59
|
+
/** Posts text in the thread. */
|
|
60
|
+
post(thread: string, text: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Posts one message of at most 2000 characters and resolves to it, so it can be edited in
|
|
63
|
+
* place; without it a thread has no `interim` posts.
|
|
64
|
+
*/
|
|
65
|
+
send?(thread: string, text: string): Promise<InterimMessage>;
|
|
66
|
+
/** Archives the thread, and locks it when the surface allows. */
|
|
67
|
+
close(thread: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
36
70
|
/**
|
|
37
71
|
* One chat network the host talks through. The host ships Discord's; a plugin may contribute
|
|
38
72
|
* another with `surfaces`. A surface serves the channels whose key starts with its `surface`
|
|
@@ -43,6 +77,19 @@ export interface ChatSurface {
|
|
|
43
77
|
readonly surface: string;
|
|
44
78
|
/** Explicit opt-in to delivering OutboundReply.files; absent or false refuses turn attachments. */
|
|
45
79
|
readonly supportsFiles?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Serves a room for each agent and group of the agent team, `<surface>:agent.<name>` and
|
|
82
|
+
* `<surface>:group.<name>`: separate conversations from the agents' Discord channels, with
|
|
83
|
+
* their own session, held actions and group log, run by the same team. The team replies in a
|
|
84
|
+
* room through `sendReply` with `OutboundReply.author`. Absent or false = no rooms.
|
|
85
|
+
*/
|
|
86
|
+
readonly agentRooms?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Hosts dispatch threads: a background dispatch started in one of this surface's channels
|
|
89
|
+
* opens its thread here, and the thread is the conversation `<surface>:<id>`. Absent = the
|
|
90
|
+
* dispatch posts in the channel as before.
|
|
91
|
+
*/
|
|
92
|
+
readonly threads?: SurfaceThreads;
|
|
46
93
|
/**
|
|
47
94
|
* Short English guidance on how a reply renders here, such as "no Markdown tables", which the
|
|
48
95
|
* runtime appends to the system prompt of every turn that replies on this surface, after the
|
|
@@ -88,9 +135,30 @@ export interface ChatSurface {
|
|
|
88
135
|
prompts?(channel: ChannelKey, scope?: PromptScope): Prompts | undefined;
|
|
89
136
|
/**
|
|
90
137
|
* Where a running turn posts the text it writes before its final answer: ordinary messages it
|
|
91
|
-
* may edit in place.
|
|
138
|
+
* may edit in place. `as` names who speaks when an agent of the team does, as `author` does in
|
|
139
|
+
* `sendReply`. Absent, or undefined, = only the final reply is posted.
|
|
140
|
+
*/
|
|
141
|
+
interim?(channel: ChannelKey, as?: PostAuthor): InterimPosts | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* The content of an attachment this surface made, such as a file the owner uploaded to a room:
|
|
144
|
+
* its bytes, or, for a file too large to hold in memory, `{ path }` of a file on the host or a
|
|
145
|
+
* stream, which the core stores without buffering the whole. A ref of a message on this
|
|
146
|
+
* surface is read here and never fetched over the network; a rejection fails that file. Absent
|
|
147
|
+
* = the refs of its messages are downloaded from their URLs.
|
|
148
|
+
*/
|
|
149
|
+
readAttachment?(
|
|
150
|
+
channel: ChannelKey,
|
|
151
|
+
ref: AttachmentRef,
|
|
152
|
+
): Promise<AttachmentContent>;
|
|
153
|
+
/**
|
|
154
|
+
* The most bytes of one attachment a turn takes from this surface, for a ref, a file, a stream
|
|
155
|
+
* or a download alike; default 25 MiB (`MAX_ATTACHMENT_BYTES`), which Discord keeps. A larger
|
|
156
|
+
* file is refused as `larger than <n> MB`; a smaller value than the default is allowed.
|
|
157
|
+
* An image over 25 MiB is still not shown to the model, and a text or PDF file over 25 MiB is
|
|
158
|
+
* not read by `read_attachment`: both stay in the conversation's store, named in the turn's
|
|
159
|
+
* `## Attachments` block, for tools that take files.
|
|
92
160
|
*/
|
|
93
|
-
|
|
161
|
+
readonly maxAttachmentBytes?: number;
|
|
94
162
|
/**
|
|
95
163
|
* Shows what a running turn writes and which tools it runs, as it goes, such as a live preview
|
|
96
164
|
* in a web chat. Called between the turn's start and its reply; a rejection is logged and the
|
|
@@ -123,8 +191,10 @@ export interface SurfacePort {
|
|
|
123
191
|
channel: ChannelKey,
|
|
124
192
|
scope?: PromptScope | Speaker,
|
|
125
193
|
): Prompts | undefined;
|
|
126
|
-
/** The
|
|
127
|
-
|
|
194
|
+
/** The surfaces that serve agent rooms (`agentRooms: true`); read after link. */
|
|
195
|
+
roomSurfaces(): readonly string[];
|
|
196
|
+
/** The channel's interim posts, under `as` when given; undefined when its surface has none. */
|
|
197
|
+
interim(channel: ChannelKey, as?: PostAuthor): InterimPosts | undefined;
|
|
128
198
|
/**
|
|
129
199
|
* Shows a running turn's progress; a no-op when no surface serves the channel or its surface
|
|
130
200
|
* shows none. The host's port always has it; it is optional so a port written before 0.8 still
|
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { userInfo } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { agentServerPlugin } from "./builtin/agent-server.ts";
|
|
5
|
+
import { agentServerPlugin, LOCAL_TEAM_ID } from "./builtin/agent-server.ts";
|
|
6
6
|
import { discordPlugin } from "./builtin/discord.ts";
|
|
7
7
|
import { discordAdminPlugin } from "./builtin/discord-admin.ts";
|
|
8
8
|
import { modulesPlugin, schedulerPlugin } from "./builtin/modules.ts";
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
precheckPlugin,
|
|
14
14
|
scheduleStorePlugin,
|
|
15
15
|
} from "./builtin/stores.ts";
|
|
16
|
+
import { threadsPlugin } from "./builtin/threads.ts";
|
|
16
17
|
import { workerSessionsPlugin } from "./builtin/worker-sessions.ts";
|
|
17
18
|
import { warnDeprecations } from "./config/access.ts";
|
|
18
19
|
import {
|
|
@@ -29,6 +30,7 @@ import { identityPlugin } from "./identity/identity-plugin.ts";
|
|
|
29
30
|
import { type JudgeModel, piJudgeModel } from "./judging/model-judge.ts";
|
|
30
31
|
import { createLogger, type LogEntry, type Logger } from "./log.ts";
|
|
31
32
|
import { formatModelRef, type ModelRef } from "./models.ts";
|
|
33
|
+
import { modesPlugin } from "./modes/modes-plugin.ts";
|
|
32
34
|
import { ErrorReporter } from "./ops/error-reporter.ts";
|
|
33
35
|
import type { RoundtablePlugin } from "./plugin.ts";
|
|
34
36
|
import {
|
|
@@ -132,34 +134,17 @@ export function discordOwnerOf(
|
|
|
132
134
|
};
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
/** The Discord bot and
|
|
137
|
+
/** The Discord bot and its administration, each in its place in the plugin list. */
|
|
136
138
|
interface DiscordAssembly {
|
|
137
139
|
discord: RoundtablePlugin;
|
|
138
140
|
admin: RoundtablePlugin[];
|
|
139
|
-
skills: RoundtablePlugin[];
|
|
140
|
-
agentServer: RoundtablePlugin;
|
|
141
|
-
/** Absent when no agent is configured: a plugin that adds nothing is refused by the registry. */
|
|
142
|
-
seeds?: RoundtablePlugin;
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
function discordAssembly(
|
|
146
144
|
config: ResolvedConfig,
|
|
147
145
|
discord: NonNullable<ResolvedConfig["discord"]>,
|
|
148
|
-
shared: {
|
|
149
|
-
modelRuntime: ModelRuntime;
|
|
150
|
-
agentSessions: AgentSessionsSlot;
|
|
151
|
-
errorReporter: ErrorReporter | undefined;
|
|
152
|
-
},
|
|
153
146
|
): DiscordAssembly {
|
|
154
|
-
const { name } = config;
|
|
155
147
|
const owner = discordOwnerOf(config);
|
|
156
|
-
const sharedPrompt = config.prompts.shared
|
|
157
|
-
? readPrompt(config.prompts.shared, "prompts.shared")
|
|
158
|
-
: readPrompt(join(ASSETS, "prompts", "shared.md"), "prompts.shared");
|
|
159
|
-
const guest = config.prompts.guest
|
|
160
|
-
? readPrompt(config.prompts.guest, "prompts.guest")
|
|
161
|
-
: readPrompt(join(ASSETS, "prompts", "shared-guest.md"), "prompts.guest");
|
|
162
|
-
const { errorReporter } = shared;
|
|
163
148
|
return {
|
|
164
149
|
discord: discordPlugin({
|
|
165
150
|
token: discord.token,
|
|
@@ -181,10 +166,43 @@ function discordAssembly(
|
|
|
181
166
|
cardCopies: discord.cardCopies,
|
|
182
167
|
}),
|
|
183
168
|
admin: discord.admin ? [discordAdminPlugin({ owner })] : [],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The agent server and what only it uses, each in its place in the plugin list: the skills addon
|
|
174
|
+
* and the seed agents. Its team lives in the Discord guild when there is one, and otherwise in
|
|
175
|
+
* the rooms of the surfaces that host them.
|
|
176
|
+
*/
|
|
177
|
+
interface AgentAssembly {
|
|
178
|
+
skills: RoundtablePlugin[];
|
|
179
|
+
agentServer: RoundtablePlugin;
|
|
180
|
+
/** Absent when no agent is configured: a plugin that adds nothing is refused by the registry. */
|
|
181
|
+
seeds?: RoundtablePlugin;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function agentAssembly(
|
|
185
|
+
config: ResolvedConfig,
|
|
186
|
+
shared: {
|
|
187
|
+
modelRuntime: ModelRuntime;
|
|
188
|
+
agentSessions: AgentSessionsSlot;
|
|
189
|
+
errorReporter: ErrorReporter | undefined;
|
|
190
|
+
},
|
|
191
|
+
): AgentAssembly {
|
|
192
|
+
const { name, discord } = config;
|
|
193
|
+
const owner = discordOwnerOf(config);
|
|
194
|
+
const sharedPrompt = config.prompts.shared
|
|
195
|
+
? readPrompt(config.prompts.shared, "prompts.shared")
|
|
196
|
+
: readPrompt(join(ASSETS, "prompts", "shared.md"), "prompts.shared");
|
|
197
|
+
const guest = config.prompts.guest
|
|
198
|
+
? readPrompt(config.prompts.guest, "prompts.guest")
|
|
199
|
+
: readPrompt(join(ASSETS, "prompts", "shared-guest.md"), "prompts.guest");
|
|
200
|
+
const { errorReporter } = shared;
|
|
201
|
+
return {
|
|
184
202
|
skills: config.skills
|
|
185
203
|
? [
|
|
186
204
|
skillsPlugin({
|
|
187
|
-
guildId: discord
|
|
205
|
+
guildId: discord?.guild ?? LOCAL_TEAM_ID,
|
|
188
206
|
reposDir: config.skills.reposDir ?? join(config.dataDir, "repos"),
|
|
189
207
|
writtenDir: join(config.dataDir, "skills"),
|
|
190
208
|
builtinDir: config.skills.builtinDir ?? join(ASSETS, "skills"),
|
|
@@ -193,8 +211,11 @@ function discordAssembly(
|
|
|
193
211
|
]
|
|
194
212
|
: [],
|
|
195
213
|
agentServer: agentServerPlugin({
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
...(discord
|
|
215
|
+
? { guildId: discord.guild, entryChannelId: discord.entryChannel }
|
|
216
|
+
: config.coordinator
|
|
217
|
+
? { coordinator: config.coordinator }
|
|
218
|
+
: {}),
|
|
198
219
|
owner,
|
|
199
220
|
assistant: name,
|
|
200
221
|
modelRuntime: shared.modelRuntime,
|
|
@@ -206,15 +227,15 @@ function discordAssembly(
|
|
|
206
227
|
scratchDir: config.scratchDir,
|
|
207
228
|
shellUser: userInfo().username,
|
|
208
229
|
prompts: { shared: sharedPrompt, guest },
|
|
209
|
-
...(discord
|
|
210
|
-
...(discord
|
|
211
|
-
...(discord
|
|
230
|
+
...(discord?.agentMemory ? { memory: discord.agentMemory } : {}),
|
|
231
|
+
...(discord?.agentSpeakers ? { speakers: discord.agentSpeakers } : {}),
|
|
232
|
+
...(discord?.attachmentDownload
|
|
212
233
|
? { attachmentDownload: discord.attachmentDownload }
|
|
213
234
|
: {}),
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
avatarUrl: config.http
|
|
217
|
-
avatarReference:
|
|
235
|
+
// The pictures are public on the listener only where a public address reaches it.
|
|
236
|
+
...(config.http ? { avatarListener: "public" } : {}),
|
|
237
|
+
...(config.http?.publicUrl ? { avatarUrl: config.http.publicUrl } : {}),
|
|
238
|
+
avatarReference: join(ASSETS, "neutral.png"),
|
|
218
239
|
avatarNeutral: join(ASSETS, "neutral.png"),
|
|
219
240
|
// An ops agent's reports are the agent server's to deliver.
|
|
220
241
|
...(errorReporter && "agent" in errorReporter.destination
|
|
@@ -229,9 +250,10 @@ function discordAssembly(
|
|
|
229
250
|
* The host's options and the plugin list of a configured bot: the built-in plugins in their
|
|
230
251
|
* fixed order (each service one provides is read by the ones after it), then the operator's, then the scheduler, so a due schedule fires only once
|
|
231
252
|
* everything it reaches runs. The memory, Discord administration, and skills addons are left out
|
|
232
|
-
* when the configuration switches them off. Without `discord` the Discord plugins
|
|
233
|
-
* the agent server and its seeds
|
|
234
|
-
*
|
|
253
|
+
* when the configuration switches them off. Without `discord` the Discord plugins are left out,
|
|
254
|
+
* and the skills, the agent server and its seeds stay only when `agents` are configured: their
|
|
255
|
+
* team then lives in the rooms of the surfaces that host them, and the runtime still runs every
|
|
256
|
+
* turn of `context.turns`. Configuration mistakes stop here, naming the key and the fix.
|
|
235
257
|
*/
|
|
236
258
|
export async function defineRoundtable(
|
|
237
259
|
input: RoundtableConfig,
|
|
@@ -271,13 +293,12 @@ export async function defineRoundtable(
|
|
|
271
293
|
warnDeprecations(logger, config.deprecations);
|
|
272
294
|
// The agent server hands the runtime its per-agent settings once it sets up.
|
|
273
295
|
const agentSessions = agentSessionsSlot();
|
|
274
|
-
const assembly = discord
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
: undefined;
|
|
296
|
+
const assembly = discord ? discordAssembly(config, discord) : undefined;
|
|
297
|
+
// The agent server runs with Discord, and without it whenever agents are configured.
|
|
298
|
+
const agentTeam =
|
|
299
|
+
discord || config.agents.length > 0
|
|
300
|
+
? agentAssembly(config, { modelRuntime, agentSessions, errorReporter })
|
|
301
|
+
: undefined;
|
|
281
302
|
return {
|
|
282
303
|
options: {
|
|
283
304
|
logger,
|
|
@@ -309,6 +330,8 @@ export async function defineRoundtable(
|
|
|
309
330
|
...(config.memory ? [memoryPlugin({ owner })] : []),
|
|
310
331
|
scheduleStorePlugin(),
|
|
311
332
|
precheckPlugin(),
|
|
333
|
+
// Before Discord, whose services hand out its instance, and the modules and agent server that use it.
|
|
334
|
+
threadsPlugin({ dataDir: config.dataDir }),
|
|
312
335
|
...(assembly ? [assembly.discord] : []),
|
|
313
336
|
modulesPlugin({
|
|
314
337
|
owner,
|
|
@@ -330,8 +353,9 @@ export async function defineRoundtable(
|
|
|
330
353
|
delegation: config.delegation,
|
|
331
354
|
}),
|
|
332
355
|
...(assembly?.admin ?? []),
|
|
333
|
-
...(
|
|
356
|
+
...(agentTeam?.skills ?? []),
|
|
334
357
|
conversationsPlugin(),
|
|
358
|
+
modesPlugin(),
|
|
335
359
|
runtimePlugin({
|
|
336
360
|
owner,
|
|
337
361
|
modelRuntime,
|
|
@@ -344,11 +368,15 @@ export async function defineRoundtable(
|
|
|
344
368
|
agents: agentSessions,
|
|
345
369
|
interimText: config.interimText,
|
|
346
370
|
interimPrimaryChars: config.interimPrimaryChars,
|
|
371
|
+
turnTimeoutMs: config.turnLimits.timeoutMs,
|
|
372
|
+
turnMaxMs: config.turnLimits.maxMs,
|
|
373
|
+
turnTimeoutCardMs: config.turnLimits.cardMs,
|
|
374
|
+
autonomy: config.autonomy,
|
|
347
375
|
memory: config.memory,
|
|
348
376
|
...(crowd ? { crowd } : {}),
|
|
349
377
|
}),
|
|
350
|
-
...(
|
|
351
|
-
? [
|
|
378
|
+
...(agentTeam
|
|
379
|
+
? [agentTeam.agentServer, ...(agentTeam.seeds ? [agentTeam.seeds] : [])]
|
|
352
380
|
: []),
|
|
353
381
|
...config.plugins,
|
|
354
382
|
schedulerPlugin(),
|