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.
Files changed (129) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +35 -9
  3. package/README.zh-TW.md +51 -13
  4. package/docs/migrating-0.8.md +2 -2
  5. package/docs/plugins.md +350 -28
  6. package/examples/discord-adapter.ts +9 -1
  7. package/examples/seeds.test.ts +2 -2
  8. package/examples/seeds.ts +1 -2
  9. package/package.json +6 -2
  10. package/src/characters/catalog.ts +1055 -0
  11. package/src/characters/characters.css +68 -0
  12. package/src/characters/colors.ts +108 -0
  13. package/src/characters/compose.ts +281 -0
  14. package/src/characters/css.ts +63 -0
  15. package/src/characters/index.ts +33 -0
  16. package/src/characters/parse.ts +196 -0
  17. package/src/characters/parts-face.ts +354 -0
  18. package/src/characters/parts-hair.ts +56 -0
  19. package/src/characters/parts-hats.ts +57 -0
  20. package/src/characters/parts-outfits.ts +81 -0
  21. package/src/characters/parts-props.ts +137 -0
  22. package/src/characters/spec.ts +224 -0
  23. package/src/characters/suggest.ts +101 -0
  24. package/src/cli/checks/characters.ts +23 -0
  25. package/src/cli/checks/images.ts +5 -3
  26. package/src/cli/doctor.ts +2 -0
  27. package/src/core/agents/agent-claim.ts +30 -11
  28. package/src/core/agents/agent-dashboard.ts +1 -1
  29. package/src/core/agents/agent-messages.ts +43 -15
  30. package/src/core/agents/agent-prompt.ts +2 -4
  31. package/src/core/agents/agent-rules.ts +42 -3
  32. package/src/core/agents/agent-schema.ts +37 -15
  33. package/src/core/agents/agent-store.ts +57 -13
  34. package/src/core/agents/agent-team-fixture.ts +90 -14
  35. package/src/core/agents/agent-team.ts +83 -50
  36. package/src/core/agents/agent-tools.ts +94 -41
  37. package/src/core/agents/avatar-studio.ts +40 -4
  38. package/src/core/agents/character-png.ts +40 -0
  39. package/src/core/agents/group-messages.ts +7 -4
  40. package/src/core/agents/group-turns.ts +27 -14
  41. package/src/core/agents/team-editing.ts +99 -58
  42. package/src/core/agents/team-keys.ts +177 -11
  43. package/src/core/agents/team-lifecycle.ts +151 -84
  44. package/src/core/agents/team-options.ts +13 -6
  45. package/src/core/agents/team-status.ts +31 -13
  46. package/src/core/agents/team-text.ts +13 -3
  47. package/src/core/agents/team-turn-types.ts +25 -7
  48. package/src/core/agents/team-turns.ts +143 -28
  49. package/src/core/attachments/attachment-fetcher.ts +162 -64
  50. package/src/core/attachments/prompt-block.ts +7 -1
  51. package/src/core/attachments/tool-attachment.ts +20 -1
  52. package/src/core/attachments/turn-attachments.ts +18 -2
  53. package/src/core/builtin/agent-server.ts +103 -40
  54. package/src/core/builtin/discord.ts +4 -17
  55. package/src/core/builtin/modules.ts +6 -5
  56. package/src/core/builtin/threads.ts +37 -0
  57. package/src/core/config/agent-team.ts +43 -0
  58. package/src/core/config/config.ts +50 -59
  59. package/src/core/config/limits.ts +93 -0
  60. package/src/core/config/ops.ts +34 -0
  61. package/src/core/config/schema.ts +26 -0
  62. package/src/core/contract/channels.ts +20 -0
  63. package/src/core/contract/surface.ts +77 -7
  64. package/src/core/define-roundtable.ts +70 -42
  65. package/src/core/discord/agent-commands.ts +108 -86
  66. package/src/core/discord/agent-panel.ts +11 -2
  67. package/src/core/discord/connection.ts +1 -1
  68. package/src/core/discord/discord-surface.ts +10 -1
  69. package/src/core/discord/dispatch-thread-host.ts +1 -1
  70. package/src/core/domain/conversation.ts +24 -0
  71. package/src/core/holds.ts +221 -15
  72. package/src/core/host-registration.ts +2 -1
  73. package/src/core/http/listeners.ts +63 -3
  74. package/src/core/i18n/agent-panel.ts +14 -21
  75. package/src/core/interactions/prompts.ts +12 -0
  76. package/src/core/modes/autonomy.ts +23 -0
  77. package/src/core/modes/conversation-modes.ts +213 -0
  78. package/src/core/modes/modes-plugin.ts +24 -0
  79. package/src/core/modules/delegation/delegator.ts +4 -4
  80. package/src/core/modules/host-shell/shell-arithmetic.ts +81 -0
  81. package/src/core/modules/host-shell/shell-lexer-types.ts +53 -0
  82. package/src/core/modules/host-shell/shell-lexer.ts +493 -0
  83. package/src/core/modules/host-shell/shell-paths.ts +85 -4
  84. package/src/core/modules/host-shell/shell-policy.ts +341 -332
  85. package/src/core/modules/host-shell/shell-programs.ts +298 -0
  86. package/src/core/modules/host-shell/shell-quoting.ts +237 -0
  87. package/src/core/modules/host-shell/shell-scripts.ts +114 -0
  88. package/src/core/modules/host-shell/shell-wrappers.ts +104 -0
  89. package/src/core/modules/schedules/precheck-tools.ts +17 -0
  90. package/src/core/modules/schedules/schedules.ts +9 -3
  91. package/src/core/plugin.ts +8 -1
  92. package/src/core/presence/direct-channels.ts +30 -0
  93. package/src/core/presentation/reply-body.ts +25 -0
  94. package/src/core/registry/contributions.ts +31 -7
  95. package/src/core/registry/providers.ts +1 -1
  96. package/src/core/routing/conversation-turns.ts +2 -15
  97. package/src/core/routing/surface-port.ts +5 -1
  98. package/src/core/runtime/extensions/approval-card.ts +1 -5
  99. package/src/core/runtime/extensions/approval-fields.ts +3 -7
  100. package/src/core/runtime/extensions/confirmation-gate-extension.ts +13 -2
  101. package/src/core/runtime/extensions/confirmation-gate.ts +41 -40
  102. package/src/core/runtime/extensions/gate-refusals.ts +23 -0
  103. package/src/core/runtime/extensions/held-calls.ts +16 -1
  104. package/src/core/runtime/extensions/hold-describer.ts +58 -8
  105. package/src/core/runtime/extensions/hold-lookups.ts +19 -19
  106. package/src/core/runtime/extensions/yolo.ts +105 -0
  107. package/src/core/runtime/pi-agent-runtime.ts +3 -5
  108. package/src/core/runtime/runtime-plugin.ts +42 -1
  109. package/src/core/runtime/runtime-types.ts +11 -0
  110. package/src/core/runtime/turn-budget.ts +32 -5
  111. package/src/core/runtime/turn-limits.ts +14 -3
  112. package/src/core/services.ts +61 -9
  113. package/src/core/shared/attachment-reader.ts +6 -0
  114. package/src/core/shared/unix-server.ts +10 -1
  115. package/src/core/testing/modules.ts +12 -8
  116. package/src/core/testing/surface-threads.ts +58 -0
  117. package/src/core/testing/thread-host.ts +17 -5
  118. package/src/core/threads/dispatch-threads.ts +299 -0
  119. package/src/core/threads/host-threads.ts +19 -0
  120. package/src/core/tool-set.snapshot.json +5 -0
  121. package/src/index.ts +27 -1
  122. package/src/kit/characters.ts +5 -0
  123. package/src/kit/index.ts +2 -0
  124. package/src/kit/shell.ts +1 -0
  125. package/src/kit/support.ts +1 -1
  126. package/src/kit/threads.ts +1 -1
  127. package/src/testing.ts +4 -0
  128. package/templates/agents.ts +1 -1
  129. package/src/core/discord/dispatch-threads.ts +0 -194
@@ -15,17 +15,20 @@ import {
15
15
  SeparatorBuilder,
16
16
  SeparatorSpacingSize,
17
17
  StringSelectMenuBuilder,
18
+ type StringSelectMenuInteraction,
18
19
  StringSelectMenuOptionBuilder,
19
20
  TextDisplayBuilder,
20
21
  TextInputBuilder,
21
22
  TextInputStyle,
22
23
  } from "discord.js";
23
24
  import {
24
- type Agent,
25
- MAX_AVATAR_PROMPT_CHARS,
26
- MAX_PROMPT_CHARS,
27
- } from "../agents/agent-store.ts";
28
- import type { AvatarMode } from "../agents/agent-tools.ts";
25
+ CATALOG,
26
+ type CharacterInput,
27
+ CLASS_IDS,
28
+ type PaletteName,
29
+ } from "../../characters/index.ts";
30
+ import { characterOf } from "../agents/agent-rules.ts";
31
+ import { type Agent, MAX_PROMPT_CHARS } from "../agents/agent-store.ts";
29
32
  import { AgentError } from "../domain/errors.ts";
30
33
  import { messages } from "../i18n/index.ts";
31
34
  import { THINKING_LEVELS, thinkingLabel } from "../models.ts";
@@ -68,22 +71,18 @@ export function formModels(
68
71
  : offered;
69
72
  }
70
73
 
71
- type Action = "edit" | "redraw" | "newprompt" | "editavatar" | "model";
72
- const ACTIONS: readonly Action[] = [
73
- "edit",
74
- "redraw",
75
- "newprompt",
76
- "editavatar",
77
- "model",
78
- ];
74
+ type Action = "edit" | "model";
75
+ /** The buttons of the panel; the look is chosen with the two selects, `class` and `color`. */
76
+ const ACTIONS: readonly Action[] = ["edit", "model"];
77
+ type LookAction = "class" | "color";
79
78
 
80
79
  export interface AgentCommandsOptions {
81
80
  store: Pick<AgentDirectory, "agentByChannel" | "groupByChannel" | "agent">;
82
81
  team: Pick<
83
82
  AgentTeam,
84
- "redrawAvatar" | "update" | "modelOf" | "usableModels" | "defaultModel"
83
+ "dress" | "update" | "modelOf" | "usableModels" | "defaultModel"
85
84
  >;
86
- studio: Pick<AvatarStudio, "url" | "canDraw">;
85
+ studio: Pick<AvatarStudio, "url">;
87
86
  /** The skills an agent carries; without the skills addon the panel lists none. */
88
87
  skills?: Pick<SkillRegistry, "carried">;
89
88
  }
@@ -133,8 +132,7 @@ export class AgentCommands {
133
132
 
134
133
  #panel(agent: Agent, note?: string): AgentPanelMessage {
135
134
  const text = messages();
136
- // Without an image provider nothing can be drawn, so the avatar buttons are not offered.
137
- const drawing = this.#options.studio.canDraw;
135
+ const look = characterOf(agent);
138
136
  const inline = agent.prompt.length <= INLINE_PROMPT_CHARS;
139
137
  const { model, thinking } = this.#options.team.modelOf(agent.name);
140
138
  const follows =
@@ -168,9 +166,10 @@ export class AgentCommands {
168
166
  )
169
167
  .addTextDisplayComponents(
170
168
  new TextDisplayBuilder().setContent(
171
- drawing
172
- ? text.agentAvatarPromptText(agent.avatarPrompt)
173
- : text.agentNoImageProvider,
169
+ text.agentLook(
170
+ CATALOG.classes[look.class].label,
171
+ look.palette ?? look.colors.primary,
172
+ ),
174
173
  ),
175
174
  new TextDisplayBuilder().setContent(
176
175
  inline
@@ -196,15 +195,14 @@ export class AgentCommands {
196
195
  new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Small),
197
196
  )
198
197
  .addActionRowComponents(
198
+ new ActionRowBuilder<StringSelectMenuBuilder>()
199
+ .addComponents(this.#lookMenu("class", agent.name, look.class))
200
+ .toJSON(),
201
+ new ActionRowBuilder<StringSelectMenuBuilder>()
202
+ .addComponents(this.#lookMenu("color", agent.name, look.palette))
203
+ .toJSON(),
199
204
  new ActionRowBuilder<ButtonBuilder>().addComponents(
200
205
  button("edit", text.agentEditPromptLabel, ButtonStyle.Primary),
201
- ...(drawing
202
- ? [
203
- button("redraw", text.agentRedrawLabel),
204
- button("newprompt", text.agentNewAvatarPromptLabel),
205
- button("editavatar", text.agentEditAvatarLabel),
206
- ]
207
- : []),
208
206
  button("model", text.agentModelLabel),
209
207
  ),
210
208
  )
@@ -227,23 +225,54 @@ export class AgentCommands {
227
225
  };
228
226
  }
229
227
 
230
- /** A panel button: a form opens, or a redraw starts. */
228
+ /** A dropdown of the look: the 24 classes or the 8 colors, the agent's own chosen. */
229
+ #lookMenu(
230
+ action: LookAction,
231
+ name: string,
232
+ current: string | undefined,
233
+ ): StringSelectMenuBuilder {
234
+ const text = messages();
235
+ const option = (label: string, value: string, description?: string) => {
236
+ const built = new StringSelectMenuOptionBuilder()
237
+ .setLabel(label)
238
+ .setValue(value)
239
+ .setDefault(value === current);
240
+ return description
241
+ ? built.setDescription(description.slice(0, 100))
242
+ : built;
243
+ };
244
+ return new StringSelectMenuBuilder()
245
+ .setCustomId(`${AGENT_BUTTON_PREFIX}${action}:${name}`)
246
+ .setPlaceholder(
247
+ action === "class"
248
+ ? text.agentClassPlaceholder
249
+ : text.agentColorPlaceholder,
250
+ )
251
+ .addOptions(
252
+ action === "class"
253
+ ? CLASS_IDS.map((id) =>
254
+ option(CATALOG.classes[id].label, id, CATALOG.classes[id].jobs),
255
+ )
256
+ : (Object.keys(CATALOG.palettes) as PaletteName[]).map((id) =>
257
+ option(id.charAt(0).toUpperCase() + id.slice(1), id),
258
+ ),
259
+ );
260
+ }
261
+
262
+ /** A panel button: a form opens. */
231
263
  async button(interaction: ButtonInteraction): Promise<void> {
232
264
  const [action, name] = interaction.customId
233
265
  .slice(AGENT_BUTTON_PREFIX.length)
234
266
  .split(":");
235
267
  const agent = name ? this.#options.store.agent(name) : undefined;
236
- if (!agent || !ACTIONS.includes(action as Action))
237
- throw new OwnerFacingError(messages().agentNotFound);
238
- if (action === "redraw") {
239
- await interaction.deferReply({ flags: MessageFlags.Ephemeral });
240
- await interaction.editReply(await this.#avatar(agent.name, "redraw"));
241
- return;
242
- }
268
+ if (!agent) throw new OwnerFacingError(messages().agentNotFound);
269
+ // A panel posted before 0.23 still carries the redraw buttons.
270
+ if (!ACTIONS.includes(action as Action))
271
+ throw new OwnerFacingError(messages().agentPanelOutdated);
243
272
  await interaction.showModal(
244
273
  action === "model"
245
274
  ? await this.#modelModal(agent)
246
- : this.#modal(action as Action, agent),
275
+ : this.#editModal(agent),
247
276
  );
248
277
  }
249
278
 
@@ -292,7 +321,7 @@ export class AgentCommands {
292
321
  );
293
322
  }
294
323
 
295
- #modal(action: Action, agent: Agent): ModalBuilder {
324
+ #editModal(agent: Agent): ModalBuilder {
296
325
  const field = (
297
326
  id: string,
298
327
  label: string,
@@ -308,50 +337,23 @@ export class AgentCommands {
308
337
  if (value) input.setValue(value);
309
338
  return new LabelBuilder().setLabel(label).setTextInputComponent(input);
310
339
  };
311
- const modal = new ModalBuilder().setCustomId(
312
- `${AGENT_MODAL_PREFIX}${action}:${agent.name}`,
313
- );
314
- if (action === "edit")
315
- return modal
316
- .setTitle(
317
- messages().agentEditModalTitle(agent.displayName).slice(0, 45),
318
- )
319
- .addLabelComponents(
320
- field(
321
- "display",
322
- messages().agentDisplayNameLabel,
323
- TextInputStyle.Short,
324
- 32,
325
- agent.displayName,
326
- ),
327
- field(
328
- "prompt",
329
- messages().agentPromptLabel,
330
- TextInputStyle.Paragraph,
331
- MAX_PROMPT_CHARS,
332
- agent.prompt,
333
- ),
334
- );
335
- if (action === "newprompt")
336
- return modal
337
- .setTitle(messages().agentNewAvatarPromptLabel)
338
- .addLabelComponents(
339
- field(
340
- "text",
341
- messages().agentAvatarPromptLabel,
342
- TextInputStyle.Paragraph,
343
- MAX_AVATAR_PROMPT_CHARS,
344
- agent.avatarPrompt,
345
- ),
346
- );
347
- return modal
348
- .setTitle(messages().agentEditAvatarLabel)
340
+ return new ModalBuilder()
341
+ .setCustomId(`${AGENT_MODAL_PREFIX}edit:${agent.name}`)
342
+ .setTitle(messages().agentEditModalTitle(agent.displayName).slice(0, 45))
349
343
  .addLabelComponents(
350
344
  field(
351
- "text",
352
- messages().agentEditAvatarField,
345
+ "display",
346
+ messages().agentDisplayNameLabel,
347
+ TextInputStyle.Short,
348
+ 32,
349
+ agent.displayName,
350
+ ),
351
+ field(
352
+ "prompt",
353
+ messages().agentPromptLabel,
353
354
  TextInputStyle.Paragraph,
354
- MAX_AVATAR_PROMPT_CHARS,
355
+ MAX_PROMPT_CHARS,
356
+ agent.prompt,
355
357
  ),
356
358
  );
357
359
  }
@@ -383,23 +385,43 @@ export class AgentCommands {
383
385
  await interaction.editReply(this.#panel(updated, messages().agentSaved));
384
386
  return;
385
387
  }
386
- const mode: AvatarMode = action === "newprompt" ? "new_prompt" : "edit";
387
- await interaction.editReply(
388
- await this.#avatar(agent.name, mode, value("text")),
389
- );
388
+ throw new OwnerFacingError(messages().agentPanelOutdated);
389
+ }
390
+
391
+ /**
392
+ * A choice in one of the panel's two dropdowns, class or color; the reply is deferred by the
393
+ * caller. The choice goes through `dress`, which keeps the colors on a new class and the
394
+ * class on a new color.
395
+ */
396
+ async select(interaction: StringSelectMenuInteraction): Promise<void> {
397
+ const [action, name] = interaction.customId
398
+ .slice(AGENT_BUTTON_PREFIX.length)
399
+ .split(":");
400
+ const chosen = interaction.values[0];
401
+ if (
402
+ !name ||
403
+ chosen === undefined ||
404
+ (action !== "class" && action !== "color")
405
+ )
406
+ throw new OwnerFacingError(messages().agentNotFound);
407
+ const input: CharacterInput =
408
+ action === "class"
409
+ ? { class: chosen as CharacterInput["class"] }
410
+ : { palette: chosen as PaletteName };
411
+ await interaction.editReply(await this.#dress(name, input));
390
412
  }
391
413
 
392
- async #avatar(name: string, mode: AvatarMode, text?: string) {
414
+ async #dress(name: string, input: CharacterInput) {
393
415
  try {
394
- const agent = await this.#options.team.redrawAvatar(name, mode, text);
395
- return this.#panel(agent, messages().agentAvatarRedrawn);
416
+ const agent = await this.#options.team.dress(name, input);
417
+ return this.#panel(agent, messages().agentLookChanged);
396
418
  } catch (error) {
397
419
  if (!(error instanceof AgentError)) throw error;
398
420
  const agent = this.#options.store.agent(name);
399
421
  return agent
400
422
  ? this.#panel(
401
423
  agent,
402
- messages().agentAvatarFailed(error.message).slice(0, 300),
424
+ messages().agentLookFailed(error.message).slice(0, 300),
403
425
  )
404
426
  : ownerPanel({
405
427
  title: messages().ownerFailedTitle,
@@ -17,7 +17,7 @@ export interface AgentPanelOptions {
17
17
  skills?: Pick<SkillRegistry, "carried">;
18
18
  }
19
19
 
20
- /** An agent's panel: its prompt, picture, model, and skills, with buttons and forms to change them. */
20
+ /** An agent's panel: its prompt, look, model, and skills, with dropdowns, buttons and forms to change them. */
21
21
  export interface AgentPanel {
22
22
  /**
23
23
  * The panel of the agent whose channel this is. Throws OwnerFacingError, which a command run
@@ -25,7 +25,7 @@ export interface AgentPanel {
25
25
  */
26
26
  open(channelId: string): AgentPanelMessage;
27
27
  /**
28
- * Answers the panel's own buttons and form, for the owner only and deferred as Discord needs;
28
+ * Answers the panel's own buttons, dropdowns and form, for the owner only and deferred as Discord needs;
29
29
  * true when it handled the interaction. The custom ids are fixed, so panels already posted keep working.
30
30
  */
31
31
  handles(interaction: Interaction): Promise<boolean>;
@@ -51,6 +51,15 @@ export function agentPanel(options: AgentPanelOptions): AgentPanel {
51
51
  await guard.run(interaction, () => commands.button(interaction));
52
52
  return true;
53
53
  }
54
+ if (
55
+ interaction.isStringSelectMenu() &&
56
+ interaction.customId.startsWith(AGENT_BUTTON_PREFIX)
57
+ ) {
58
+ if (!(await guard.allows(interaction))) return true;
59
+ await interaction.deferReply({ flags: MessageFlags.Ephemeral });
60
+ await guard.run(interaction, () => commands.select(interaction));
61
+ return true;
62
+ }
54
63
  if (
55
64
  interaction.isModalSubmit() &&
56
65
  interaction.customId.startsWith(AGENT_MODAL_PREFIX)
@@ -1,8 +1,8 @@
1
1
  import type { AgentChannels, DashboardBoard } from "../agents/agent-ports.ts";
2
2
  import type { ChannelKey } from "../domain/conversation.ts";
3
3
  import type { OwnerOperations } from "../modules/discord-admin/discord-admin.ts";
4
+ import type { ThreadHost } from "../threads/dispatch-threads.ts";
4
5
  import type { ChannelExecutor } from "./channel-operations.ts";
5
- import type { ThreadHost } from "./dispatch-threads.ts";
6
6
 
7
7
  /** What Discord calls a channel, for the web app's lists. */
8
8
  export type ChannelInfo =
@@ -12,7 +12,7 @@ import {
12
12
  type REST,
13
13
  } from "discord.js";
14
14
  import type { AgentChannels, DashboardBoard } from "../agents/agent-ports.ts";
15
- import type { ChatSurface } from "../contract/surface.ts";
15
+ import type { ChatSurface, SurfaceThreads } from "../contract/surface.ts";
16
16
  import type {
17
17
  ChannelKey,
18
18
  InboundMessage,
@@ -25,6 +25,7 @@ import type { PromptScope, Prompts } from "../interactions/prompts.ts";
25
25
  import type { Logger } from "../log.ts";
26
26
  import type { OwnerOperations } from "../modules/discord-admin/discord-admin.ts";
27
27
  import { splitReply } from "../presentation/reply-splitter.ts";
28
+ import { threadsOfHost } from "../threads/host-threads.ts";
28
29
  import { DiscordAgentChannels, DiscordDashboard } from "./agent-discord.ts";
29
30
  import {
30
31
  type ContextSourceMessage,
@@ -77,6 +78,14 @@ export class DiscordSurface
77
78
  readonly surface = "discord";
78
79
  readonly supportsFiles = true;
79
80
  readonly formatting: string | undefined;
81
+ /** Dispatch threads, through the client as it is when each call is made. */
82
+ readonly threads: SurfaceThreads = threadsOfHost({
83
+ open: (parentId, name, line) =>
84
+ this.threadHost().open(parentId, name, line),
85
+ post: (threadId, text) => this.threadHost().post(threadId, text),
86
+ send: (threadId, text) => this.threadHost().send(threadId, text),
87
+ close: (threadId) => this.threadHost().close(threadId),
88
+ });
80
89
  readonly #options: DiscordSurfaceOptions;
81
90
  readonly #client = new Client({
82
91
  intents: [
@@ -6,7 +6,7 @@ import {
6
6
  } from "discord.js";
7
7
  import type { InterimMessage } from "../domain/interim.ts";
8
8
  import { splitReply } from "../presentation/reply-splitter.ts";
9
- import type { ThreadHost } from "./dispatch-threads.ts";
9
+ import type { ThreadHost } from "../threads/dispatch-threads.ts";
10
10
 
11
11
  /** Parents a dispatch thread may start in; DMs, threads, forums, and voice chats keep posting in place. */
12
12
  const HOSTS: readonly ChannelType[] = [
@@ -26,6 +26,12 @@ export interface HeldCall {
26
26
  * a restart honors them; a private conversation ignores them.
27
27
  */
28
28
  approverIds?: string[];
29
+ /**
30
+ * The hold classes of the rules that held it, as `<rule>.<class>` ids (see `HoldRule.holdClasses`),
31
+ * or a rule's bare name for a rule that reports none, kept with the call across a restart. Absent for a call held before 0.23 or by a check that
32
+ * has no classes: its classes are unknown, and yolo mode always asks a card for it.
33
+ */
34
+ holdClasses?: string[];
29
35
  }
30
36
 
31
37
  export interface PendingConfirmation {
@@ -55,6 +61,17 @@ export type TurnResult =
55
61
  | { ok: true; text: string; thinking?: string; files?: ReplyFile[] }
56
62
  | { ok: false; error: Error; stopped?: true };
57
63
 
64
+ /**
65
+ * Who a reply speaks as on a surface that shows authors, such as an agent in a room: the name to
66
+ * show and, for an agent of the agent team, its stable name.
67
+ */
68
+ export interface PostAuthor {
69
+ /** The name to show, such as an agent's display name. */
70
+ name: string;
71
+ /** The agent's name, when an agent of the agent team speaks. */
72
+ agent?: string;
73
+ }
74
+
58
75
  export interface OutboundReply {
59
76
  /** The assistant's thinking for this turn, posted as a quiet line before the card. */
60
77
  thinking?: string;
@@ -64,4 +81,11 @@ export interface OutboundReply {
64
81
  chunks: string[];
65
82
  /** Files the run produced, posted after the text. */
66
83
  files?: ReplyFile[];
84
+ /**
85
+ * Who speaks, for a surface that shows authors, such as a room of an agent. Absent = the
86
+ * assistant. Discord posts agents through webhooks and ignores it.
87
+ */
88
+ author?: PostAuthor;
89
+ /** Set only on the reply for a turn that ended in an error. A surface may show it; Discord ignores it. */
90
+ failed?: true;
67
91
  }