pi-roundtable 0.22.5 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +61 -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 +351 -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
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "agent": {
48
48
  "tools": [
49
+ "agent_avatar",
49
50
  "agent_create",
50
51
  "agent_get",
51
52
  "agent_list",
@@ -107,6 +108,7 @@
107
108
  "skill_update"
108
109
  ],
109
110
  "selection": [
111
+ "agent_avatar",
110
112
  "agent_create",
111
113
  "agent_get",
112
114
  "agent_list",
@@ -134,6 +136,7 @@
134
136
  },
135
137
  "group seat": {
136
138
  "tools": [
139
+ "agent_avatar",
137
140
  "agent_create",
138
141
  "agent_get",
139
142
  "agent_list",
@@ -194,6 +197,7 @@
194
197
  "skill_update"
195
198
  ],
196
199
  "selection": [
200
+ "agent_avatar",
197
201
  "agent_create",
198
202
  "agent_get",
199
203
  "agent_list",
@@ -220,6 +224,7 @@
220
224
  }
221
225
  },
222
226
  "tiers": {
227
+ "agent_avatar": "admin",
223
228
  "agent_create": "admin",
224
229
  "agent_get": "member",
225
230
  "agent_list": "member",
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export {
4
4
  AGENT_SERVER_PRIORITY,
5
5
  OWNER_TARGET,
6
6
  } from "./core/agents/agent-claim.ts";
7
+ export { characterOf } from "./core/agents/agent-rules.ts";
7
8
  export type {
8
9
  Agent,
9
10
  AgentGroup,
@@ -11,11 +12,17 @@ export type {
11
12
  AgentStatus,
12
13
  } from "./core/agents/agent-store.ts";
13
14
  export type { AvatarMode } from "./core/agents/agent-tools.ts";
15
+ export {
16
+ agentRoomKey,
17
+ groupRoomKey,
18
+ roomOf,
19
+ } from "./core/agents/team-keys.ts";
14
20
  export type {
15
21
  AgentStatus as TeamAgentStatus,
16
22
  GroupStatus,
17
23
  TeamStatus,
18
24
  } from "./core/agents/team-status.ts";
25
+ export { MAX_ATTACHMENT_BYTES } from "./core/attachments/attachment-fetcher.ts";
19
26
  export type { AttachmentRetention } from "./core/attachments/attachment-retention.ts";
20
27
  export type {
21
28
  ToolAttachment,
@@ -24,6 +31,7 @@ export type {
24
31
  export {
25
32
  AGENT_SERVER_PLUGIN,
26
33
  AGENT_TEAM_SERVICE,
34
+ LOCAL_TEAM_ID,
27
35
  } from "./core/builtin/agent-server.ts";
28
36
  export type {
29
37
  AccessConfig,
@@ -39,6 +47,7 @@ export type {
39
47
  RoundtableConfig,
40
48
  TierConfig,
41
49
  } from "./core/config/config.ts";
50
+ export type { LimitsConfig } from "./core/config/limits.ts";
42
51
  export type { SkillsConfig } from "./core/config/skills-config.ts";
43
52
  export type {
44
53
  AttachmentPort,
@@ -51,6 +60,8 @@ export type {
51
60
  export { AttachmentRefusal } from "./core/contract/attachments.ts";
52
61
  export type {
53
62
  Admission,
63
+ AttachmentContent,
64
+ AttachmentFile,
54
65
  AttachmentRef,
55
66
  BackgroundRunsAs,
56
67
  BackgroundTarget,
@@ -80,7 +91,11 @@ export type {
80
91
  } from "./core/contract/runtime.ts";
81
92
  export type { ServiceKey, Services } from "./core/contract/services.ts";
82
93
  export { serviceKey } from "./core/contract/services.ts";
83
- export type { ChatSurface, SurfacePort } from "./core/contract/surface.ts";
94
+ export type {
95
+ ChatSurface,
96
+ SurfacePort,
97
+ SurfaceThreads,
98
+ } from "./core/contract/surface.ts";
84
99
  export { channelKey, parseChannelKey } from "./core/contract/surface.ts";
85
100
  export type {
86
101
  ConversationRecord,
@@ -120,6 +135,7 @@ export type {
120
135
  HeldCall,
121
136
  OutboundReply,
122
137
  PendingConfirmation,
138
+ PostAuthor,
123
139
  ReplyFile,
124
140
  TranscriptEntry,
125
141
  TurnResult,
@@ -155,6 +171,7 @@ export {
155
171
  ProviderError,
156
172
  } from "./core/errors.ts";
157
173
  export type {
174
+ ClassedHold,
158
175
  HoldCheck,
159
176
  HoldContext,
160
177
  HoldDescription,
@@ -168,6 +185,7 @@ export type {
168
185
  ListenerAddress,
169
186
  ListenerConfig,
170
187
  } from "./core/http/listeners.ts";
188
+ export { DEFAULT_MAX_REQUEST_BODY_BYTES } from "./core/http/listeners.ts";
171
189
  export type {
172
190
  RouteSocket,
173
191
  WebSocketAccept,
@@ -226,6 +244,12 @@ export {
226
244
  export type { JudgeModel } from "./core/judging/model-judge.ts";
227
245
  export type { LogEntry, LogFn, Logger } from "./core/log.ts";
228
246
  export type { ThinkingLevel, ThinkingSetting } from "./core/models.ts";
247
+ export type {
248
+ AutoApproval,
249
+ ConversationMode,
250
+ ConversationModes,
251
+ ModeChange,
252
+ } from "./core/modes/conversation-modes.ts";
229
253
  export {
230
254
  PERSONAL_TARGET,
231
255
  type PerPrincipalLimits,
@@ -328,10 +352,12 @@ export {
328
352
  IDENTITY,
329
353
  IDENTITY_LINKS,
330
354
  MEMORY,
355
+ MODES,
331
356
  PRECHECKS,
332
357
  RUNTIME,
333
358
  SCHEDULES,
334
359
  SKILLS,
360
+ THREADS,
335
361
  WORKER_SESSIONS,
336
362
  } from "./core/services.ts";
337
363
  export type {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A character as a PNG: `composeSvg` rasterized by canvas. `size` defaults to 512; the other
3
+ * options are the composer's, defaulting to the idle face on the modern light disc as Discord shows it.
4
+ */
5
+ export { characterPng as renderCharacterPng } from "../core/agents/character-png.ts";
package/src/kit/index.ts CHANGED
@@ -17,6 +17,7 @@ export {
17
17
  withAttachmentsBlock,
18
18
  withReference,
19
19
  } from "./channels.ts";
20
+ export { renderCharacterPng } from "./characters.ts";
20
21
  export type {
21
22
  CompactionEngine,
22
23
  CompactionHistory,
@@ -134,6 +135,7 @@ export {
134
135
  SHELL_TOOLS,
135
136
  shellHoldRule,
136
137
  shellHoldRuleFor,
138
+ YOLO_CARDED_SHELL_CLASSES,
137
139
  } from "./shell.ts";
138
140
  export {
139
141
  checkRepoName,
package/src/kit/shell.ts CHANGED
@@ -6,4 +6,5 @@ export {
6
6
  SHELL_TOOLS,
7
7
  shellHoldRule,
8
8
  shellHoldRuleFor,
9
+ YOLO_CARDED_SHELL_CLASSES,
9
10
  } from "../core/modules/host-shell/shell-policy.ts";
@@ -12,7 +12,6 @@ export type {
12
12
  } from "../core/agents/agent-ports.ts";
13
13
  export type { Backlog, GroupMessage } from "../core/agents/agent-rules.ts";
14
14
  export type { AgentOps } from "../core/agents/agent-tools.ts";
15
- export type { ThreadHost } from "../core/discord/dispatch-threads.ts";
16
15
  export type { Notifier, OwnerNotifier } from "../core/domain/ports.ts";
17
16
  export type {
18
17
  EffortPolicy,
@@ -24,3 +23,4 @@ export type { DelegationWorker } from "../core/modules/delegation/delegator.ts";
24
23
  export type { AgentChannelLookup } from "../core/modules/schedules/schedules.ts";
25
24
  export type { AssistantLike } from "../core/shared/session-messages.ts";
26
25
  export type { SpeakerFacts, SpeakerPolicy } from "../core/speakers.ts";
26
+ export type { ThreadHost } from "../core/threads/dispatch-threads.ts";
@@ -4,4 +4,4 @@ export type {
4
4
  DispatchThread,
5
5
  DispatchThreads,
6
6
  DispatchThreadsOptions,
7
- } from "../core/discord/dispatch-threads.ts";
7
+ } from "../core/threads/dispatch-threads.ts";
package/src/testing.ts CHANGED
@@ -38,6 +38,7 @@ import type {
38
38
  import { NotLinkedError, PluginError } from "./core/errors.ts";
39
39
  import { EventBus } from "./core/events.ts";
40
40
  import type { HoldCheck } from "./core/holds.ts";
41
+ import { type ListenerConfig, listenerAddress } from "./core/http/listeners.ts";
41
42
  import { contactsOf } from "./core/identity/identity-view.ts";
42
43
  import type { OwnerIdentity } from "./core/identity.ts";
43
44
  import { ConfirmationJudge } from "./core/judging/confirmation-judge.ts";
@@ -256,6 +257,8 @@ export interface TestPluginOptions {
256
257
  apiKeys?: Readonly<Record<string, string>>;
257
258
  /** How long the router holds a bare forward for the message that follows it; the host option `conversations.forwardJoinMs`. */
258
259
  forwardJoinMs?: number;
260
+ /** The addresses `context.listener(id)` reports; a plugin that reads one gets `undefined` for any not listed. */
261
+ listeners?: readonly ListenerConfig[];
259
262
  }
260
263
 
261
264
  export interface RecordedEvent {
@@ -542,6 +545,7 @@ export async function testPlugin(
542
545
  Object.hasOwn(options.apiKeys ?? {}, provider)
543
546
  ? options.apiKeys?.[provider]
544
547
  : undefined,
548
+ listener: (id) => listenerAddress(options.listeners ?? [], id),
545
549
  };
546
550
  const registry = await collectContributions(
547
551
  [plugin],
@@ -10,7 +10,7 @@ export const agents: AgentSeed[] = [
10
10
  displayName: "Guide",
11
11
  prompt:
12
12
  "You are Guide, a friendly assistant. Answer briefly and ask when a request is unclear.",
13
- avatarPrompt: "A friendly lighthouse keeper with a warm lantern",
13
+ character: { class: "wizard", palette: "teal" },
14
14
  // The agent in the entry channel is the coordinator: it answers there and can create the others.
15
15
  channelId: env("DISCORD_ENTRY_CHANNEL_ID"),
16
16
  },
@@ -1,194 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- renameSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { dirname } from "node:path";
9
- import type { ChannelKey } from "../domain/conversation.ts";
10
- import type { InterimMessage, InterimPosts } from "../domain/interim.ts";
11
- import { messages } from "../i18n/index.ts";
12
- import type { Logger } from "../log.ts";
13
-
14
- const PREFIX = "discord:";
15
- /** Discord's limit on a thread name. */
16
- const THREAD_NAME_LIMIT = 100;
17
-
18
- /** The chat side of dispatch threads; the Discord surface implements it. */
19
- export interface ThreadHost {
20
- /**
21
- * Posts the start line in the parent and opens a public thread from it; `line` gets the
22
- * thread's mention once it exists. Undefined when the parent cannot host threads, such as a
23
- * DM or a thread; throws when Discord refuses.
24
- */
25
- open(
26
- parentId: string,
27
- name: string,
28
- line: (thread?: string) => string,
29
- ): Promise<string | undefined>;
30
- post(threadId: string, text: string): Promise<void>;
31
- /**
32
- * Posts one message of at most Discord's 2000 characters and resolves to it, so it can be
33
- * edited in place; without it a thread has no `interim` posts.
34
- */
35
- send?(threadId: string, text: string): Promise<InterimMessage>;
36
- /** Archives the thread, and locks it when allowed. */
37
- close(threadId: string): Promise<void>;
38
- }
39
-
40
- /** One background dispatch's thread: its progress while it runs, then its report. */
41
- export interface DispatchThread {
42
- readonly id: string;
43
- /** The thread as a conversation channel, where its owner cards are posted. */
44
- readonly channel: ChannelKey;
45
- /** `<#id>`, for the report turn and the start line. */
46
- readonly mention: string;
47
- /** Posts in the thread; a failure is logged. */
48
- post(text: string): Promise<void>;
49
- /**
50
- * Where the dispatch posts its progress as it goes, under the same name as its report, such
51
- * as a coding worker's interim text; absent when the host cannot edit a message it posted.
52
- * Unlike `post`, a failure rejects.
53
- */
54
- readonly interim?: InterimPosts;
55
- /** Posts the final report, if any, then archives the thread. Runs once; never rejects. */
56
- close(report?: string): Promise<void>;
57
- }
58
-
59
- export interface DispatchThreadsOptions {
60
- host: ThreadHost;
61
- /** Threads still open, so a start after a crash can archive them. */
62
- ledgerPath: string;
63
- /** Channels whose claims keep reports in place rather than opening threads. */
64
- excluded?: (channel: ChannelKey) => boolean;
65
- logger: Logger;
66
- }
67
-
68
- interface LedgerEntry {
69
- title: string;
70
- parent: ChannelKey;
71
- startedAt: string;
72
- }
73
-
74
- /** The thread's name: the title on one line, within Discord's limit. */
75
- export function threadName(title: string): string {
76
- const line = title.replace(/\s+/g, " ").trim() || "dispatch";
77
- return line.length > THREAD_NAME_LIMIT
78
- ? `${line.slice(0, THREAD_NAME_LIMIT - 1)}…`
79
- : line;
80
- }
81
-
82
- /**
83
- * Background dispatches (coding tasks, delegated tasks, messages between agents) each get a
84
- * thread in the channel that started them, leaving one start line there. A channel that cannot
85
- * host one, or a failed creation, gets no thread, and the dispatch posts as before.
86
- */
87
- export class DispatchThreads {
88
- readonly #options: DispatchThreadsOptions;
89
- readonly #open = new Map<string, LedgerEntry>();
90
-
91
- constructor(options: DispatchThreadsOptions) {
92
- this.#options = options;
93
- const { ledgerPath } = options;
94
- if (!existsSync(ledgerPath)) return;
95
- let parsed: unknown;
96
- try {
97
- parsed = JSON.parse(readFileSync(ledgerPath, "utf8"));
98
- } catch (error) {
99
- throw new Error(`${ledgerPath} is not valid JSON`, { cause: error });
100
- }
101
- if (typeof parsed !== "object" || parsed === null)
102
- throw new Error(`${ledgerPath} must hold a JSON object`);
103
- for (const [id, entry] of Object.entries(parsed))
104
- this.#open.set(id, entry as LedgerEntry);
105
- }
106
-
107
- /** Opens the dispatch's thread under `origin`, posting `intro` first; never rejects. */
108
- async open(
109
- origin: ChannelKey | undefined,
110
- title: string,
111
- intro?: string,
112
- ): Promise<DispatchThread | undefined> {
113
- const { host, excluded, logger } = this.#options;
114
- if (!origin?.startsWith(PREFIX) || excluded?.(origin)) return undefined;
115
- const name = threadName(title);
116
- let id: string | undefined;
117
- try {
118
- id = await host.open(origin.slice(PREFIX.length), name, (thread) =>
119
- messages().threadStarted(name, thread),
120
- );
121
- } catch (error) {
122
- logger.warn({ origin, err: error }, "dispatch thread not opened");
123
- return undefined;
124
- }
125
- if (!id) return undefined;
126
- this.#open.set(id, {
127
- title: name,
128
- parent: origin,
129
- startedAt: new Date().toISOString(),
130
- });
131
- this.#write();
132
- logger.info({ origin, thread: id }, "dispatch thread opened");
133
- const thread = this.#thread(id);
134
- if (intro) await thread.post(intro);
135
- return thread;
136
- }
137
-
138
- /** Archives the threads a restart left open; their dispatches never report. */
139
- async sweep(): Promise<void> {
140
- for (const id of [...this.#open.keys()])
141
- await this.#thread(id).close(messages().restartNotice);
142
- }
143
-
144
- #thread(id: string): DispatchThread {
145
- const { host, logger } = this.#options;
146
- let closed = false;
147
- const send = host.send?.bind(host);
148
- const post = async (text: string) => {
149
- try {
150
- await host.post(id, text);
151
- } catch (error) {
152
- logger.warn({ thread: id, err: error }, "dispatch thread post failed");
153
- }
154
- };
155
- return {
156
- id,
157
- channel: `${PREFIX}${id}`,
158
- mention: `<#${id}>`,
159
- post,
160
- ...(send ? { interim: { post: (text: string) => send(id, text) } } : {}),
161
- close: async (report) => {
162
- if (closed) return;
163
- closed = true;
164
- if (report) await post(report);
165
- try {
166
- await host.close(id);
167
- } catch (error) {
168
- logger.warn(
169
- { thread: id, err: error },
170
- "dispatch thread not archived",
171
- );
172
- }
173
- this.#open.delete(id);
174
- this.#write();
175
- },
176
- };
177
- }
178
-
179
- #write(): void {
180
- const path = this.#options.ledgerPath;
181
- try {
182
- mkdirSync(dirname(path), { recursive: true });
183
- const temporary = `${path}.tmp`;
184
- writeFileSync(
185
- temporary,
186
- `${JSON.stringify(Object.fromEntries(this.#open), null, 2)}\n`,
187
- { mode: 0o600 },
188
- );
189
- renameSync(temporary, path);
190
- } catch (error) {
191
- this.#options.logger.warn({ err: error }, "dispatch threads not saved");
192
- }
193
- }
194
- }