pi-roundtable 0.22.5 → 0.23.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 +55 -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 +350 -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
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,61 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.23.0] - 2026-10-29
|
|
7
|
+
|
|
8
|
+
Agents can live on the web alone, and the web is an owner app: rooms for the agent team, approval cards, yolo and long-running modes, an installable PWA with push.
|
|
9
|
+
Entries marked **Breaking** need a change from a host or plugin that uses the thing they name.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `pi-roundtable-web`: the owner app. The web package becomes a chat surface of its own: rooms for the agent team with approval cards, Stop, uploads of any kind up to 100 MiB, yolo and long-running switches, schedules and an agent profile; `auth: none | password | verifier`; an installable PWA; Web Push; composed characters, the Modern and Camelot looks and a round table. It needs no Discord. The package's own changelog and README have the detail; the pieces of the core it stands on are the entries below.
|
|
14
|
+
- Agent rooms on any surface (`pi-roundtable`). A `ChatSurface` that declares `agentRooms: true` hosts a room for each agent and group of the agent team, `<surface>:agent.<name>` and `<surface>:group.<name>`, built with `agentRoomKey` and `groupRoomKey` and read with `roomOf`. A room is a conversation of its own, with its own session, held actions and, for a group, log (`<group>@<surface>`) and seats, run by the same team, apart from the agent's Discord channel. The team replies in a room through `surfaces.sendReply` with the new `OutboundReply.author` (`PostAuthor`) and `surfaces.interim(channel, author)`, never through a Discord webhook. The agent server claims a room only on a surface that declares `agentRooms` and only for an agent or group the team has; it never claims another key of that surface. `ChatSurface.readAttachment(channel, ref)` returns the bytes of an attachment the surface made, which is then never fetched over the network. `SurfacePort.roomSurfaces()` lists the surfaces with rooms, and `AgentTeam` gains `coordinator()` and `roomsOf(name)`; archiving clears the schedules and sessions of every room. `AgentTeam`, `SurfacePort` and `OutboundReply` gain members, so a host that implements them in tests adds them. Discord behaves as before. See [agent rooms on any surface](docs/plugins.md#agent-rooms-on-any-surface). A reply in a room on a surface that does not declare `supportsFiles` is offered no reply files, as in other conversations, instead of being lost whole. `schedule_list` from a room reads the other agent's room, and the dashboard counts an agent's schedules, waiting turns and context use across its Discord channel and its rooms. A turn the team runs in a room reports its progress through `SurfacePort.progress`, as a turn run through `context.turns` does, so a surface can show the text and tools as they go.
|
|
15
|
+
- Agent team without Discord (`pi-roundtable`). `agents` and `skills` are allowed without `discord`: the host assembles the agent server and the skills addon whenever `agents` is not empty, and the team lives in the rooms of a surface that declares `agentRooms`. The new `coordinator` key names the coordinating agent (default the first seed; a `ConfigError` together with `discord`, where the entry channel's agent coordinates). The team's id is `LOCAL_TEAM_ID` (`"local"`) in place of the guild's id. The start fails when no installed surface hosts rooms ("agents need a surface that hosts their rooms, such as pi-roundtable-web"). Without Discord channels no channel is made, arranged or read (`channel_arrange` and `channel_read` say so) and there is no dashboard; the agents' pictures are the path `/avatars/<hash>.png` unless `http.publicUrl` is set, and the surface serves them through `AGENTS.avatars.serve`. `AgentServerOptions.guildId` and `entryChannelId` are optional, `AgentServerOptions.coordinator`, `avatarListener` and `avatarUrl` are optional additions, `AvatarStudioOptions.publicUrl` is optional, and `AgentTeam.channelOf(name, near?)` answers with the agent's room on the asker's surface. See [an agent team without Discord](docs/plugins.md#an-agent-team-without-discord). The plugin's coordinator defaults to the first seed when neither `entryChannelId` nor `coordinator` is given, as the option documents, also for a host that builds the plugin itself.
|
|
16
|
+
- Dispatch threads on any surface (`pi-roundtable`, `pi-roundtable/kit`). A `ChatSurface` can declare `threads`, a `SurfaceThreads` (`open(parent, name, line)`, `post`, optional `send`, `close`), and a background dispatch started in one of its channels (a coding task, a delegated task, a message between agents) opens its thread there; the thread is the conversation `channelKey(surface, id)`, such as `app:thread.<uuid>`. The new `threads` plugin provides `THREADS`, one `DispatchThreads` over the host's surfaces that picks the surface by the prefix of the origin channel, so it works on a host without Discord; `DISCORD.threads` is the same instance, so the coding package and other plugins that read either see no change. The Discord surface implements `SurfaceThreads` through its current connection. `message_agent` from an agent room now opens the exchange's thread in the sender's room when the room's surface hosts threads, as it does in a Discord channel, and posts the request and the answer there under each agent's name. `DispatchThreadsOptions` takes `surfaces`; the ledger of open threads, `dispatch-threads.json`, is keyed by each thread's channel key, and a ledger keyed by Discord thread id is read as before. The sweep after a restart closes only the threads the ledger held at start, never one opened in this run, skips with a warning an entry that is not an object or has no parent channel, lets one failing thread leave the rest alone, and keeps a thread whose surface hosts no threads at this start in the ledger to retry at the next.
|
|
17
|
+
- Per-conversation modes (`pi-roundtable`). A new `modes` plugin provides `MODES` (`ConversationModes`): each conversation key has a `yolo` and a `longRunning` mode, both off by default, stored in a `conversation_modes` table so they survive a restart. `peek(channel)` reads one from memory, `set(channel, { yolo?, longRunning? }, speaker)` changes it and throws `PluginError` unless the speaker is an owner other than `SYSTEM_PRINCIPAL`, `onChange` hears every change, and `autoApproved` with `onAutoApproved` report a held call that yolo mode approved. New types `ConversationMode`, `ModeChange` and `AutoApproval`. The `modes` plugin registers after `conversations` and before `runtime`.
|
|
18
|
+
- Long-running conversations (`pi-roundtable`). In a conversation whose `longRunning` mode is on, a turn that reaches its time limit goes on by itself, as the owner's Continue would, up to `autonomy.longRunning.maxSeconds` of work (default 14400, four hours); then it asks on a card, or aborts when it cannot show one, as before. `ask_user` is never answered automatically. `TurnBudgetSeams.keepGoing` carries the decision, and the log says `long-running mode continued the turn`. The cap is per turn and counts worked time: an extension stops at the cap, so a 3-hour `turnLimits.timeoutSeconds` with a 4-hour cap asks at 4 hours.
|
|
19
|
+
- Turn limits and autonomy in the configuration (`pi-roundtable`). `turnLimits` sets `timeoutSeconds` (60 to 86400, default 600), `maxSeconds` (at least the timeout, default three times it) and `cardSeconds` (30 to 3600, default 300), which `runtimePlugin` passes to the runtime as `turnTimeoutMs`, `turnMaxMs` and `turnTimeoutCardMs`; they were fixed before. `autonomy` holds `yolo.allow`, `yolo.card` and `longRunning.maxSeconds`, passed to `PiAgentRuntimeOptions` as `modes` and `autonomy`. Both keys are typed by the new `LimitsConfig`, which `RoundtableConfig` extends. The time-limit card waits `turnLimits.cardSeconds`, 5 minutes by default.
|
|
20
|
+
- Yolo mode (`pi-roundtable`). In a conversation whose `yolo` mode an owner switched on, the server approves a held call itself, before a card opens, when all of these hold: the turn's speaker is an owner other than `SYSTEM_PRINCIPAL`, the call names no approvers and its card is not the speaker's alone, and the call's hold classes are all allowed. The approval is reported as `ApprovedBy.auto: "yolo"` (`ToolTurn.approvedBy` shows it), logged as `held call approved by yolo mode` and told to `MODES.onAutoApproved` listeners; a call held without classes always asks, and a card opened before the toggle stays open. **A plain `git push` and a write outside the workspace are approved under yolo** (classes `shell.push` and `shell.write`); put `shell.push` or `shell.write` in `autonomy.yolo.card` to keep them behind a card, and `autonomy.yolo.allow` to also approve a carded class such as `shell.rm`. A plugin rule that reports no classes leaves its calls unclassed and yolo asks on a card for them until `autonomy.yolo.allow` lists the rule's name (`tool:<name>` for a tool's own hold); a rule that reports classes has opted its calls in. Saving a precheck script is classed by every held call the script makes. Yolo covers every owner-attributed turn in the conversation, including those another agent or a schedule starts. Messages that confirm a held call work as before. See [hold classes and yolo mode](docs/plugins.md#hold-classes-and-yolo-mode). `autonomy.yolo.card: ["precheck-scripts"]` cards saving a precheck script (the rule is `HoldRule.relay`, whose calls carry `<rule>.relay`); a rule that is not a relay cannot report another rule's class ids, so a rule `mail` cannot escape `card: ["mail"]` by reporting `shell.write`, and classes a custom hold check names in a description are dropped. A redirect or `tee` target the shell cannot expand (`> $DISK`) is `shell.disk`, not `shell.write`. A precheck script's call whose arguments are computed when the script runs is unclassed, under its tool's own rule names too, so `autonomy.yolo.allow: ["precheck-scripts"]` does not approve it.
|
|
21
|
+
- Hold classes (`pi-roundtable`, `pi-roundtable/kit`). `HoldRule.holdClasses(tool, input, context)` lists the `<rule>.<class>` ids of a call a rule holds (the rule's bare name when absent or not named after the rule, which marks the call unclassed; a rule name has no dot), `HoldDescription.classes` names them from a description, `holdChain` reports the classes of every rule that holds the call as `classesOf` and `inTurnWithClasses` (the new type `ClassedHold`), and the gate records them with the call as `HeldCall.holdClasses`, which persists with pending confirmations. `YOLO_CARDED_SHELL_CLASSES` lists the shell classes yolo never approves unless `autonomy.yolo.allow` names them: all of `shell.privilege`, `shell.disk`, `shell.power`, `shell.kill`, `shell.rm`, `shell.service`, `shell.docker`, `shell.firewall`, `shell.packages`, `shell.force-push`, `shell.reset-hard`, `shell.gh`, `shell.crontab` and `shell.unreadable`, but not `shell.write` or `shell.push`.
|
|
22
|
+
- `ApprovalOptions.purpose` (`"action"` or `"turn-timeout"`) and `ApprovedBy.auto` (`"yolo"`) (`pi-roundtable`). The card at a turn's time limit now carries `purpose: "turn-timeout"`, so a surface can tell it from a held call's approval; Discord ignores it. `ApprovedBy.auto` marks an approval the server made itself in yolo mode and no person answered.
|
|
23
|
+
- Large attachments from a surface (`pi-roundtable`). `ChatSurface.maxAttachmentBytes` raises the 25 MiB a turn takes from a surface (Discord keeps it), and `attachmentsOf` takes it as `maxBytes`; it holds a ref's reported size, a file, a stream and a download alike, and a file over it is `larger than <n> MB`. `ChatSurface.readAttachment` (and `attachmentsOf`'s `read`) may now answer an `AttachmentContent`: the bytes, `{ path }` of a file on the host (`AttachmentFile`, copied into the conversation's store and left in place) or a `ReadableStream<Uint8Array>`; the last two are stored as a stream and never held whole in memory, and the limit is counted as they are copied. A stored file over 25 MiB is not inlined: an image is not shown to the model, `read_attachment` refuses a text or PDF file that large, and the `## Attachments` block marks it `too large for read_attachment; kept as a file for tools`. `ToolAttachment` gains `stream()` so a tool reads such a file without buffering it. New exports `AttachmentContent`, `AttachmentFile` and `MAX_ATTACHMENT_BYTES`. See [files from outside a turn](docs/plugins.md#contextattachments-files-from-outside-a-turn).
|
|
24
|
+
- A request body limit on HTTP listeners (`pi-roundtable`). The listener now sets Bun's `maxRequestBodySize` explicitly, on TCP ports and unix sockets: 128 MiB (`DEFAULT_MAX_REQUEST_BODY_BYTES`, the same as before) unless `HttpRoute.maxRequestBodyBytes` asks for more or less (the largest value among the listener's routes) or the host sets `ListenerConfig.maxRequestBodyBytes`, which decides. A body over it is answered `413` before a handler runs; a value that is not a positive integer is refused at start. A plugin that takes big uploads sets its route's limit from its own upload limit plus the multipart overhead. See [`http`: routes on the bot's listener](docs/plugins.md#http-routes-on-the-bots-listener).
|
|
25
|
+
- A plugin can read where a listener serves (`pi-roundtable`). `context.listener(id)` returns the listener's configured address, `{ socketPath, mode? }` or `{ port, hostname? }`, as a frozen copy without the id, or `undefined` for an id the host has no listener for. A plugin that serves without authentication, such as the web console's `auth: { mode: "none" }`, reads it to refuse an address that reaches beyond the machine. The `testPlugin` harness takes the addresses as `listeners`. See [the context](docs/plugins.md#the-context).
|
|
26
|
+
- `DirectChannelProvider.mirror?(principalId, text)` (`pi-roundtable`). After `notify` delivers a notice through one provider, it calls `mirror` on every other provider that has one, so a channel such as Web Push can echo what a direct message already carried. It runs only after a delivery, its failures are logged and never thrown, and it never changes what `notify` returns. See [`directChannels`](docs/plugins.md#directchannels-reaching-a-person-on-their-own).
|
|
27
|
+
- Composed characters (`pi-roundtable/characters`, `pi-roundtable/characters.css`). A new browser-safe entry draws an agent as a character built from parts, so a look is a few ids and three colors instead of a generated picture. `CATALOG` holds the 24 classes (`CLASS_IDS`), their outfits, hats, props, hair, beards, accessories, eight palettes, six skin tones and hair swatches; `CharacterSpec` is a resolved look (with `CharacterColors`, `CharacterInput`, `PaletteName`, `Hex` and the part ids `OutfitId`, `HatId`, `PropId`, `HairId`, `BeardId`, `AccessoryId`, `ClassId`). `resolveCharacter` makes one from an input over a base or a class preset, `parseCharacter` checks one strictly and `readCharacter` reads a stored one leniently, and a bad one throws a `CharacterError` whose `field` names the offending field. `suggestCharacter` chooses a look from an agent's name and prompt. `composeSvg` returns an SVG string with literal colors, none of `style`, `var(`, `<style>`, scripts, links or event handlers and at most 8 KB, for 13 `EXPRESSIONS` and seven `AVATAR_STATES` (type `AvatarState`; `STATE_EXPRESSION` gives each state's face), in four `CharacterLook`s and two frames, with `ComposeOptions` choosing among them; the page can inject it under a content security policy that forbids inline styles and `canvas` rasterizes it as it is. `accentOf` shades a look's primary color to a readable accent, `characterCss` is the text of the stylesheet `characters.css`, which moves the figures by `data-state`. The art is the approved lab's: a test rasterizes the lab's 38 reference drawings and the port's pictures of the same looks and finds them identical. The package `files` leave out `src/characters/fixtures`. See [Characters](docs/plugins.md#characters-composed-pictures-for-agents).
|
|
28
|
+
- Agent characters in core (`pi-roundtable`, `pi-roundtable/kit`). Every agent is a composed character: `Agent.character` (a `CharacterSpec`) is stored in a new nullable `agents.character` jsonb column (migration `agents-character`, add-only), `characterOf` returns it or the look its role suggests, and `AgentSeed.character` and `agents[].character` in the configuration (a `CharacterInput`, checked as `resolveCharacter` does) set a seed's look. `AgentTeam.dress(name, input)` changes a look, keeping the agent's colors when only the class changes, and `AvatarStudio.render(spec)` renders a character to a 512 px PNG stored by hash, which is the agent's Discord picture; `renderCharacterPng` rasterizes one. A new agent gets the look its role suggests unless `agent_create` gives one, and the team start dresses every active agent without a character from its seed or role (idempotent). The agent server refuses to start when `canvas` cannot rasterize SVG: the start fails as a `PluginError` whose `cause` is a `ConfigError` ("SVG support"). A plugin seed with a bad `character` is a `PluginError` naming the plugin, seed and field. `suggestCharacter` draws `build` from its own hash instead of following `skin`. See [Agent characters](docs/plugins.md#agent-characters).
|
|
29
|
+
- `accentOf(spec, on)` (`pi-roundtable/characters`) also takes a look (`camelot-light`, `camelot-dark`, `modern-light`, `modern-dark`) and shades the accent for that look's surfaces, so a ring reads at 3:1 on Camelot's parchment and night blue; a scheme still means the modern page.
|
|
30
|
+
- `OutboundReply.failed` (`pi-roundtable`). The reply for a turn that ended in an error, not a stopped one, carries `failed: true`, so a surface can show the turn as failed; Discord ignores it.
|
|
31
|
+
- Doctor check `characters` (`roundtable doctor`, `roundtable start`): rasterizes one composed character with `canvas`, the probe the agent server runs at start, and fails with the reason and the librsvg fix when it cannot.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Breaking:** `AgentGroup.channelId` and `GroupStatus.channelId` are optional (`pi-roundtable`), because a group of a team without Discord has no Discord channel; a plugin that reads them handles `undefined`. The agent server's tables gain the migration `agent-groups-channel-optional` (`agent_groups.channel_id` drops `NOT NULL`). On a host without Discord but with `agents`, the agent server now runs where 0.22 refused the configuration, and it needs a surface that hosts rooms. `AgentChannelLookup` takes the asker's conversation as a second argument.
|
|
36
|
+
- Agent tools and the Discord profile panel choose looks instead of drawing (`pi-roundtable`). **Breaking:** `agent_create` loses `avatar_prompt` and takes `class` (one of the 24) and `color` (one of the 8 palettes); `agent_avatar` keeps its name and tier (`toolTiers` configs stay valid) but changes a look, `{ name, class?, color?, parts? }`, through `AgentTeam.dress`, and is offered on every host (the `redraw`, `new_prompt` and `edit` modes and the image-provider gate are gone); `agent_get` shows `Look: <class>, <palette or color>` instead of the avatar prompt; the agent prompt says "agent_avatar changes an agent's look (class, colors, parts)". `/profile` replaces the redraw, new-prompt and edit-avatar buttons with a class select (24) and a color select (8), which edit the reply with the new picture; a panel posted before 0.23 answers its old buttons that it is out of date.
|
|
37
|
+
- **Breaking for a caller that reads the result as bytes:** `ChatSurface.readAttachment` now returns `Promise<AttachmentContent>` instead of `Promise<Uint8Array>` (`pi-roundtable`). Check for the other two forms (`{ path }` and a stream); a surface that returns bytes needs no change. `context.attachments.save` stays capped at 25 MiB.
|
|
38
|
+
- **Breaking for a plugin that builds `DispatchThreads`:** `DispatchThreadsOptions` takes `surfaces` (any surface that declares `threads`) instead of `host`; `host`, a `ThreadHost`, is optional and serves `discord:` channels only. A thread's `DispatchThread.id` is its id on its surface and `channel` is `channelKey(surface, id)`; `mention` comes from the surface (`<#id>` on Discord). The threads' sweep after a restart runs once every service has started, as the `threads` plugin's background start, rather than inside the Discord plugin's service start. The ledger, `dispatch-threads.json`, is keyed by channel key; one keyed by Discord thread id is read as before.
|
|
39
|
+
- **Breaking for the web package:** the default `panes` of `pi-roundtable-web` become `rooms`, `table`, `overview`, `schedules`, `conversations`, `notes` (they were `overview`, `conversations`, `notes`). An explicit list is kept as written, so a host that listed its panes adds the new ones by hand. See the package's changelog.
|
|
40
|
+
- The shell hold rule reports the class of every held command in a line, including those inside `bash -c`, instead of stopping at the first. A push that deletes, mirrors or prunes refs (`--delete`, `-d`, `--mirror`, `--prune`, a `:ref` refspec) or uses a `+` refspec, and a combined short flag such as `-fu`, are now `shell.force-push`; a redirect or `tee` to a device node other than `/dev/null` is `shell.disk`; the held reasons of other commands are unchanged.
|
|
41
|
+
- The doctor's `images` check no longer says the slot draws avatars.
|
|
42
|
+
- **Downgrading from 0.23 to 0.22** leaves the dispatch threads open: 0.23 keys `dispatch-threads.json` by channel key (`discord:<id>`), which 0.22 does not read as a thread, so threads opened by 0.23 are not closed by a 0.22 start.
|
|
43
|
+
|
|
44
|
+
### Deprecated
|
|
45
|
+
|
|
46
|
+
- `ThreadHost` (`pi-roundtable/kit`) is deprecated until 1.0 in favour of `ChatSurface.threads`; pass `surfaces` to `DispatchThreads` instead of `host`.
|
|
47
|
+
- Drawn avatars (`pi-roundtable`): `AgentSeed.avatarPrompt` and `agents[].avatarPrompt` are ignored and optional, and `AvatarStudio.draw`, `edit`, `fallback` and `canDraw` have no caller in core; all are removed in 1.0. **Every existing agent's Discord picture is replaced by its character at the first start.** `AvatarStudio` gains `render` and `AgentTeam` gains `dress`, which a plugin that implements them adds.
|
|
48
|
+
- `AgentTeam.redrawAvatar` and the `AvatarMode` type (`pi-roundtable`) still work as before for callers that use them (they need an image provider) but nothing in core calls them any more; use `AgentTeam.dress`. Both are removed in 1.0.
|
|
49
|
+
- `config.avatar` (`pi-roundtable`) is ignored: when it is set the start logs one warning ("agents are composed characters since 0.23; `avatar` is ignored"), and the agents' fallback picture is always the plain bot. The `images` slot stays for `generate_image`.
|
|
50
|
+
|
|
51
|
+
### Security
|
|
52
|
+
|
|
53
|
+
- A seeded generative differential test for the shell hold rule (`shell-policy.fuzz.test.ts`) composes random command lines from the grammar of bash, plants `sudo marker` in them, and runs each in bash 3.2 and 5 on a PATH of stubs: whenever bash runs the stub, the rule must hold the line. A long run (more than 100,000 lines) found gaps that the hand corpus had missed, all now held (the minimized lines are in the hand corpus too): a `NAME+=value` before the command (`A+=b sudo x`); a wrapper option that takes an argument, so that the argument was read as the command (`env -u FOO sudo x`, `exec -a name sudo x`, `timeout -s KILL 5 sudo x`, `nice -n 5`, `stdbuf -o L`, `xargs -I {} sudo x`, `-n`, `-P`, a fractional `timeout` duration), and `env -S`, which splits its argument into a command line and is now held as `shell.unreadable`; a descriptor duplicated or closed for input in front of the command (`<&0 sudo x`, `0<&- sudo x`); a subscript that a builtin evaluates (`declare 'a[$(sudo x)]=1'`, `read`, `printf -v`, `typeset`, `local`, `export`, `unset`), now `shell.unreadable`; `PS4`, `PROMPT_COMMAND` and the other self-running variables assigned on their own (`PS4='$(sudo x)'; set -x`) or after `!`; a backslash before a double quote in a backtick substitution inside double quotes, which bash takes out; a variable the line writes more than once, whose value the rule cannot follow through a loop, `read`, `declare`, `eval`, a function or a branch (`x=ls; for x in sudo; do $x; done`), which is now unknown, so a command name or an `rm` operand built from it is held; and a shell that reads its script where the line does not show it as a word: `echo 'sudo x' | bash`, `bash <<< 'sudo x'`, a here-document, `source <(…)`, `. /dev/stdin`, a file the line wrote earlier (`echo 'sudo x' > f; bash f`, `./f`, `source f`), and anything `xargs` fills in from its input (`xargs bash`, `xargs env`, `xargs -I{} {}`), all held as `shell.unreadable`. Running a script file the line did not write stays unheld, as does any other program. See [the shell rule](docs/plugins.md#the-shell-rule) for how to run the fuzz.
|
|
54
|
+
- The web app can run turns, approve held actions and turn yolo mode on, so its authentication guards the owner's power over the agents. `auth: { mode: "none" }` and `{ mode: "password" }` resist the network but not an agent whose shell runs as the service user, which can reach the listener's socket, the password variable and the session key, and so act as the owner. Only a `verifier` (for example Cloudflare Access, whose signed token an agent cannot forge) resists it. The package's README has the threat model.
|
|
55
|
+
- Yolo mode approves a held call itself, so it is bounded: only for an owner other than `SYSTEM_PRINCIPAL` (a webhook or a report turn never counts), never for a call that names approvers or is the speaker's alone, never for a class `YOLO_CARDED_SHELL_CLASSES` lists unless `autonomy.yolo.allow` names it, and a call held without classes always asks. **A plain `git push` and a write outside the workspace are approved under yolo.**
|
|
56
|
+
- The shell hold rule no longer holds arithmetic. Words inside `$(( ))`, `(( ))` and `for ((;;))` were read as a command, so `echo $(( $n + 1 ))`, `n=$(( $(date +%s) - start ))` and `(( $n > 3 )) && echo big` were held as `shell.unreadable` in every mode; they are dropped once the group closes with `))`, while a `$(…)` or backtick inside still runs and is judged, and `((a); (b))`, `$((a); (b))` and `$( (a) )` are subshells as before. `bash -c 'echo $(date)'` is no longer held: a script is held whole only when the outer shell expands a substitution in it (an unquoted or double-quoted `$(…)`, as in `bash -c "echo $(date)"`); a literal script is judged like any line, so `bash -c 'sudo x'` still holds.
|
|
57
|
+
- The shell hold rule closes the gaps the widened differential test found: `trap 'sudo x' EXIT|ERR|DEBUG` is judged as a script; `alias NAME=…`, an assignment to `PROMPT_COMMAND`, `PS0`, `PS1`, `PS2`, `PS4`, `BASH_ENV`, `ENV` or `SHELLOPTS`, and `${x@P}` are held as `shell.unreadable`; a NUL byte is dropped as bash drops it; and a line that evaluates arithmetic (`(( ))`, `$(( ))`, `$[ ]`, `let`, `declare -i`, `[[ a -eq b ]]`) and also holds a `$(` or backtick in single quotes or behind an escape is held, because arithmetic runs a `$(` in the value of a variable (`x='a[$(sudo x)]'; echo $((x))`). The last one also holds `echo '$(date)'; echo $((1 + 2))`, a known cost of failing closed.
|
|
58
|
+
- The shell hold rule fails closed. Its lexer is rewritten to read a command line the way bash does, because several forms let a destructive command run unheld in every mode: a quote closed inside a nested substitution (`echo "$(echo "x$(echo ")")" ; rm -rf ~)"`), a comment in a quoted substitution (`echo "$(echo hi # )⏎rm -rf ~)"`), a `case` pattern's `)` (`echo "$(case x in x) rm -rf ~;; esac)"`), `\'` inside `$'…'`, an apostrophe in a here-document body (`cat <<EOF⏎it's⏎EOF⏎rm -rf ~`), `${#x}` and `{#}` read as comments, and a line continuation inside a word. It also judges the command behind a leading keyword (`then sudo ls`, `do rm -rf /x`), a descriptor redirection (`3>&1 sudo ls`), `eval`, `bash -c` with clustered options (`-ec`) and `find -exec`; `$'\\x73udo'` is read as `sudo`, and a command whose name is a brace expansion, a pattern or a variable not known (`{sudo,ls}`, `/usr/bin/su*`, `$cmd`) is held as `shell.unreadable`, as is a brace expansion in an `rm` operand. A line it cannot read to the end (over 200,000 characters, nested deeper than 48 levels, a here-document or `case` it cannot place) is held as the new class `shell.unreadable`, which yolo mode cards like every class but `shell.write` and `shell.push`; so is `eval` or `bash -c` of a script built from a variable or a substitution. The old scan was quadratic and overflowed the stack at about 20,000 nested substitutions; the new one is linear. A hold rule that throws, or a `describe` or `holdClasses` that fails in the shell rule, now holds the call, and the `tool_call` handler blocks the call with the failure as its reason (pi already turned a throwing handler into an error result and did not run the call; the block says why). A differential test runs a corpus of lines with real bash (3.2 and 5, on a PATH of stubs): whenever bash runs `sudo`, the rule holds the line.
|
|
59
|
+
- The shell hold rule judges a command or backtick substitution inside double quotes like an unquoted one: `echo "$(rm -rf ~)"`, `x="$(dd if=/dev/zero of=/dev/sda)"` and ``echo "`sudo reboot`"`` were copied as text and ran unheld in every mode. Single quotes still hide their content, and an unterminated substitution is judged to its end. A substitution in the target of a redirect or `tee` also cards under yolo (shell.disk); that is a known cost of failing closed.
|
|
60
|
+
- The shell hold rule checks the redirect operators `>|`, `>& file` (a target that is not a descriptor number) and `<>` like `>`, so `echo x >| /dev/sda` is `shell.disk`; `2>&1` is unchanged. `tee` is recognised behind a path or a wrapper (`/usr/bin/tee`, `env tee`), so its substituted target is unknown too.
|
|
7
61
|
|
|
8
62
|
## [0.22.5] - 2026-10-25
|
|
9
63
|
|
package/README.md
CHANGED
|
@@ -65,16 +65,17 @@ A web chat project asks for `OWNER_NAME`, `DATABASE_URL`, and `MODEL`, and for t
|
|
|
65
65
|
1. Bun's version.
|
|
66
66
|
2. `.env` has a value for every variable `.env.example` lists.
|
|
67
67
|
3. `roundtable.config.ts` against its schema, naming the failing key.
|
|
68
|
-
4. Every plugin loads,
|
|
68
|
+
4. Every plugin loads, no two share a name, and the registration is valid (`Roundtable.check()`).
|
|
69
69
|
5. Whom `access` serves: the owners, and a warning when `everyone: true` admits everyone on every surface of a host with more than one.
|
|
70
70
|
6. Whether a plugin fills the `images` slot.
|
|
71
|
-
The check passes with or without one; agents
|
|
72
|
-
7.
|
|
73
|
-
8.
|
|
74
|
-
9.
|
|
71
|
+
The check passes with or without one; agents' avatars are composed characters and no longer drawn with it.
|
|
72
|
+
7. Whether `canvas` can rasterize SVG, which the agent server needs for the agents' pictures.
|
|
73
|
+
8. PostgreSQL is reachable and migratable.
|
|
74
|
+
9. The principals: a dry run of what the next start makes from the people the database already knows, printed as the start logs it; a configured owner's identity linked to someone else, which stops the start; and a disabled owner.
|
|
75
|
+
10. With Discord configured: the Discord token is valid, the bot is in your server, the Message Content intent is on, and the bot has the permissions it needs in the entry channel (including Pin Messages).
|
|
75
76
|
When the bot is not in the server, the fix is an invitation link that asks for exactly those permissions.
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
11. The model login exists.
|
|
78
|
+
12. When `http.publicUrl` is set: `PUBLIC_URL` is a well-formed address; with `--reachable` it also has to answer, which is only true while the bot runs.
|
|
78
79
|
|
|
79
80
|
It exits non-zero on any failure, not on a warning, and changes nothing it checked.
|
|
80
81
|
A fresh project fails only on the credentials you have not entered yet, and says which.
|
|
@@ -160,6 +161,11 @@ bun add pi-roundtable-mcp
|
|
|
160
161
|
|
|
161
162
|
It releases in lockstep with pi-roundtable, and its README lists every option.
|
|
162
163
|
|
|
164
|
+
## Official packages
|
|
165
|
+
|
|
166
|
+
Eight packages in this repository release in lockstep with the core: webchat and mcp above, plus [browser](packages/browser), [coding](packages/coding), [drawing](packages/drawing), [line](packages/line), [sandbox](packages/sandbox) and [web](packages/web).
|
|
167
|
+
The [plugin guide](docs/plugins.md#official-plugins) describes each and what it needs.
|
|
168
|
+
|
|
163
169
|
## Settings
|
|
164
170
|
|
|
165
171
|
`roundtable.config.ts` holds the settings and the list of plugins.
|
|
@@ -176,6 +182,24 @@ export default {
|
|
|
176
182
|
} satisfies RoundtableConfig;
|
|
177
183
|
```
|
|
178
184
|
|
|
185
|
+
A turn works 10 minutes by default before it asks you whether to go on, and 30 minutes at most in all; `turnLimits` and `autonomy` change that, and a conversation you switch to long-running mode goes on by itself up to `autonomy.longRunning.maxSeconds`:
|
|
186
|
+
|
|
187
|
+
| Setting | Default | What it is |
|
|
188
|
+
| --- | --- | --- |
|
|
189
|
+
| `turnLimits.timeoutSeconds` | `600` | A turn's budget before the card asks to continue, 60 to 86400; each message steered into the turn renews it |
|
|
190
|
+
| `turnLimits.maxSeconds` | three times `timeoutSeconds` | The most one turn works in all, at least `timeoutSeconds` |
|
|
191
|
+
| `turnLimits.cardSeconds` | `300` | How long that card waits for an answer, 30 to 3600; with none, the turn stops |
|
|
192
|
+
| `autonomy.longRunning.maxSeconds` | `14400` (4 h) | The most a long-running conversation's turn works before it asks or stops, 600 to 604800 |
|
|
193
|
+
| `autonomy.yolo.allow`, `autonomy.yolo.card` | none | Hold classes yolo mode may approve beyond its defaults (`shell.rm`, say), and rule names whose calls it approves although the rule reports no classes (`tool:repo_push`, say), and classes or rule names it never approves (`shell.push`, say). By default yolo approves a plain `git push` and writes outside the workspace and cards the other destructive shell classes; see the [plugin guide](docs/plugins.md#hold-classes-and-yolo-mode) |
|
|
194
|
+
|
|
195
|
+
Without `discord` the agents still run when `agents` lists them, in the rooms of a surface that hosts them, such as pi-roundtable-web; the host refuses to start when none does:
|
|
196
|
+
|
|
197
|
+
| Setting | Default | What it is |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `agents` | none | The first team, created once. Without `discord`, a seed has no `channelId` and the team's rows are kept under the id `local` |
|
|
200
|
+
| `coordinator` | the first of `agents` | Without `discord`, the agent the owner's general requests come to; it cannot be archived. With `discord` it is an error: the entry channel's agent coordinates |
|
|
201
|
+
| `skills` | on with `agents` | The skills addon; without `discord` it is on only when `agents` lists some |
|
|
202
|
+
|
|
179
203
|
When someone addresses the assistant in an agent's channel, the turn also reads the channel messages posted since the assistant's last post there (channel context; see the [plugin guide](docs/plugins.md#channel-context-what-was-said-around-an-addressed-message)).
|
|
180
204
|
Tune it under `discord.channelContext`, or set `channelContext: false` to turn it off:
|
|
181
205
|
|
|
@@ -202,10 +226,12 @@ When a conversation is started over in a server channel or thread, the assistant
|
|
|
202
226
|
| `false` | | Tell the model nothing |
|
|
203
227
|
|
|
204
228
|
An approval card or an `ask_user` question (a set of up to four questions is one card) holds its turn for `discord.cardGraceSeconds` (default `120`).
|
|
205
|
-
The files of a message in an agent's channel download within `discord.attachmentDownload.deadlineMs` (default 60,000) for the message and the one it replies to together, and a file that sends no byte for `discord.attachmentDownload.idleMs` (default 10,000) is given up; the turn goes on without a file it could not get.
|
|
206
|
-
The copy button of an approval card keeps its text in `card-copies.json` under the data directory, the newest 100 for 30 days; `discord.cardCopies` (`{ keepDays?, keep? }`) changes that, and `false` keeps them in memory only.
|
|
207
229
|
Answered within it, the turn goes on with the answer; otherwise the turn ends saying it is waiting, the card stays open with no deadline, and answering it later starts a new turn in that conversation with the answer, or with the result of the approved action.
|
|
208
230
|
A coding worker's cards keep the worker waiting instead.
|
|
231
|
+
Every `ask_user` card has a **Cancel** button beside Submit: pressed while the turn waits, the model is told you cancelled the question and does not ask it again.
|
|
232
|
+
A turn that reaches its time limit (the built-in runtime's budget, 10 minutes by default, or its cap, 30 by default; see `turnLimits`) asks before it ends, on a card that names the limit and what the turn is doing, with **Continue** and **Cancel**; with no answer within `turnLimits.cardSeconds` (5 minutes by default) the turn is aborted.
|
|
233
|
+
The copy button of an approval card keeps its text in `card-copies.json` under the data directory, the newest 100 for 30 days; `discord.cardCopies` (`{ keepDays?, keep? }`) changes that, and `false` keeps them in memory only.
|
|
234
|
+
The files of a message in an agent's channel download within `discord.attachmentDownload.deadlineMs` (default 60,000) for the message and the one it replies to together, and a file that sends no byte for `discord.attachmentDownload.idleMs` (default 10,000) is given up; the turn goes on without a file it could not get.
|
|
209
235
|
|
|
210
236
|
## Threat model
|
|
211
237
|
|
package/README.zh-TW.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
## 你需要準備
|
|
18
18
|
|
|
19
|
-
- [Bun](https://bun.sh/docs/installation) 1.
|
|
19
|
+
- [Bun](https://bun.sh/docs/installation) 1.4.4 以上。
|
|
20
20
|
- PostgreSQL。`init` 建立的專案附有 `docker-compose.yml`,可以直接啟動一個。
|
|
21
21
|
- 模型登入:你選的模型的供應商 API key(`anthropic/...` 用 `ANTHROPIC_API_KEY`),或之前用 Pi 登入過的帳號。
|
|
22
22
|
- 用 Discord 的話:一個 Discord bot(有 bot 使用者、開了 Message Content intent,也已經邀請進你的伺服器),以及一個從網際網路連得到這個行程的位址,例如通道(tunnel)。Discord 要從這個位址抓智慧體的頭像。
|
|
@@ -62,15 +62,16 @@ Bun 會自己讀 `.env`,`.gitignore` 也已經擋掉它。
|
|
|
62
62
|
1. Bun 的版本。
|
|
63
63
|
2. `.env` 對 `.env.example` 列出的每個變數都有值。
|
|
64
64
|
3. `roundtable.config.ts` 符合其 schema,失敗時指出是哪個鍵。
|
|
65
|
-
4.
|
|
65
|
+
4. 每個外掛都能載入、沒有兩個外掛同名,而且註冊內容有效(`Roundtable.check()`)。
|
|
66
66
|
5. `access` 服務哪些人:列出 owner;主機有不只一個 surface、又寫了 `everyone: true` 時提出警告,因為它會讓每個 surface 上的所有人都能對話。
|
|
67
|
-
6. 有沒有外掛填了 `images`
|
|
68
|
-
7.
|
|
69
|
-
8.
|
|
70
|
-
9.
|
|
67
|
+
6. 有沒有外掛填了 `images` 槽位。沒有也不算失敗,智慧體的頭像是組合出來的角色,不再靠它繪製。
|
|
68
|
+
7. `canvas` 能不能把 SVG 轉成圖片;agent 伺服器畫智慧體的圖片需要它。
|
|
69
|
+
8. PostgreSQL 連得上,且能執行 migration。
|
|
70
|
+
9. principal:預演下次啟動會從資料庫已知的人建立哪些 principal,印出的文字和啟動時的 log 相同;設定裡 owner 的身分若連到別的 principal 就判定失敗,因為主機會因此無法啟動;owner 被停用時提出警告。
|
|
71
|
+
10. 有設定 Discord 時:Discord token 有效、bot 已在你的伺服器裡、Message Content intent 已開啟,而且 bot 在入口頻道有它需要的權限(包含 Pin Messages)。
|
|
71
72
|
bot 還不在伺服器裡的話,它會給你一個邀請連結,連結已經帶好這些權限。
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
11. 模型登入存在。
|
|
74
|
+
12. 設了 `http.publicUrl` 時:`PUBLIC_URL` 的格式正確;加上 `--reachable` 還要求它有回應,所以 bot 得先跑起來。
|
|
74
75
|
|
|
75
76
|
只要有一項失敗就以非零狀態結束,警告不算失敗;檢查過程不會改動任何東西。
|
|
76
77
|
全新的專案只會卡在還沒填的憑證,它會列出是哪幾個。
|
|
@@ -155,6 +156,11 @@ bun add pi-roundtable-mcp
|
|
|
155
156
|
|
|
156
157
|
它與 pi-roundtable 同步發版,每個選項都列在它的 README。
|
|
157
158
|
|
|
159
|
+
## 官方套件
|
|
160
|
+
|
|
161
|
+
這個 repository 裡有八個套件與核心同步發版:上面的 webchat 和 mcp,加上 [browser](packages/browser)、[coding](packages/coding)、[drawing](packages/drawing)、[line](packages/line)、[sandbox](packages/sandbox) 和 [web](packages/web)。
|
|
162
|
+
[外掛指南](docs/plugins.md#official-plugins)(英文)說明每一個套件和它需要什麼。
|
|
163
|
+
|
|
158
164
|
## 設定
|
|
159
165
|
|
|
160
166
|
`roundtable.config.ts` 放設定和外掛清單。
|
|
@@ -171,6 +177,24 @@ export default {
|
|
|
171
177
|
} satisfies RoundtableConfig;
|
|
172
178
|
```
|
|
173
179
|
|
|
180
|
+
一輪預設工作 10 分鐘後會問你要不要繼續,總共最多 30 分鐘;用 `turnLimits` 與 `autonomy` 調整。切到長時間執行模式的對話,會自己繼續,直到 `autonomy.longRunning.maxSeconds`:
|
|
181
|
+
|
|
182
|
+
| 設定 | 預設 | 說明 |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `turnLimits.timeoutSeconds` | `600` | 一輪在卡片詢問是否繼續之前的工作時間,60 到 86400;插入這一輪的每則訊息都會重新計時 |
|
|
185
|
+
| `turnLimits.maxSeconds` | `timeoutSeconds` 的三倍 | 一輪總共最多工作多久,不得小於 `timeoutSeconds` |
|
|
186
|
+
| `turnLimits.cardSeconds` | `300` | 那張卡片等你回答多久,30 到 3600;時間到沒回答,這一輪就停止 |
|
|
187
|
+
| `autonomy.longRunning.maxSeconds` | `14400`(4 小時) | 長時間執行對話的一輪,工作到這個上限才會詢問或停止,600 到 604800 |
|
|
188
|
+
| `autonomy.yolo.allow`、`autonomy.yolo.card` | 無 | yolo 模式在預設之外可以核准的 hold 類別(例如 `shell.rm`),以及它也核准其呼叫的規則名稱(該規則沒有回報類別,例如 `tool:repo_push`),以及它永遠不核准的類別或規則名稱(例如 `shell.push`)。預設下 yolo 會核准一般的 `git push` 和工作區以外的寫入,其餘破壞性的 shell 類別仍然出卡;見[外掛指南](docs/plugins.md#hold-classes-and-yolo-mode) |
|
|
189
|
+
|
|
190
|
+
沒有 `discord` 時,只要 `agents` 列了 agent,它們就在支援房間的 surface(例如 pi-roundtable-web)裡運作;沒有任何 surface 支援房間時 host 拒絕啟動:
|
|
191
|
+
|
|
192
|
+
| 設定 | 預設 | 說明 |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| `agents` | 無 | 第一批團隊,只建立一次。沒有 `discord` 時,種子沒有 `channelId`,團隊的資料列記在 id `local` 之下 |
|
|
195
|
+
| `coordinator` | `agents` 的第一位 | 沒有 `discord` 時,接手主人一般請求的 agent,不能被封存。有 `discord` 時設它會出錯:由入口頻道的 agent 協調 |
|
|
196
|
+
| `skills` | 有 `agents` 時開啟 | 技能 addon;沒有 `discord` 時,只有 `agents` 列了 agent 才會開 |
|
|
197
|
+
|
|
174
198
|
有人在 agent 的頻道對助理說話時,那一輪也會讀進助理上次在該頻道發言之後的頻道訊息(頻道脈絡;見[外掛指南](docs/plugins.md#channel-context-what-was-said-around-an-addressed-message))。
|
|
175
199
|
用 `discord.channelContext` 調整,或設 `channelContext: false` 關閉:
|
|
176
200
|
|
|
@@ -184,11 +208,25 @@ export default {
|
|
|
184
208
|
| `foreignWebhook` | 無 | `(webhookId) => boolean \| "skip"`:回傳 true 的 webhook 所發的貼文視為別人的訊息,不是助理自己的;回傳 `"skip"` 則把該 webhook 的貼文當作不存在,不納入也不截斷範圍(只能用程式碼設定) |
|
|
185
209
|
| `attachments` | `{ images: 0, maxBytes: 10 MiB }` | `images`:替這一輪抓取保留訊息中的幾張圖片,最多 4 張 |
|
|
186
210
|
|
|
211
|
+
在伺服器頻道或討論串裡重新開始對話時,助理會在那裡貼一條簡短的分隔線,讓下一輪不讀之前的內容;私訊不貼。
|
|
212
|
+
|
|
213
|
+
| `discord.freshMarker` | 預設 | 說明 |
|
|
214
|
+
| --- | --- | --- |
|
|
215
|
+
| 文字 | `─── new conversation ───` | 分隔線的文字 |
|
|
216
|
+
| `false` | | 不貼分隔線 |
|
|
217
|
+
|
|
218
|
+
| `discord.formatting` | 預設 | 說明 |
|
|
219
|
+
| --- | --- | --- |
|
|
220
|
+
| 文字 | Markdown 會轉成排版,表格除外 | 以英文告訴模型回覆在 Discord 裡怎麼呈現;附加在每個在那裡回覆的回合的系統提示後面 |
|
|
221
|
+
| `false` | | 不告訴模型任何事 |
|
|
222
|
+
|
|
187
223
|
核准卡和 `ask_user` 問題(一次最多四題的一組問題放在同一張卡片上)會讓這一輪等 `discord.cardGraceSeconds` 秒(預設 `120`)。
|
|
188
|
-
agent 頻道裡訊息的附件,整則訊息(連同它回覆的那則)要在 `discord.attachmentDownload.deadlineMs`(預設 60,000)毫秒內下載完;單一檔案連續 `discord.attachmentDownload.idleMs`(預設 10,000)毫秒沒收到任何位元組就放棄,這一輪會略過收不到的檔案繼續。
|
|
189
|
-
核准卡「複製」按鈕的文字存在資料目錄的 `card-copies.json`,預設留最新 100 張、30 天;`discord.cardCopies`(`{ keepDays?, keep? }`)可以調整,設成 `false` 則只留在記憶體。
|
|
190
224
|
在這段時間內回答,這一輪就帶著答案繼續;沒回答的話,這一輪會說它在等你,然後結束,卡片繼續開著、沒有期限,之後再回答就會在同一個對話開新的一輪,帶上你的答案,或核准後那個動作的結果。
|
|
191
225
|
coding worker 的卡片則會讓 worker 一直等下去。
|
|
226
|
+
每張 `ask_user` 卡片在送出旁邊都有**取消**按鈕:在這一輪還在等的時候按下,模型會被告知你取消了這個問題、不要再問一次。
|
|
227
|
+
一輪執行到時間上限(內建 runtime 的預算,預設 10 分鐘,或上限,預設 30 分鐘;見 `turnLimits`)時,會先問再結束:卡片寫出是哪個上限與這一輪正在做什麼,附**繼續**和**取消**;在 `turnLimits.cardSeconds`(預設 5 分鐘)內沒回答就中止這一輪。
|
|
228
|
+
核准卡「複製」按鈕的文字存在資料目錄的 `card-copies.json`,預設留最新 100 張、30 天;`discord.cardCopies`(`{ keepDays?, keep? }`)可以調整,設成 `false` 則只留在記憶體。
|
|
229
|
+
agent 頻道裡訊息的附件,整則訊息(連同它回覆的那則)要在 `discord.attachmentDownload.deadlineMs`(預設 60,000)毫秒內下載完;單一檔案連續 `discord.attachmentDownload.idleMs`(預設 10,000)毫秒沒收到任何位元組就放棄,這一輪會略過收不到的檔案繼續。
|
|
192
230
|
|
|
193
231
|
## 威脅模型
|
|
194
232
|
|
|
@@ -197,9 +235,9 @@ pi-roundtable 為一位擁有者執行一個助理。
|
|
|
197
235
|
|
|
198
236
|
- **信任誰。** 營運者掌控主機、設定和憑證。principal 代表主機服務的人,identity 是連到這個人的帳號或憑證。擁有者只能由 `access.owners` 或 `roundtable principal` 授予,不能來自 token claim。其他人的 `member` 或 `admin` tier 由核心 `access` 依驗證過的身分與帶 surface 前綴的角色判斷;外掛不能自行指定發言者的權限。
|
|
199
237
|
- **一輪能做什麼。** 一輪只拿得到發言者的 tier 允許的工具;沒有外掛給 tier 的工具只有擁有者能用。工具在主機的行程裡執行,用的是主機的檔案和網路:`web_search` 和 `fetch_content` 連得到內部位址,所以網頁人格要在 `selection` 裡寫明它的工具。外掛也在同一個行程裡執行,權限一樣。
|
|
200
|
-
- **核准。**
|
|
238
|
+
- **核准。** 暫緩的呼叫只由當輪暫緩它的發言者核准,而且當下 tier 仍要足夠;在共用對話裡,擁有者也可以在卡片上或用確認訊息核准。私人對話裡別人都不能核准,高於發言者 tier 的呼叫在那裡也不會被核准。
|
|
201
239
|
- **對話。** Discord 裡智慧體的頻道和群組房間是共用的:在那裡發言的每個人,都會寫進智慧體讀取的同一段對話。網頁聊天的對話屬於開啟它的人:在網頁聊天裡只有開啟者本人能讀、能發言、能回答它的提問,但擁有者可以透過擁有者主控台([pi-roundtable-web](packages/web))讀取,營運者也能從資料庫和資料目錄讀到。
|
|
202
|
-
- **記憶隔離。** 記憶依 principal 分開,不依擁有者 tier 判斷。私人對話讀本人的,共用對話讀當輪發言者的;連結 Discord
|
|
240
|
+
- **記憶隔離。** 記憶依 principal 分開,不依擁有者 tier 判斷。私人對話讀本人的,共用對話讀當輪發言者的;連結 Discord 與網頁身分後,同一個人共用一份記憶。在共用對話送給模型的請求和摘要裡,記憶工具與載入記憶的 worker 的呼叫(參數和結果成對)會對其他發言者隱藏,先前回合的 reasoning 則依 provider 清除。其他工具的參數和公開的助理回覆仍是公開內容,即使模型在裡面重述私人記憶;這不是一般的秘密流向防護。第三方摘要器必須先使用 `privateCompaction`,才能摘要共用歷史。`claude-bridge` 的共用回合只在原始讀者紀錄(含已壓縮的回合)顯示另一位讀者的私人記憶(連只放進 prompt 的記憶與其 reasoning 也算),或某個呼叫已對當前讀者隱藏時才被拒絕。沒有私人記憶的回合不會擋其他讀者,關掉之後的記憶也繞不過先前的私人歷史;開放其他人進來只會在開機和 doctor 裡警告。SYSTEM 只在這項核心防護裡算作主要擁有者,所以它能讀取並公開重述擁有者保留的呼叫與 reasoning;0.9 之前、第一筆範圍紀錄之前未標記的內建記憶結果,仍歸屬擁有者。沙盒的全場共用 prompt 區塊必須明確使用 `memory.visibility: "shared"`,私人回合需要 worker 能力。Bridge 會在這些投影之外重播未過濾的歷史;見[確切規則與剩餘風險](docs/migration-0.9.md#claude-bridge-guard)。
|
|
203
241
|
- **模型憑證。** 每一輪不論誰發言,都用主機的模型登入執行、由它計費。每個人自己的憑證預計在之後的版本加入。
|
|
204
242
|
- **資料。** 對話、記憶和排程以未加密的形式存在 PostgreSQL 和資料目錄裡。
|
|
205
243
|
|
|
@@ -218,7 +256,7 @@ pi-roundtable 為一位擁有者執行一個助理。
|
|
|
218
256
|
| `roundtable start` | 先執行不需要網路的檢查,再啟動 bot |
|
|
219
257
|
| `roundtable add plugin <name>` | 新增 `plugins/<name>.ts` 和它的測試,並列進設定 |
|
|
220
258
|
| `roundtable add package <spec>` | 用 `bun add` 安裝 Pi package,並新增一個載入它、把它的工具交給每一輪 agent 的外掛 |
|
|
221
|
-
| `roundtable upgrade [--write]` | 以 diff 顯示改寫成 0.9 寫法的 `roundtable.config.ts`(`owner` 與 `speakers` 改成 `access`,`discord` 改成 adapter);加上 `--write`
|
|
259
|
+
| `roundtable upgrade [--write [--unchecked]]` | 以 diff 顯示改寫成 0.9 寫法的 `roundtable.config.ts`(`owner` 與 `speakers` 改成 `access`,`discord` 改成 adapter);加上 `--write` 時,先確認改寫後服務的人不變(這需要主機執行時的環境變數),無法確認就拒絕寫入;`--unchecked` 不確認直接寫入,並印出警告 |
|
|
222
260
|
| `roundtable principal list\|show\|create\|link\|unlink\|grant\|revoke\|disable\|enable` | 列出並修改資料庫裡的 principal、它們的身分與角色;執行中的主機在 30 秒內看到變更 |
|
|
223
261
|
|
|
224
262
|
## 變更與授權
|
package/docs/migrating-0.8.md
CHANGED
|
@@ -114,10 +114,10 @@ Before anyone signs in, read the web chat README's [provider settings](../packag
|
|
|
114
114
|
|
|
115
115
|
### Leaving Discord out of an existing configuration
|
|
116
116
|
|
|
117
|
-
Remove `discord` and the host runs without it: no Discord plugin, no agent server, no agents, and no skills.
|
|
117
|
+
Remove `discord` and the host runs without it: no Discord plugin, and, unless you keep `agents` (see [an agent team without Discord](plugins.md#an-agent-team-without-discord)), no agent server, no agents, and no skills.
|
|
118
118
|
Every claim that runs turns through `context.turns` still works, over the chat surfaces your plugins bring.
|
|
119
119
|
|
|
120
|
-
- `
|
|
120
|
+
- `ops.agent` is a configuration error. In 0.8 `agents` and `skills` were too; from 0.23 they work without Discord in the rooms of a surface that hosts them. `ops: { conversation: "<surface>:<id>" }` needs a chat surface that serves it, a claim that owns it and takes background turns, and a plugin that contributes the `owner` background target, or the host does not start; without Discord nothing contributes `owner` unless a plugin of yours does, so leave `ops` out unless you have one. The web chat cannot take error reports in 0.8, so `web:<id>` fails at startup.
|
|
121
121
|
- `http` is optional; without it no listener opens.
|
|
122
122
|
- `notify_owner` is not registered.
|
|
123
123
|
- `schedule_*` and `delegate_task` are not registered, since their runs are turns of the `owner` background target, which the agent server contributes; a plugin that contributes it brings them back. The web chat takes no background turns yet, so leave them out of a web persona's `selection` on a host that keeps Discord.
|