pi-roundtable 0.10.0 → 0.11.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 +17 -0
- package/README.md +3 -0
- package/README.zh-TW.md +3 -0
- package/docs/plugins.md +19 -6
- package/package.json +1 -1
- package/src/core/agents/agent-claim.ts +11 -7
- package/src/core/attachments/image-prep.ts +32 -4
- package/src/core/builtin/discord.ts +22 -0
- package/src/core/config/adapters.ts +33 -0
- package/src/core/contract/channel-context.ts +83 -3
- package/src/core/define-roundtable.ts +1 -0
- package/src/core/discord/card-copies.ts +192 -0
- package/src/core/discord/channel-context.ts +142 -15
- package/src/core/discord/context-images.ts +164 -0
- package/src/core/discord/owner-cards-fixture.ts +9 -2
- package/src/core/discord/owner-cards.ts +47 -1
- package/src/core/domain/attachment.ts +3 -0
- package/src/core/i18n/cards.ts +7 -0
- package/src/core/interactions/prompts.ts +10 -0
- package/src/discord/index.ts +6 -1
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type CardAudienceOptions,
|
|
25
25
|
CardAudiences,
|
|
26
26
|
} from "./card-audience.ts";
|
|
27
|
+
import { CardCopies, copyReply, offeredCopy } from "./card-copies.ts";
|
|
27
28
|
import { DiscordOwners, discordUser } from "./discord-owners.ts";
|
|
28
29
|
import type { InteractionModule } from "./interaction-module.ts";
|
|
29
30
|
import {
|
|
@@ -75,6 +76,13 @@ export interface OwnerCardsOptions {
|
|
|
75
76
|
* answered. Without it no turn stops waiting on its cards.
|
|
76
77
|
*/
|
|
77
78
|
resume?(turn: LateTurn): void;
|
|
79
|
+
/**
|
|
80
|
+
* Where and how long the text behind approval cards' copy buttons is kept. With a `path` it is
|
|
81
|
+
* a JSON file that survives a restart; without one the texts live in memory and a card's copy
|
|
82
|
+
* button stops working when the process restarts. `keep` (default 100) is how many cards'
|
|
83
|
+
* texts, the newest, and `keepDays` (default 30) how long.
|
|
84
|
+
*/
|
|
85
|
+
copies?: { path?: string; keep?: number; keepDays?: number };
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/**
|
|
@@ -91,11 +99,18 @@ export class OwnerCards implements InteractionModule {
|
|
|
91
99
|
readonly #options: OwnerCardsOptions;
|
|
92
100
|
readonly #audiences: CardAudiences;
|
|
93
101
|
readonly #open = new Map<string, OpenCard>();
|
|
102
|
+
readonly #copies: CardCopies;
|
|
94
103
|
|
|
95
104
|
constructor(options: OwnerCardsOptions) {
|
|
96
105
|
this.#options = options;
|
|
106
|
+
this.#copies = new CardCopies({
|
|
107
|
+
...options.copies,
|
|
108
|
+
logger: options.logger,
|
|
109
|
+
});
|
|
97
110
|
this.#audiences = new CardAudiences(options, new DiscordOwners(options));
|
|
98
111
|
}
|
|
112
|
+
/** Resolves once the remembered copy texts are on disk. */
|
|
113
|
+
flushCopies = (): Promise<void> => this.#copies.flush();
|
|
99
114
|
|
|
100
115
|
commands() {
|
|
101
116
|
return [];
|
|
@@ -114,9 +129,15 @@ export class OwnerCards implements InteractionModule {
|
|
|
114
129
|
signal,
|
|
115
130
|
minTier = "owner",
|
|
116
131
|
wait,
|
|
117
|
-
|
|
132
|
+
details,
|
|
118
133
|
options,
|
|
119
134
|
) => {
|
|
135
|
+
const copy = offeredCopy(details?.copy, (chars) =>
|
|
136
|
+
this.#options.logger.warn(
|
|
137
|
+
{ channelId, chars },
|
|
138
|
+
"card copy text too long; the card has no copy button",
|
|
139
|
+
),
|
|
140
|
+
);
|
|
120
141
|
const audience = await this.#audiences.approval(
|
|
121
142
|
scope,
|
|
122
143
|
minTier,
|
|
@@ -154,6 +175,7 @@ export class OwnerCards implements InteractionModule {
|
|
|
154
175
|
: {}),
|
|
155
176
|
title,
|
|
156
177
|
sections: [message],
|
|
178
|
+
...(copy ? { copy: copy.text } : {}),
|
|
157
179
|
rows: (id, disabled) => [
|
|
158
180
|
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
159
181
|
new ButtonBuilder()
|
|
@@ -168,6 +190,16 @@ export class OwnerCards implements InteractionModule {
|
|
|
168
190
|
.setEmoji("❌")
|
|
169
191
|
.setStyle(ButtonStyle.Secondary)
|
|
170
192
|
.setDisabled(disabled),
|
|
193
|
+
// Copying answers nothing, so it stays usable once the card has ended.
|
|
194
|
+
...(copy
|
|
195
|
+
? [
|
|
196
|
+
new ButtonBuilder()
|
|
197
|
+
.setCustomId(`${CARD_PREFIX}${id}:copy`)
|
|
198
|
+
.setLabel(copy.label)
|
|
199
|
+
.setEmoji("📋")
|
|
200
|
+
.setStyle(ButtonStyle.Secondary),
|
|
201
|
+
]
|
|
202
|
+
: []),
|
|
171
203
|
),
|
|
172
204
|
],
|
|
173
205
|
footer: messages().cardApprovalFooter,
|
|
@@ -221,6 +253,8 @@ export class OwnerCards implements InteractionModule {
|
|
|
221
253
|
audience: Audience;
|
|
222
254
|
title: string;
|
|
223
255
|
sections: string[];
|
|
256
|
+
/** The text behind the card's copy button, kept under the card's id. */
|
|
257
|
+
copy?: string;
|
|
224
258
|
question?: OwnerQuestion;
|
|
225
259
|
rows(id: string, disabled: boolean): Rows;
|
|
226
260
|
footer: string;
|
|
@@ -236,6 +270,7 @@ export class OwnerCards implements InteractionModule {
|
|
|
236
270
|
if (signal?.aborted) return Promise.resolve(cancelled);
|
|
237
271
|
const wait = resume ? card.wait : undefined;
|
|
238
272
|
const id = randomUUID();
|
|
273
|
+
if (card.copy !== undefined) this.#copies.remember(id, card.copy);
|
|
239
274
|
const sections = [
|
|
240
275
|
...card.sections,
|
|
241
276
|
...(card.audience.note ? [card.audience.note] : []),
|
|
@@ -361,6 +396,17 @@ export class OwnerCards implements InteractionModule {
|
|
|
361
396
|
const [id = "", action = ""] = interaction.customId
|
|
362
397
|
.slice(CARD_PREFIX.length)
|
|
363
398
|
.split(":");
|
|
399
|
+
// Copying answers nothing: anyone who sees the card may copy, as often as they like, also
|
|
400
|
+
// once the card has ended or the process restarted.
|
|
401
|
+
if (interaction.isButton() && action === "copy") {
|
|
402
|
+
const text = this.#copies.recall(id);
|
|
403
|
+
await interaction.reply(
|
|
404
|
+
text === undefined
|
|
405
|
+
? { content: messages().cardCopyGone, flags: MessageFlags.Ephemeral }
|
|
406
|
+
: copyReply(text),
|
|
407
|
+
);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
364
410
|
const card = this.#open.get(id);
|
|
365
411
|
if (!card) {
|
|
366
412
|
await interaction.reply({
|
|
@@ -19,6 +19,9 @@ export interface AttachmentFailure {
|
|
|
19
19
|
fromReference: boolean;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** The most images one turn carries to the model. */
|
|
23
|
+
export const MAX_TURN_IMAGES = 4;
|
|
24
|
+
|
|
22
25
|
/** An image in the form the model accepts. */
|
|
23
26
|
export interface ModelImage {
|
|
24
27
|
data: string;
|
package/src/core/i18n/cards.ts
CHANGED
|
@@ -5,6 +5,10 @@ export function cardsEn(ctx: CatalogContext) {
|
|
|
5
5
|
return {
|
|
6
6
|
cardRunLabel: "Run",
|
|
7
7
|
cardCancelLabel: "Cancel",
|
|
8
|
+
cardCopyLabel: "Copy",
|
|
9
|
+
cardCopyGone:
|
|
10
|
+
"The text of this card is no longer kept; ask the assistant to show it again.",
|
|
11
|
+
cardCopyFile: "The text is long, so it is attached as a file.",
|
|
8
12
|
cardApprovalFooter:
|
|
9
13
|
"Nothing runs until you approve; answer whenever you are ready.",
|
|
10
14
|
cardQuestionFooter: "Answer whenever you are ready.",
|
|
@@ -39,6 +43,9 @@ export function cardsZhTW(ctx: CatalogContext): ReturnType<typeof cardsEn> {
|
|
|
39
43
|
return {
|
|
40
44
|
cardRunLabel: "執行",
|
|
41
45
|
cardCancelLabel: "取消",
|
|
46
|
+
cardCopyLabel: "複製",
|
|
47
|
+
cardCopyGone: "這張卡片的文字已經不在了;請再請助理顯示一次。",
|
|
48
|
+
cardCopyFile: "文字比較長,所以用檔案附上。",
|
|
42
49
|
cardApprovalFooter: "你核准之前不會執行;準備好再回覆就好。",
|
|
43
50
|
cardQuestionFooter: "準備好再回答就好。",
|
|
44
51
|
cardStopped: "⏹️ 這一輪已停止。",
|
|
@@ -35,6 +35,16 @@ export interface ApprovalDetails {
|
|
|
35
35
|
input: Record<string, unknown>;
|
|
36
36
|
/** The files the call sends by path; absent when it sends none. */
|
|
37
37
|
files?: ApprovalFile[];
|
|
38
|
+
/**
|
|
39
|
+
* A text the card offers to copy, such as a command or a message the call would send. A surface
|
|
40
|
+
* that can shows a control, usable by anyone who sees the card, any number of times, that hands
|
|
41
|
+
* them `text` (`label` names the control, default the surface's own); using it never answers
|
|
42
|
+
* the prompt. It is informational: a surface that cannot show it ignores it. The text is
|
|
43
|
+
* visible to anyone who can see the card, so it holds nothing they may not read. A text over
|
|
44
|
+
* 20,000 characters is not offered (Discord logs a warning), and a surface with a size limit of
|
|
45
|
+
* its own leaves it out before it cuts the call's `input`.
|
|
46
|
+
*/
|
|
47
|
+
copy?: { label?: string; text: string };
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
/**
|
package/src/discord/index.ts
CHANGED
|
@@ -6,10 +6,15 @@ export { DISCORD } from "../core/builtin/discord.ts";
|
|
|
6
6
|
export type { DiscordAdapterConfig } from "../core/config/config.ts";
|
|
7
7
|
export type {
|
|
8
8
|
ChannelContext,
|
|
9
|
+
ChannelContextAttachment,
|
|
10
|
+
ChannelContextAttachmentOptions,
|
|
9
11
|
ChannelContextMessage,
|
|
10
12
|
ChannelContextOptions,
|
|
11
13
|
} from "../core/contract/channel-context.ts";
|
|
12
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
withChannelContext,
|
|
16
|
+
withChannelContextImages,
|
|
17
|
+
} from "../core/contract/channel-context.ts";
|
|
13
18
|
export type { AgentPanelMessage } from "../core/discord/agent-commands.ts";
|
|
14
19
|
export type {
|
|
15
20
|
AgentPanel,
|