pi-roundtable 0.8.0 → 0.9.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 +115 -0
- package/README.md +35 -11
- package/README.zh-TW.md +27 -10
- package/docs/migration-0.9.md +247 -0
- package/docs/plugins.md +618 -86
- package/examples/channel-context.test.ts +88 -0
- package/examples/channel-context.ts +62 -0
- package/examples/echo-runtime.test.ts +1 -0
- package/examples/fake-surface.test.ts +69 -6
- package/examples/fake-surface.ts +73 -7
- package/examples/headless.ts +5 -1
- package/examples/prechecks.test.ts +1 -0
- package/examples/principals.test.ts +60 -0
- package/examples/principals.ts +48 -0
- package/examples/prompt.test.ts +1 -1
- package/examples/study-room.test.ts +29 -1
- package/examples/study-room.ts +16 -19
- package/examples/support-desk.test.ts +3 -1
- package/examples/tools.test.ts +6 -1
- package/package.json +5 -2
- package/src/cli/checks/database.ts +11 -0
- package/src/cli/checks/identity.ts +225 -0
- package/src/cli/checks/model.ts +21 -3
- package/src/cli/cli.ts +115 -1
- package/src/cli/config-edit.ts +83 -13
- package/src/cli/doctor.ts +7 -0
- package/src/cli/line-diff.ts +72 -0
- package/src/cli/principal.ts +412 -0
- package/src/cli/project.ts +5 -0
- package/src/cli/report.ts +12 -3
- package/src/cli/upgrade-rewrite.ts +102 -0
- package/src/cli/upgrade-source.ts +430 -0
- package/src/cli/upgrade.ts +176 -0
- package/src/core/agents/agent-claim.ts +56 -34
- package/src/core/agents/team-turns.ts +8 -4
- package/src/core/builtin/agent-server.ts +9 -7
- package/src/core/builtin/discord-admin.ts +4 -1
- package/src/core/builtin/discord.ts +71 -13
- package/src/core/builtin/modules.ts +150 -86
- package/src/core/builtin/stores.ts +40 -21
- package/src/core/config/access.ts +229 -0
- package/src/core/config/adapters.ts +64 -1
- package/src/core/config/config.ts +87 -41
- package/src/core/contract/channel-context.ts +53 -0
- package/src/core/contract/channels.ts +82 -9
- package/src/core/contract/runtime.ts +23 -7
- package/src/core/contract/surface.ts +22 -6
- package/src/core/conversations/conversation-registry.ts +14 -0
- package/src/core/conversations/conversation-store.ts +13 -0
- package/src/core/define-roundtable.ts +42 -11
- package/src/core/discord/agent-panel.ts +2 -2
- package/src/core/discord/card-audience.ts +148 -0
- package/src/core/discord/channel-context.ts +348 -0
- package/src/core/discord/connection.ts +15 -3
- package/src/core/discord/direct-channel.ts +54 -0
- package/src/core/discord/discord-owners.ts +133 -0
- package/src/core/discord/discord-surface.ts +69 -14
- package/src/core/discord/fresh-marker.ts +24 -0
- package/src/core/discord/inbound-message.ts +21 -7
- package/src/core/discord/interaction-module.ts +11 -1
- package/src/core/discord/owner-cards-fixture.ts +80 -0
- package/src/core/discord/owner-cards.ts +58 -97
- package/src/core/discord/owner-command.ts +27 -13
- package/src/core/discord/stop-button.ts +4 -4
- package/src/core/domain/conversation.ts +7 -2
- package/src/core/domain/errors.ts +5 -0
- package/src/core/domain/ports.ts +27 -2
- package/src/core/drain.ts +56 -5
- package/src/core/errors.ts +13 -0
- package/src/core/host-background.ts +56 -0
- package/src/core/host.ts +52 -68
- package/src/core/i18n/cards.ts +8 -4
- package/src/core/i18n/conversation.ts +2 -0
- package/src/core/identity/access-policy.ts +227 -0
- package/src/core/identity/actor-facts.ts +65 -0
- package/src/core/identity/caching-principal-store.ts +111 -0
- package/src/core/identity/config-owner.ts +145 -0
- package/src/core/identity/contact.ts +27 -0
- package/src/core/identity/identity-plugin.ts +56 -0
- package/src/core/identity/identity-schema.ts +282 -0
- package/src/core/identity/identity-service.ts +489 -0
- package/src/core/identity/identity-view.ts +77 -0
- package/src/core/identity/plugin-identities.ts +109 -0
- package/src/core/identity/principal-reads.ts +38 -0
- package/src/core/identity/principal-store.ts +478 -0
- package/src/core/identity/principal-tables.ts +53 -0
- package/src/core/identity/seen-throttle.ts +57 -0
- package/src/core/identity/ulid.ts +36 -0
- package/src/core/identity.ts +34 -5
- package/src/core/interactions/prompts.ts +127 -0
- package/src/core/modules/background/background-turns.ts +56 -17
- package/src/core/modules/background/personal-target.ts +50 -0
- package/src/core/modules/delegation/delegate.ts +28 -14
- package/src/core/modules/delegation/delegator.ts +23 -7
- package/src/core/modules/memory/owner-memory.ts +123 -50
- package/src/core/modules/notify/notify.ts +53 -13
- package/src/core/modules/notify/session-notify.ts +110 -0
- package/src/core/modules/schedules/prechecks.ts +8 -2
- package/src/core/modules/schedules/schedule-prechecks.ts +4 -1
- package/src/core/modules/schedules/schedule-store.ts +51 -20
- package/src/core/modules/schedules/schedule-tools.ts +122 -12
- package/src/core/modules/schedules/scheduler.ts +56 -4
- package/src/core/modules/schedules/schedules.ts +43 -14
- package/src/core/plugin.ts +48 -0
- package/src/core/presence/direct-channels.ts +104 -0
- package/src/core/registry/contributions.ts +63 -3
- package/src/core/routing/channel-queue.ts +18 -1
- package/src/core/routing/channel-router.ts +274 -21
- package/src/core/routing/conversation-turns.ts +45 -10
- package/src/core/routing/surface-port.ts +32 -2
- package/src/core/routing/system-turns.ts +29 -0
- package/src/core/runtime/abort-guard.ts +58 -0
- package/src/core/runtime/bridge-guard.ts +225 -0
- package/src/core/runtime/compaction-tiers.ts +5 -1
- package/src/core/runtime/conversation-sessions.ts +139 -12
- package/src/core/runtime/extensions/ask-user.ts +1 -1
- package/src/core/runtime/extensions/confirmation-gate.ts +28 -14
- package/src/core/runtime/extensions/private-memory.ts +447 -0
- package/src/core/runtime/pending-confirmation-store.ts +30 -5
- package/src/core/runtime/pi-agent-runtime.ts +174 -190
- package/src/core/runtime/preflight-tools.ts +47 -0
- package/src/core/runtime/prompt-slot.ts +4 -4
- package/src/core/runtime/reader-history.ts +129 -0
- package/src/core/runtime/runtime-plugin.ts +51 -5
- package/src/core/runtime/runtime-types.ts +48 -12
- package/src/core/runtime/session-conversation.ts +143 -0
- package/src/core/runtime/session-factory.ts +115 -15
- package/src/core/runtime/session-scope.ts +78 -0
- package/src/core/runtime/steerable-run.ts +21 -3
- package/src/core/runtime/thinking-level.ts +47 -0
- package/src/core/runtime/tool-call-scope.ts +43 -0
- package/src/core/runtime/turn-answer.ts +30 -1
- package/src/core/runtime/turn-wrapup.ts +63 -0
- package/src/core/services.ts +27 -1
- package/src/core/sessions.ts +30 -0
- package/src/core/speakers.ts +21 -19
- package/src/core/testing/background.ts +31 -0
- package/src/core/testing/fixture-database.ts +55 -0
- package/src/core/testing/map-identity.ts +76 -0
- package/src/core/testing/module-sessions.ts +134 -0
- package/src/core/testing/modules.ts +108 -21
- package/src/core/testing/owner.ts +17 -0
- package/src/core/testing/surface-contract.ts +118 -41
- package/src/core/testing/test-host.ts +79 -16
- package/src/core/testing/tool-set.ts +32 -0
- package/src/core/tool-set.snapshot.json +4 -4
- package/src/core/tool-tiers.ts +57 -3
- package/src/discord/index.ts +8 -0
- package/src/index.ts +48 -8
- package/src/kit/compaction.ts +13 -0
- package/src/kit/index.ts +10 -0
- package/src/kit/support.ts +1 -1
- package/src/testing.ts +55 -16
- package/templates/.env.example +1 -1
- package/templates/adapters/web/README.md +7 -0
- package/templates/adapters/web/roundtable.config.ts +14 -10
- package/templates/roundtable.config.ts +12 -1
- package/src/core/domain/owner-prompts.ts +0 -45
|
@@ -1,5 +1,65 @@
|
|
|
1
|
+
import type { ChannelContextOptions } from "../contract/channel-context.ts";
|
|
1
2
|
import { ConfigError } from "../domain/errors.ts";
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
bool,
|
|
5
|
+
integer,
|
|
6
|
+
number,
|
|
7
|
+
oneOf,
|
|
8
|
+
optional,
|
|
9
|
+
orOff,
|
|
10
|
+
shape,
|
|
11
|
+
text,
|
|
12
|
+
} from "./schema.ts";
|
|
13
|
+
|
|
14
|
+
/** The Discord bot and its guild, as the top-level `discord` or `discord()` from `pi-roundtable/discord` gives them. */
|
|
15
|
+
export interface DiscordConfig {
|
|
16
|
+
/** The bot token; keep it in `.env`, not in this file. */
|
|
17
|
+
token: string;
|
|
18
|
+
/** The agent server's guild id. */
|
|
19
|
+
guild: string;
|
|
20
|
+
/** The channel the coordinator lives in. */
|
|
21
|
+
entryChannel: string;
|
|
22
|
+
/** The root slash command, without the slash; default the lowercase assistant name. */
|
|
23
|
+
rootCommand?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the owner's conversations have Discord administration tools; default true. `false`
|
|
26
|
+
* leaves the `discord-admin` addon out.
|
|
27
|
+
*/
|
|
28
|
+
admin?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Text appended as it is to the refusal a non-owner gets from the root command, such as a
|
|
31
|
+
* pointer to the commands anyone may use; include the space or punctuation your language
|
|
32
|
+
* needs before it. Default none.
|
|
33
|
+
*/
|
|
34
|
+
refusalHint?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whose turns in the agents' channels load memory: `"everyone"` (default) lets each speaker
|
|
37
|
+
* read their own, `"owners"` only the owners, so a guest's turn has no memory in its prompt and
|
|
38
|
+
* no memory tools, and never makes claude-bridge refuse the owner afterwards.
|
|
39
|
+
*/
|
|
40
|
+
agentMemory?: "everyone" | "owners";
|
|
41
|
+
/**
|
|
42
|
+
* What a turn addressed to the assistant in a server channel reads of the messages posted there
|
|
43
|
+
* since its last post, mentions or not; on with the defaults when left out. `false` turns it off
|
|
44
|
+
* for the agent server and every plugin's claim.
|
|
45
|
+
*/
|
|
46
|
+
channelContext?: ChannelContextOptions | false;
|
|
47
|
+
/**
|
|
48
|
+
* The divider the assistant posts in a server channel or thread when its conversation starts
|
|
49
|
+
* over (a new conversation by an owner's command), which ends the window channel context reads;
|
|
50
|
+
* never in a direct message. Default `─── new conversation ───`; `false` posts none.
|
|
51
|
+
*/
|
|
52
|
+
freshMarker?: string | false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** What `discord.channelContext` takes besides `false`. */
|
|
56
|
+
const channelContextShape = shape({
|
|
57
|
+
fetch: optional(integer(1, 100)),
|
|
58
|
+
keep: optional(integer(1, 100)),
|
|
59
|
+
similarity: optional(number(0, 1)),
|
|
60
|
+
messageChars: optional(integer(1, 4000)),
|
|
61
|
+
botMessageChars: optional(integer(1, 4000)),
|
|
62
|
+
});
|
|
3
63
|
|
|
4
64
|
/** The Discord settings, at the top level or in a Discord adapter. */
|
|
5
65
|
export const discordShape = shape({
|
|
@@ -9,6 +69,9 @@ export const discordShape = shape({
|
|
|
9
69
|
rootCommand: optional(text),
|
|
10
70
|
admin: optional(bool),
|
|
11
71
|
refusalHint: optional(text),
|
|
72
|
+
agentMemory: optional(oneOf<"everyone" | "owners">("everyone", "owners")),
|
|
73
|
+
channelContext: optional(orOff(channelContextShape)),
|
|
74
|
+
freshMarker: optional(orOff(text)),
|
|
12
75
|
});
|
|
13
76
|
|
|
14
77
|
/** The adapters `adapters` takes, by the name their factory gives them. */
|
|
@@ -3,7 +3,9 @@ import { join } from "node:path";
|
|
|
3
3
|
import type { AgentSeed } from "../agents/agent-rules.ts";
|
|
4
4
|
import { ConfigError } from "../domain/errors.ts";
|
|
5
5
|
import type { InterimTextMode } from "../domain/interim.ts";
|
|
6
|
+
import { DRAIN_LIMIT_MS } from "../drain.ts";
|
|
6
7
|
import { isLocale, type Locale } from "../i18n/index.ts";
|
|
8
|
+
import type { AccessRules } from "../identity/access-policy.ts";
|
|
7
9
|
import type { OwnerIdentity } from "../identity.ts";
|
|
8
10
|
import {
|
|
9
11
|
type ModelRef,
|
|
@@ -11,11 +13,13 @@ import {
|
|
|
11
13
|
THINKING_LEVELS,
|
|
12
14
|
type ThinkingLevel,
|
|
13
15
|
} from "../models.ts";
|
|
16
|
+
import type { PerPrincipalLimits } from "../modules/background/personal-target.ts";
|
|
14
17
|
import type { RoundtablePlugin } from "../plugin.ts";
|
|
15
18
|
import { PRIMARY_CHARS } from "../runtime/interim-text.ts";
|
|
16
19
|
import type { ChannelKey } from "../sessions.ts";
|
|
17
20
|
import type { Tier, TierMembers } from "../speakers.ts";
|
|
18
|
-
import {
|
|
21
|
+
import { type AccessConfig, accessOf, accessShape } from "./access.ts";
|
|
22
|
+
import { type DiscordConfig, discordShape, liftAdapters } from "./adapters.ts";
|
|
19
23
|
import {
|
|
20
24
|
bool,
|
|
21
25
|
guarded,
|
|
@@ -30,6 +34,8 @@ import {
|
|
|
30
34
|
text,
|
|
31
35
|
} from "./schema.ts";
|
|
32
36
|
|
|
37
|
+
export type { DiscordConfig };
|
|
38
|
+
|
|
33
39
|
/** How prompts refer back to the owner. */
|
|
34
40
|
export type Pronouns = "he" | "she" | "they";
|
|
35
41
|
|
|
@@ -43,29 +49,6 @@ export interface TierConfig {
|
|
|
43
49
|
everyone?: boolean;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
/** The Discord bot and its guild, as the top-level `discord` or `discord()` from `pi-roundtable/discord` gives them. */
|
|
47
|
-
export interface DiscordConfig {
|
|
48
|
-
/** The bot token; keep it in `.env`, not in this file. */
|
|
49
|
-
token: string;
|
|
50
|
-
/** The agent server's guild id. */
|
|
51
|
-
guild: string;
|
|
52
|
-
/** The channel the coordinator lives in. */
|
|
53
|
-
entryChannel: string;
|
|
54
|
-
/** The root slash command, without the slash; default the lowercase assistant name. */
|
|
55
|
-
rootCommand?: string;
|
|
56
|
-
/**
|
|
57
|
-
* Whether the owner's conversations have Discord administration tools; default true. `false`
|
|
58
|
-
* leaves the `discord-admin` addon out.
|
|
59
|
-
*/
|
|
60
|
-
admin?: boolean;
|
|
61
|
-
/**
|
|
62
|
-
* Text appended as it is to the refusal a non-owner gets from the root command, such as a
|
|
63
|
-
* pointer to the commands anyone may use; include the space or punctuation your language
|
|
64
|
-
* needs before it. Default none.
|
|
65
|
-
*/
|
|
66
|
-
refusalHint?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
52
|
/** A chat network the host talks through, as its adapter's factory makes it; `adapter` names the network. */
|
|
70
53
|
export interface AdapterConfig {
|
|
71
54
|
readonly adapter: string;
|
|
@@ -77,12 +60,21 @@ export interface DiscordAdapterConfig extends AdapterConfig {
|
|
|
77
60
|
readonly discord: DiscordConfig;
|
|
78
61
|
}
|
|
79
62
|
|
|
63
|
+
/** The configuration's `background`: each person's share of the host's background work. */
|
|
64
|
+
export interface BackgroundConfig {
|
|
65
|
+
perPrincipal?: PerPrincipalLimits;
|
|
66
|
+
}
|
|
67
|
+
|
|
80
68
|
/** What `roundtable.config.ts` gives `defineRoundtable`. */
|
|
81
69
|
export interface RoundtableConfig {
|
|
82
70
|
/** The assistant's display name; default "Roundtable". */
|
|
83
71
|
name?: string;
|
|
84
|
-
/**
|
|
85
|
-
|
|
72
|
+
/**
|
|
73
|
+
* The one owner, as 0.8 wrote it: their id, which is both their principal and their Discord
|
|
74
|
+
* user id, their name, and pronouns. Deprecated, going away in 0.10: write `access` instead,
|
|
75
|
+
* never both; `roundtable upgrade` rewrites it.
|
|
76
|
+
*/
|
|
77
|
+
owner?: { id: string; name: string; pronouns?: Pronouns };
|
|
86
78
|
/**
|
|
87
79
|
* The Discord bot, its guild, and the agent server that lives there. Leave it out for a host
|
|
88
80
|
* without Discord: no Discord plugins, no agent server or agents, no skills, and no tool that
|
|
@@ -113,8 +105,19 @@ export interface RoundtableConfig {
|
|
|
113
105
|
locale?: Locale;
|
|
114
106
|
/** The IANA time zone schedules and stamps use; default "UTC". */
|
|
115
107
|
timeZone?: string;
|
|
116
|
-
/** Who may talk to the agents besides the owner, and
|
|
108
|
+
/** Who may talk to the agents besides the owner, by Discord user and role ids, as 0.8 wrote it. Deprecated with `owner`: write `access` instead. */
|
|
117
109
|
speakers?: { admins?: TierConfig; members?: TierConfig };
|
|
110
|
+
/**
|
|
111
|
+
* Who the host serves: the owners, the first the primary one, and the admins and members by
|
|
112
|
+
* identity (`<provider>:<subject>`), role (`<surface>:role:<name>`), or everyone. Required
|
|
113
|
+
* unless the deprecated `owner` is given, and never with it.
|
|
114
|
+
*/
|
|
115
|
+
access?: AccessConfig;
|
|
116
|
+
/**
|
|
117
|
+
* Each person's limits across all their conversations: `perPrincipal.schedules` they may keep and
|
|
118
|
+
* `perPrincipal.delegations` running at once. Unset, only each conversation's, as in 0.8.
|
|
119
|
+
*/
|
|
120
|
+
background?: BackgroundConfig;
|
|
118
121
|
toolTiers?: Record<string, Tier>;
|
|
119
122
|
/** Prompt files, read once at start; `shared` starts every agent's prompt. */
|
|
120
123
|
prompts?: { shared: string; guest?: string };
|
|
@@ -162,6 +165,11 @@ export interface RoundtableConfig {
|
|
|
162
165
|
* conversation or no plugin's claim owns it; the web chat takes no error reports.
|
|
163
166
|
*/
|
|
164
167
|
ops?: { agent: string } | { conversation: string };
|
|
168
|
+
/**
|
|
169
|
+
* Seconds a shutdown waits for running turns before stopping them; default 180. Give the
|
|
170
|
+
* service manager a stop timeout a minute longer, such as systemd's `TimeoutStopSec`.
|
|
171
|
+
*/
|
|
172
|
+
drainSeconds?: number;
|
|
165
173
|
/**
|
|
166
174
|
* Whether a turn posts the text it writes before its final answer as it goes: long or
|
|
167
175
|
* structured text as ordinary messages, short narration and the tools called in one small
|
|
@@ -187,11 +195,13 @@ const isPlugin = (value: unknown): value is RoundtablePlugin =>
|
|
|
187
195
|
|
|
188
196
|
const schema = shape({
|
|
189
197
|
name: optional(text),
|
|
190
|
-
owner:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
owner: optional(
|
|
199
|
+
shape({
|
|
200
|
+
id: text,
|
|
201
|
+
name: text,
|
|
202
|
+
pronouns: optional(oneOf<Pronouns>("he", "she", "they")),
|
|
203
|
+
}),
|
|
204
|
+
),
|
|
195
205
|
discord: optional(discordShape),
|
|
196
206
|
database: shape({ url: text }),
|
|
197
207
|
dataDir: text,
|
|
@@ -216,6 +226,17 @@ const schema = shape({
|
|
|
216
226
|
speakers: optional(
|
|
217
227
|
shape({ admins: optional(tierMembers), members: optional(tierMembers) }),
|
|
218
228
|
),
|
|
229
|
+
access: optional(accessShape),
|
|
230
|
+
background: optional(
|
|
231
|
+
shape({
|
|
232
|
+
perPrincipal: optional(
|
|
233
|
+
shape({
|
|
234
|
+
schedules: optional(integer(1, 10_000)),
|
|
235
|
+
delegations: optional(integer(1, 1_000)),
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
219
240
|
toolTiers: optional(record(oneOf<Tier>("owner", "admin", "member"))),
|
|
220
241
|
prompts: optional(shape({ shared: text, guest: optional(text) })),
|
|
221
242
|
agents: optional(
|
|
@@ -246,6 +267,7 @@ const schema = shape({
|
|
|
246
267
|
),
|
|
247
268
|
memory: optional(bool),
|
|
248
269
|
ops: optional(shape({ agent: optional(text), conversation: optional(text) })),
|
|
270
|
+
drainSeconds: optional(integer(1, 3_600)),
|
|
249
271
|
interimText: optional(oneOf<InterimTextMode>("on", "off")),
|
|
250
272
|
interimPrimaryChars: optional(integer(1, 100_000)),
|
|
251
273
|
plugins: optional(
|
|
@@ -255,12 +277,6 @@ const schema = shape({
|
|
|
255
277
|
),
|
|
256
278
|
});
|
|
257
279
|
|
|
258
|
-
const PRONOUNS: Record<Pronouns, OwnerIdentity["pronouns"]> = {
|
|
259
|
-
he: { subject: "he", object: "him", possessive: "his" },
|
|
260
|
-
she: { subject: "she", object: "her", possessive: "her" },
|
|
261
|
-
they: { subject: "they", object: "them", possessive: "their" },
|
|
262
|
-
};
|
|
263
|
-
|
|
264
280
|
/** The configuration with every default filled in. */
|
|
265
281
|
export interface ResolvedConfig {
|
|
266
282
|
name: string;
|
|
@@ -269,7 +285,17 @@ export interface ResolvedConfig {
|
|
|
269
285
|
* with Discord. `discord.rootCommand` when Discord is on, else the lowercase assistant name.
|
|
270
286
|
*/
|
|
271
287
|
slug: string;
|
|
288
|
+
/**
|
|
289
|
+
* The primary owner, `access.owners[0]` (or the 0.8 `owner`): their principal id, name, and
|
|
290
|
+
* pronouns, and the Discord user id the Discord parts act for until 0.10.
|
|
291
|
+
*/
|
|
292
|
+
primaryOwner: OwnerIdentity & { id: string; discordId?: string };
|
|
293
|
+
/** The primary owner as 0.8 knew them, `id` their principal id. Deprecated: read `primaryOwner`. */
|
|
272
294
|
owner: OwnerIdentity & { id: string };
|
|
295
|
+
/** Who the host serves, the 0.8 `owner` and `speakers` converted when they are what the configuration gives. */
|
|
296
|
+
access: AccessRules;
|
|
297
|
+
/** What the configuration wrote in a deprecated form, which the host logs once. */
|
|
298
|
+
deprecations: string[];
|
|
273
299
|
/** Undefined on a host without Discord. */
|
|
274
300
|
discord?: {
|
|
275
301
|
token: string;
|
|
@@ -278,6 +304,10 @@ export interface ResolvedConfig {
|
|
|
278
304
|
rootCommand: string;
|
|
279
305
|
admin: boolean;
|
|
280
306
|
refusalHint?: string;
|
|
307
|
+
agentMemory?: "everyone" | "owners";
|
|
308
|
+
/** As configured: `{}` when left out, so the defaults apply, or `false`. */
|
|
309
|
+
channelContext: NonNullable<DiscordConfig["channelContext"]>;
|
|
310
|
+
freshMarker?: string | false;
|
|
281
311
|
};
|
|
282
312
|
databaseUrl: string;
|
|
283
313
|
dataDir: string;
|
|
@@ -288,7 +318,10 @@ export interface ResolvedConfig {
|
|
|
288
318
|
delegation: { model: ModelRef; thinking: ThinkingLevel };
|
|
289
319
|
locale: Locale;
|
|
290
320
|
timeZone: string;
|
|
321
|
+
/** The 0.8 `speakers`; empty when the configuration writes `access`. Deprecated: read `access`. */
|
|
291
322
|
speakers: { admins?: TierMembers; members?: TierMembers };
|
|
323
|
+
/** Each person's limits across their conversations; a limit left out is unset. */
|
|
324
|
+
background: { perPrincipal: PerPrincipalLimits };
|
|
292
325
|
toolTiers: Record<string, Tier>;
|
|
293
326
|
prompts: { shared?: string; guest?: string };
|
|
294
327
|
agents: AgentSeed[];
|
|
@@ -308,6 +341,7 @@ export interface ResolvedConfig {
|
|
|
308
341
|
skills: false | { builtinDir?: string; reposDir?: string };
|
|
309
342
|
memory: boolean;
|
|
310
343
|
ops?: { agent: string } | { conversation: ChannelKey };
|
|
344
|
+
drainMs: number;
|
|
311
345
|
interimText: InterimTextMode;
|
|
312
346
|
interimPrimaryChars: number;
|
|
313
347
|
plugins: RoundtablePlugin[];
|
|
@@ -378,13 +412,16 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
378
412
|
const slug =
|
|
379
413
|
discord?.rootCommand ?? name.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
380
414
|
const ops = opsOf(config.ops, discord !== undefined);
|
|
415
|
+
const access = accessOf(config, discord !== undefined);
|
|
416
|
+
const { primaryOwner } = access;
|
|
381
417
|
return {
|
|
382
418
|
name,
|
|
383
419
|
slug,
|
|
420
|
+
...access,
|
|
384
421
|
owner: {
|
|
385
|
-
id:
|
|
386
|
-
name:
|
|
387
|
-
pronouns:
|
|
422
|
+
id: primaryOwner.id,
|
|
423
|
+
name: primaryOwner.name,
|
|
424
|
+
pronouns: primaryOwner.pronouns,
|
|
388
425
|
},
|
|
389
426
|
...(discord
|
|
390
427
|
? {
|
|
@@ -397,6 +434,13 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
397
434
|
...(discord.refusalHint === undefined
|
|
398
435
|
? {}
|
|
399
436
|
: { refusalHint: discord.refusalHint }),
|
|
437
|
+
...(discord.agentMemory === undefined
|
|
438
|
+
? {}
|
|
439
|
+
: { agentMemory: discord.agentMemory }),
|
|
440
|
+
channelContext: discord.channelContext ?? {},
|
|
441
|
+
...(discord.freshMarker === undefined
|
|
442
|
+
? {}
|
|
443
|
+
: { freshMarker: discord.freshMarker }),
|
|
400
444
|
},
|
|
401
445
|
}
|
|
402
446
|
: {}),
|
|
@@ -423,6 +467,7 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
423
467
|
...(config.speakers?.admins ? { admins: config.speakers.admins } : {}),
|
|
424
468
|
...(config.speakers?.members ? { members: config.speakers.members } : {}),
|
|
425
469
|
},
|
|
470
|
+
background: { perPrincipal: { ...config.background?.perPrincipal } },
|
|
426
471
|
toolTiers: config.toolTiers ?? {},
|
|
427
472
|
prompts: config.prompts ?? {},
|
|
428
473
|
agents: config.agents ?? [],
|
|
@@ -446,6 +491,7 @@ export function resolveConfig(input: unknown): ResolvedConfig {
|
|
|
446
491
|
skills: config.skills ?? (discord ? {} : false),
|
|
447
492
|
memory: config.memory ?? true,
|
|
448
493
|
...(ops ? { ops } : {}),
|
|
494
|
+
drainMs: (config.drainSeconds ?? DRAIN_LIMIT_MS / 1_000) * 1_000,
|
|
449
495
|
interimText: config.interimText ?? "on",
|
|
450
496
|
interimPrimaryChars: config.interimPrimaryChars ?? PRIMARY_CHARS,
|
|
451
497
|
plugins: config.plugins ?? [],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// What a surface tells a turn about the channel around the message it answers. Discord fills it
|
|
2
|
+
// (`DISCORD.channelContext`); the types and the way a turn carries it are any surface's.
|
|
3
|
+
|
|
4
|
+
/** How much of a channel a turn reads as its channel context; every member has a default. */
|
|
5
|
+
export interface ChannelContextOptions {
|
|
6
|
+
/** How many messages before the addressed one are fetched; default 50, at most 100 (Discord's page). */
|
|
7
|
+
fetch?: number;
|
|
8
|
+
/** How many of the window's messages are kept, the newest; default 15. */
|
|
9
|
+
keep?: number;
|
|
10
|
+
/**
|
|
11
|
+
* How alike (0 to 1) two consecutive messages of one author must be to count as one, the later
|
|
12
|
+
* kept, such as a message and its corrected repost; default 0.8.
|
|
13
|
+
*/
|
|
14
|
+
similarity?: number;
|
|
15
|
+
/** Where a message's text is cut; default 500 characters. */
|
|
16
|
+
messageChars?: number;
|
|
17
|
+
/** Where another bot's message is cut; default 80 characters. */
|
|
18
|
+
botMessageChars?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One channel message a turn reads around the message that addressed the assistant. */
|
|
22
|
+
export interface ChannelContextMessage {
|
|
23
|
+
id: string;
|
|
24
|
+
authorId: string;
|
|
25
|
+
/** How the author appears in the channel. */
|
|
26
|
+
authorName: string;
|
|
27
|
+
/** Another bot, or an integration such as a webhook that is not the assistant's. */
|
|
28
|
+
bot: boolean;
|
|
29
|
+
/** One of the host's owners. */
|
|
30
|
+
owner: boolean;
|
|
31
|
+
/** The text, with mentions by name and stickers and files as text, cut where the options say. */
|
|
32
|
+
text: string;
|
|
33
|
+
at: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What was said in a server channel since the assistant last posted there, before the message that
|
|
38
|
+
* addressed it: public channel text, not anyone's private memory, and not from the turn's speaker.
|
|
39
|
+
*/
|
|
40
|
+
export interface ChannelContext {
|
|
41
|
+
/** Oldest first; never empty. */
|
|
42
|
+
readonly messages: readonly ChannelContextMessage[];
|
|
43
|
+
/** The delimited block `withChannelContext` appends to a turn's text. */
|
|
44
|
+
readonly text: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The turn's text, then its channel context when there is one, as `withReference` adds a replied-to message. */
|
|
48
|
+
export function withChannelContext(
|
|
49
|
+
text: string,
|
|
50
|
+
context: ChannelContext | undefined,
|
|
51
|
+
): string {
|
|
52
|
+
return context ? `${text}\n\n${context.text}` : text;
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Locale } from "../i18n/index.ts";
|
|
2
|
+
import type { ActorFacts } from "../identity/actor-facts.ts";
|
|
2
3
|
import type { ChannelKey } from "../sessions.ts";
|
|
3
|
-
import type { Tier } from "../speakers.ts";
|
|
4
|
+
import type { Speaker, Tier } from "../speakers.ts";
|
|
4
5
|
|
|
5
6
|
/** A file attached to a chat message, as the surface reports it; nothing is downloaded yet. */
|
|
6
7
|
export interface AttachmentRef {
|
|
@@ -16,6 +17,21 @@ export interface InboundMessage {
|
|
|
16
17
|
channel: ChannelKey;
|
|
17
18
|
/** Unique per surface; keeps the files of different messages apart. */
|
|
18
19
|
messageId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Who wrote it, as the surface reports them: the router resolves them to `speaker` through the
|
|
22
|
+
* identity service. A surface that leaves it out has it read from `authorId`, `authorName`, and
|
|
23
|
+
* `authorRoleIds` under its own name as the provider, with a `deprecated` warning; that goes
|
|
24
|
+
* away in 1.0.
|
|
25
|
+
*/
|
|
26
|
+
actor?: ActorFacts;
|
|
27
|
+
/**
|
|
28
|
+
* Who the author is, with their principal and tier, as the router resolved them before any
|
|
29
|
+
* claim admits the message: undefined when the access rules serve no one by them, and for a bot
|
|
30
|
+
* or an integration, which are not resolved. Only the router sets it; a surface's own value is
|
|
31
|
+
* dropped. A claim that serves only people the host serves answers only when it is set.
|
|
32
|
+
*/
|
|
33
|
+
speaker?: Speaker;
|
|
34
|
+
/** The id the surface knows the author by; who they are to the host is `speaker`. */
|
|
19
35
|
authorId: string;
|
|
20
36
|
/** How the author appears in the channel, for example a server nickname. */
|
|
21
37
|
authorName: string;
|
|
@@ -77,10 +93,22 @@ export interface BackgroundTarget {
|
|
|
77
93
|
name: string;
|
|
78
94
|
/** How lists such as `/<root> schedule` name it, in the host's locale. */
|
|
79
95
|
label(locale: Locale): string;
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Limits of the schedules made for it; absent, nothing may be scheduled for it. `perPrincipal`,
|
|
98
|
+
* when set, holds each person to that many of its schedules across all their conversations.
|
|
99
|
+
*/
|
|
100
|
+
schedules?: {
|
|
101
|
+
perChannel: number;
|
|
102
|
+
perPrincipal?: number;
|
|
103
|
+
promptChars: number;
|
|
104
|
+
aheadDays: number;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Limits of the delegated tasks reporting to it; absent, nothing may be delegated to it.
|
|
108
|
+
* `maxRunningPerPrincipal`, when set, holds each person to that many running at once across
|
|
109
|
+
* all their conversations.
|
|
110
|
+
*/
|
|
111
|
+
delegation?: { maxRunning: number; maxRunningPerPrincipal?: number };
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
/** A turn nobody wrote in the channel: a schedule's, or a report of work done elsewhere. */
|
|
@@ -91,18 +119,31 @@ export interface BackgroundTurn {
|
|
|
91
119
|
* plugin contributes, and a claim skips one it does not serve.
|
|
92
120
|
*/
|
|
93
121
|
target: string;
|
|
94
|
-
author: { id: string; name: string };
|
|
95
122
|
/**
|
|
96
|
-
*
|
|
97
|
-
* the
|
|
123
|
+
* Whom the turn is for: the principal who set up the work, and the id and name they are shown
|
|
124
|
+
* by, such as the identity they spoke as. The system principal is the host's alone.
|
|
98
125
|
*/
|
|
99
|
-
|
|
126
|
+
author: { principalId: string; id: string; name: string };
|
|
127
|
+
/**
|
|
128
|
+
* The tier the turn asks to run at, its creator's when they set it up. It runs at that tier or
|
|
129
|
+
* the principal's own now, whichever is lower.
|
|
130
|
+
*/
|
|
131
|
+
tier: Tier;
|
|
100
132
|
turnId: string;
|
|
101
133
|
text: string;
|
|
102
134
|
/** It delivers a report the owner is waiting for, so it may ask them on cards. */
|
|
103
135
|
report?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Who the turn runs as, set by the router alone once it checked the author: their principal
|
|
138
|
+
* exists and is not disabled, and the tier is capped at theirs. A claim runs the turn as this
|
|
139
|
+
* speaker; one the caller sets is replaced.
|
|
140
|
+
*/
|
|
141
|
+
speaker?: Speaker;
|
|
104
142
|
}
|
|
105
143
|
|
|
144
|
+
/** Who a background turn runs as, once checked; or why it may not run now. */
|
|
145
|
+
export type BackgroundRunsAs = { speaker: Speaker } | { skipped: string };
|
|
146
|
+
|
|
106
147
|
/**
|
|
107
148
|
* Whose conversation a channel holds, as the claim that owns it names it when `startFresh` says
|
|
108
149
|
* whose it was: any string, such as "owner" or "study". The host reads none of them, so a plugin
|
|
@@ -125,6 +166,12 @@ export type Admission =
|
|
|
125
166
|
};
|
|
126
167
|
/** The turn, run in the channel's queue. */
|
|
127
168
|
run(): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* Called instead of `run` when the router drops the message the claim admitted, because
|
|
171
|
+
* its author, once recorded, was not who they were when admitted; frees what the claim
|
|
172
|
+
* holds for it.
|
|
173
|
+
*/
|
|
174
|
+
dropped?(): void;
|
|
128
175
|
/** The log line when the turn throws. */
|
|
129
176
|
failure: string;
|
|
130
177
|
}
|
|
@@ -161,13 +208,32 @@ export interface ChannelClaim {
|
|
|
161
208
|
deleteConversation?(channel: ChannelKey): Promise<void>;
|
|
162
209
|
/** Reports of work started in an owned channel stay in it instead of opening a thread. */
|
|
163
210
|
postsInPlace?: boolean;
|
|
211
|
+
/**
|
|
212
|
+
* Whether the claim's `background` answers the host's own turns (`SYSTEM_PRINCIPAL`), such as
|
|
213
|
+
* `ops.conversation`'s error reports, in its conversations; true when left out. A claim whose
|
|
214
|
+
* conversations are each private to the person who opened them, such as the web chat's, says
|
|
215
|
+
* false, and a host whose `ops.conversation` names one of them does not start.
|
|
216
|
+
*/
|
|
217
|
+
takesSystemReports?: boolean;
|
|
164
218
|
}
|
|
165
219
|
|
|
166
220
|
/** The conversations of every claimed channel, through the channel queue. */
|
|
167
221
|
export interface ConversationPort {
|
|
168
222
|
/** Never rejects; resolves when the message's turn is done or dropped. */
|
|
169
223
|
handle(message: InboundMessage): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Runs a turn nobody wrote, as its author's principal at the lower of its tier and theirs; it
|
|
226
|
+
* is skipped, with the reason, when the principal is unknown, disabled, holds no tier, or was
|
|
227
|
+
* last seen too long ago (`access.backgroundStaleDays`), or when the turn names no principal or
|
|
228
|
+
* tier. Never rejects.
|
|
229
|
+
*/
|
|
170
230
|
background(turn: BackgroundTurn): Promise<ScheduledOutcome>;
|
|
231
|
+
/**
|
|
232
|
+
* Who a background turn would run as now, checked as `background` checks it, without running
|
|
233
|
+
* it: for work that must not happen for an author who may not run the turn, such as a
|
|
234
|
+
* schedule's precheck. Throws when the identity service cannot tell.
|
|
235
|
+
*/
|
|
236
|
+
runsAs(turn: BackgroundTurn): Promise<BackgroundRunsAs>;
|
|
171
237
|
/** A contributed background target, read when used; undefined when no plugin contributes it. */
|
|
172
238
|
target(name: string): BackgroundTarget | undefined;
|
|
173
239
|
/** Waits for the channel's running turn, then starts its conversation over; says whose it was. */
|
|
@@ -182,6 +248,8 @@ export interface ConversationPort {
|
|
|
182
248
|
owns(channel: ChannelKey): boolean;
|
|
183
249
|
/** Whether the claim that owns the channel runs background turns there; false when none owns it. */
|
|
184
250
|
takesBackground(channel: ChannelKey): boolean;
|
|
251
|
+
/** Whether the claim that owns the channel takes the host's own turns; false when none owns it. */
|
|
252
|
+
takesSystemReports(channel: ChannelKey): boolean;
|
|
185
253
|
}
|
|
186
254
|
|
|
187
255
|
/** The host's channel queue, as plugins use it. */
|
|
@@ -191,6 +259,11 @@ export interface QueuePort {
|
|
|
191
259
|
size(channel: ChannelKey): number;
|
|
192
260
|
/** Channels with a task running or waiting. */
|
|
193
261
|
busy(): ChannelKey[];
|
|
262
|
+
/**
|
|
263
|
+
* Whether the host is shutting down: a task that has not started is then refused with
|
|
264
|
+
* HostStoppingError, and a plugin that starts work on its own, such as a timer, starts none.
|
|
265
|
+
*/
|
|
266
|
+
readonly closed: boolean;
|
|
194
267
|
/** Called whenever a channel's count of running and waiting tasks changes. */
|
|
195
268
|
onChange(listener: (channel: ChannelKey) => void): void;
|
|
196
269
|
}
|
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
TranscriptEntry,
|
|
6
6
|
TurnResult,
|
|
7
7
|
} from "../domain/conversation.ts";
|
|
8
|
-
import type { OwnerPrompts } from "../domain/owner-prompts.ts";
|
|
9
8
|
import type { TurnRequest } from "../domain/ports.ts";
|
|
9
|
+
import type { PromptScope, Prompts } from "../interactions/prompts.ts";
|
|
10
10
|
import type { Logger } from "../log.ts";
|
|
11
11
|
import type { ThinkingSetting } from "../models.ts";
|
|
12
12
|
import type { HostEnv, LinkedSessions } from "../plugin.ts";
|
|
@@ -51,11 +51,18 @@ export interface AgentRuntime {
|
|
|
51
51
|
startFresh(conversation: ChannelKey): Promise<void>;
|
|
52
52
|
/** Like `startFresh`, but removes the conversation and every archive of it for good; called between turns. */
|
|
53
53
|
deleteConversation(conversation: ChannelKey): Promise<void>;
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* The conversation's held actions known in memory: those of a turn run since startup, and those
|
|
56
|
+
* `heldActions` or `recentTranscript` restored from the store for it. After a restart, ask one of
|
|
57
|
+
* those first.
|
|
58
|
+
*/
|
|
55
59
|
pendingConfirmation(
|
|
56
60
|
conversation: ChannelKey,
|
|
57
61
|
): PendingConfirmation | undefined;
|
|
58
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* The conversation's held actions, restored from the store after a restart, so that
|
|
64
|
+
* `pendingConfirmation` knows them too, as `recentTranscript` does. Expired ones count as none.
|
|
65
|
+
*/
|
|
59
66
|
heldActions(
|
|
60
67
|
conversation: ChannelKey,
|
|
61
68
|
): Promise<PendingConfirmation | undefined>;
|
|
@@ -96,6 +103,13 @@ export interface AgentSessions {
|
|
|
96
103
|
modelOf(name: string): { model: string; thinking: ThinkingSetting };
|
|
97
104
|
/** The channel the scope's turns run in: the group's for a seat in one, else the agent's own. */
|
|
98
105
|
turnChannel(scope: AgentTurnScope): ChannelKey;
|
|
106
|
+
/**
|
|
107
|
+
* Whose turns in an agent's conversation load memory: `"everyone"` (the default) lets each
|
|
108
|
+
* speaker read their own, `"owners"` only the owners, so a speaker below the owner tier gets no
|
|
109
|
+
* memory in their prompt and no memory tools, and their turn never carries private memory into
|
|
110
|
+
* the conversation's history.
|
|
111
|
+
*/
|
|
112
|
+
memory?: "everyone" | "owners";
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
/** Where a runtime keeps each conversation's held actions, so they survive a restart. */
|
|
@@ -124,13 +138,15 @@ export interface RuntimeDeps {
|
|
|
124
138
|
/** What each tool needs; plugin tools are added when the host links, so ask at use time. */
|
|
125
139
|
toolTiers: ToolTiers;
|
|
126
140
|
/**
|
|
127
|
-
* The
|
|
128
|
-
*
|
|
141
|
+
* The approval and question prompts in a conversation for a turn's scope, made with
|
|
142
|
+
* `promptScope(speaker, visibility)`, from its chat surface; undefined when the surface has
|
|
143
|
+
* none, and the action then waits for a message that approves it. A `Speaker`, the 0.8 form,
|
|
144
|
+
* is read as theirs in a shared conversation, with a deprecation warning, until 1.0.
|
|
129
145
|
*/
|
|
130
146
|
prompts(
|
|
131
147
|
conversation: ChannelKey,
|
|
132
|
-
|
|
133
|
-
):
|
|
148
|
+
scope?: PromptScope | Speaker,
|
|
149
|
+
): Prompts | undefined;
|
|
134
150
|
/**
|
|
135
151
|
* The agent server's per-agent settings, for agent turns. The runtime is built before the agent
|
|
136
152
|
* server, so read this when a turn runs, not in the factory; it stays undefined on a host without
|