pi-roundtable 0.15.0 → 0.15.2

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 CHANGED
@@ -5,6 +5,18 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.15.2] - 2026-10-15
9
+
10
+ ### Fixed
11
+
12
+ - The agent server's `#dashboard` is found again after a restart once it is pinned. Pinning it makes Discord post a "pinned a message" notice in the bot's name, and the dashboard search took that system message as the dashboard: every edit failed with `Cannot execute action on a system message` and the host logged `dashboard not updated` at every start. The search now passes over system messages.
13
+
14
+ ## [0.15.1] - 2026-10-15
15
+
16
+ ### Fixed
17
+
18
+ - The agent server's `#dashboard` message is updated again after a restart. The first message was posted as components v2 but later edits left the flag out, and an old v1 message found after a restart could not be edited at all: Discord answered 400 `Invalid Form Body` (`components[0][UNION_TYPE_CHOICES]`), the host logged `dashboard not updated` at every start, and the dashboard was never rebuilt. An edit now keeps the components v2 flag, and a message Discord refuses with that 400 is deleted and posted fresh once; any other failure is still reported.
19
+
8
20
  ## [0.15.0] - 2026-10-14
9
21
 
10
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "A plugin-driven Pi agent server for one owner, reached through Discord or the web",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,6 +3,7 @@ import {
3
3
  ChannelType,
4
4
  type Client,
5
5
  ContainerBuilder,
6
+ DiscordAPIError,
6
7
  discordSort,
7
8
  type Guild,
8
9
  type Message,
@@ -291,6 +292,15 @@ function channelMessage(message: Message): ChannelMessage {
291
292
  };
292
293
  }
293
294
 
295
+ /** Discord's 400 "Invalid Form Body": the payload is refused for this message, retrying it as is cannot help. */
296
+ function isInvalidForm(error: unknown): boolean {
297
+ return (
298
+ error instanceof DiscordAPIError &&
299
+ error.status === 400 &&
300
+ error.code === 50035
301
+ );
302
+ }
303
+
294
304
  /**
295
305
  * `#dashboard` at the top of the agent server and the assistant's one message in it, found again after a
296
306
  * restart and made again when either is deleted. The message is pinned when the assistant has the Pin
@@ -324,6 +334,7 @@ export class DiscordDashboard implements DashboardBoard {
324
334
  const body = {
325
335
  components: [container],
326
336
  allowedMentions: { parse: [] },
337
+ flags: MessageFlags.IsComponentsV2 as const,
327
338
  };
328
339
  const message = this.#message ?? (await this.#find());
329
340
  if (message) {
@@ -333,15 +344,24 @@ export class DiscordDashboard implements DashboardBoard {
333
344
  return;
334
345
  } catch (error) {
335
346
  this.#message = undefined;
336
- // A message deleted by hand is made again; anything else is the caller's to log.
337
- if (!(await this.#gone(message))) throw error;
347
+ // A message deleted by hand is made again, and so is one Discord refuses to edit as
348
+ // components v2 (an old v1 message): it is deleted and posted fresh, once. Anything
349
+ // else is the caller's to log.
350
+ const gone = await this.#gone(message);
351
+ if (!gone && !isInvalidForm(error)) throw error;
352
+ if (!gone)
353
+ await message
354
+ .delete()
355
+ .catch((deleteError: unknown) =>
356
+ this.#logger.warn(
357
+ { err: deleteError },
358
+ "old dashboard message not deleted; a fresh one is posted beside it",
359
+ ),
360
+ );
338
361
  }
339
362
  }
340
363
  const channel = await this.#channel();
341
- const posted = await channel.send({
342
- ...body,
343
- flags: MessageFlags.IsComponentsV2,
344
- });
364
+ const posted = await channel.send(body);
345
365
  this.#message = posted;
346
366
  await this.#pin(posted);
347
367
  }
@@ -365,7 +385,11 @@ export class DiscordDashboard implements DashboardBoard {
365
385
  const channel = await this.#channel();
366
386
  const botId = this.#client.user?.id;
367
387
  const recent = await channel.messages.fetch({ limit: 50 });
368
- return recent.find((message) => message.author.id === botId);
388
+ // Pinning the dashboard makes Discord post a "pinned a message" notice in the bot's name; a
389
+ // system message cannot be edited, so it is never the dashboard.
390
+ return recent.find(
391
+ (message) => message.author.id === botId && !message.system,
392
+ );
369
393
  }
370
394
 
371
395
  async #gone(message: Message): Promise<boolean> {