pi-roundtable 0.1.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 (315) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +140 -0
  4. package/docs/plugins.md +924 -0
  5. package/examples/channels.test.ts +13 -0
  6. package/examples/channels.ts +27 -0
  7. package/examples/dashboard.test.ts +11 -0
  8. package/examples/dashboard.ts +9 -0
  9. package/examples/events.test.ts +26 -0
  10. package/examples/events.ts +25 -0
  11. package/examples/guide.test.ts +52 -0
  12. package/examples/holds.test.ts +16 -0
  13. package/examples/holds.ts +30 -0
  14. package/examples/http.test.ts +12 -0
  15. package/examples/http.ts +17 -0
  16. package/examples/interactions.test.ts +25 -0
  17. package/examples/interactions.ts +28 -0
  18. package/examples/migrations.test.ts +31 -0
  19. package/examples/migrations.ts +42 -0
  20. package/examples/packages.test.ts +9 -0
  21. package/examples/packages.ts +9 -0
  22. package/examples/preflight.test.ts +11 -0
  23. package/examples/preflight.ts +19 -0
  24. package/examples/prompt.test.ts +25 -0
  25. package/examples/prompt.ts +20 -0
  26. package/examples/providers.test.ts +10 -0
  27. package/examples/providers.ts +17 -0
  28. package/examples/seeds.test.ts +12 -0
  29. package/examples/seeds.ts +18 -0
  30. package/examples/selection.test.ts +13 -0
  31. package/examples/selection.ts +14 -0
  32. package/examples/services.test.ts +20 -0
  33. package/examples/services.ts +30 -0
  34. package/examples/session-tools.test.ts +24 -0
  35. package/examples/session-tools.ts +34 -0
  36. package/examples/tools.test.ts +18 -0
  37. package/examples/tools.ts +28 -0
  38. package/package.json +55 -0
  39. package/src/cli/add-plugin.test.ts +107 -0
  40. package/src/cli/add-plugin.ts +75 -0
  41. package/src/cli/checks/basic.test.ts +276 -0
  42. package/src/cli/checks/bun.ts +26 -0
  43. package/src/cli/checks/configuration.ts +28 -0
  44. package/src/cli/checks/database.test.ts +117 -0
  45. package/src/cli/checks/database.ts +100 -0
  46. package/src/cli/checks/discord.test.ts +200 -0
  47. package/src/cli/checks/discord.ts +218 -0
  48. package/src/cli/checks/environment.ts +27 -0
  49. package/src/cli/checks/model.ts +28 -0
  50. package/src/cli/checks/public-url.ts +40 -0
  51. package/src/cli/cli.test.ts +122 -0
  52. package/src/cli/cli.ts +166 -0
  53. package/src/cli/config-edit.test.ts +130 -0
  54. package/src/cli/config-edit.ts +199 -0
  55. package/src/cli/discord-api.ts +96 -0
  56. package/src/cli/doctor.test.ts +184 -0
  57. package/src/cli/doctor.ts +92 -0
  58. package/src/cli/http.ts +23 -0
  59. package/src/cli/init.test.ts +127 -0
  60. package/src/cli/init.ts +82 -0
  61. package/src/cli/main.ts +13 -0
  62. package/src/cli/project.ts +123 -0
  63. package/src/cli/report.ts +68 -0
  64. package/src/cli/roundtable.mjs +22 -0
  65. package/src/cli/runtime.ts +53 -0
  66. package/src/cli/size.test.ts +9 -0
  67. package/src/cli/start.ts +33 -0
  68. package/src/cli/templates.test.ts +129 -0
  69. package/src/cli/templates.ts +119 -0
  70. package/src/cli/testing/fixtures.ts +106 -0
  71. package/src/core/agents/agent-claim.test.ts +142 -0
  72. package/src/core/agents/agent-claim.ts +170 -0
  73. package/src/core/agents/agent-dashboard.test.ts +120 -0
  74. package/src/core/agents/agent-dashboard.ts +185 -0
  75. package/src/core/agents/agent-guild.test.ts +124 -0
  76. package/src/core/agents/agent-messages.ts +189 -0
  77. package/src/core/agents/agent-ports.ts +103 -0
  78. package/src/core/agents/agent-prompt.ts +163 -0
  79. package/src/core/agents/agent-rules.ts +106 -0
  80. package/src/core/agents/agent-schema.ts +115 -0
  81. package/src/core/agents/agent-settings.ts +49 -0
  82. package/src/core/agents/agent-store.test.ts +205 -0
  83. package/src/core/agents/agent-store.ts +384 -0
  84. package/src/core/agents/agent-team.ts +445 -0
  85. package/src/core/agents/agent-tools.ts +386 -0
  86. package/src/core/agents/avatar-studio.test.ts +88 -0
  87. package/src/core/agents/avatar-studio.ts +147 -0
  88. package/src/core/agents/group-messages.ts +99 -0
  89. package/src/core/agents/group-round.test.ts +143 -0
  90. package/src/core/agents/group-round.ts +117 -0
  91. package/src/core/agents/group-turns.ts +132 -0
  92. package/src/core/agents/owner-identity.test.ts +172 -0
  93. package/src/core/agents/team-editing.ts +205 -0
  94. package/src/core/agents/team-keys.ts +40 -0
  95. package/src/core/agents/team-layout.ts +135 -0
  96. package/src/core/agents/team-lifecycle.ts +188 -0
  97. package/src/core/agents/team-options.ts +52 -0
  98. package/src/core/agents/team-status.ts +80 -0
  99. package/src/core/agents/team-text.ts +60 -0
  100. package/src/core/agents/team-turn-types.ts +107 -0
  101. package/src/core/agents/team-turns.test.ts +278 -0
  102. package/src/core/agents/team-turns.ts +323 -0
  103. package/src/core/assets/neutral.png +0 -0
  104. package/src/core/assets/prompts/shared-guest.md +11 -0
  105. package/src/core/assets/prompts/shared.md +11 -0
  106. package/src/core/assets/skills/writing-skills/SKILL.md +21 -0
  107. package/src/core/attachments/attachment-dir.ts +15 -0
  108. package/src/core/attachments/attachment-fetcher.ts +83 -0
  109. package/src/core/attachments/attachments.test.ts +85 -0
  110. package/src/core/attachments/image-prep.ts +50 -0
  111. package/src/core/attachments/prompt-block.ts +37 -0
  112. package/src/core/attachments/turn-attachments.ts +79 -0
  113. package/src/core/boundary.test.ts +45 -0
  114. package/src/core/builtin/agent-server.ts +275 -0
  115. package/src/core/builtin/discord.ts +95 -0
  116. package/src/core/builtin/modules.test.ts +127 -0
  117. package/src/core/builtin/modules.ts +176 -0
  118. package/src/core/builtin/seeds.ts +10 -0
  119. package/src/core/builtin/stores.ts +42 -0
  120. package/src/core/config/config.test.ts +135 -0
  121. package/src/core/config/config.ts +285 -0
  122. package/src/core/config/schema.ts +181 -0
  123. package/src/core/contract/channels.ts +152 -0
  124. package/src/core/contract/discord.ts +33 -0
  125. package/src/core/contract/providers.ts +67 -0
  126. package/src/core/db/guild-scope.ts +53 -0
  127. package/src/core/db/migrations.test.ts +237 -0
  128. package/src/core/db/migrations.ts +37 -0
  129. package/src/core/define-roundtable.test.ts +134 -0
  130. package/src/core/define-roundtable.ts +195 -0
  131. package/src/core/define.test.ts +144 -0
  132. package/src/core/define.ts +154 -0
  133. package/src/core/discord/agent-commands.test.ts +58 -0
  134. package/src/core/discord/agent-commands.ts +385 -0
  135. package/src/core/discord/agent-discord.test.ts +70 -0
  136. package/src/core/discord/agent-discord.ts +367 -0
  137. package/src/core/discord/channel-executor.ts +404 -0
  138. package/src/core/discord/channel-operations.ts +329 -0
  139. package/src/core/discord/discord-surface.ts +431 -0
  140. package/src/core/discord/dispatch-thread-host.test.ts +114 -0
  141. package/src/core/discord/dispatch-thread-host.ts +72 -0
  142. package/src/core/discord/dispatch-threads.test.ts +94 -0
  143. package/src/core/discord/dispatch-threads.ts +180 -0
  144. package/src/core/discord/interaction-module.ts +1 -0
  145. package/src/core/discord/owner-cards.test.ts +435 -0
  146. package/src/core/discord/owner-cards.ts +463 -0
  147. package/src/core/discord/owner-command.ts +126 -0
  148. package/src/core/discord/owner-discord-access.ts +183 -0
  149. package/src/core/discord/owner-discord-threads.ts +160 -0
  150. package/src/core/discord/owner-discord.test.ts +325 -0
  151. package/src/core/discord/owner-discord.ts +282 -0
  152. package/src/core/discord/owner-panel.ts +103 -0
  153. package/src/core/discord/schedule-commands.ts +149 -0
  154. package/src/core/discord/stop-button.ts +20 -0
  155. package/src/core/domain/attachment.ts +39 -0
  156. package/src/core/domain/conversation.ts +42 -0
  157. package/src/core/domain/errors.ts +31 -0
  158. package/src/core/domain/expression.ts +21 -0
  159. package/src/core/domain/owner-prompts.ts +45 -0
  160. package/src/core/domain/ports.ts +91 -0
  161. package/src/core/domain/profile.ts +32 -0
  162. package/src/core/drain.test.ts +46 -0
  163. package/src/core/drain.ts +33 -0
  164. package/src/core/errors.ts +29 -0
  165. package/src/core/events.test.ts +81 -0
  166. package/src/core/events.ts +55 -0
  167. package/src/core/holds.test.ts +54 -0
  168. package/src/core/holds.ts +46 -0
  169. package/src/core/host.test.ts +536 -0
  170. package/src/core/host.ts +297 -0
  171. package/src/core/http/listeners.test.ts +164 -0
  172. package/src/core/http/listeners.ts +151 -0
  173. package/src/core/i18n/agent-panel.ts +96 -0
  174. package/src/core/i18n/cards.ts +64 -0
  175. package/src/core/i18n/channels.ts +28 -0
  176. package/src/core/i18n/conversation.ts +49 -0
  177. package/src/core/i18n/dashboard.ts +72 -0
  178. package/src/core/i18n/discord.ts +23 -0
  179. package/src/core/i18n/en.ts +27 -0
  180. package/src/core/i18n/i18n.test.ts +139 -0
  181. package/src/core/i18n/index.ts +58 -0
  182. package/src/core/i18n/owner.ts +35 -0
  183. package/src/core/i18n/schedules.ts +92 -0
  184. package/src/core/i18n/time-zones.ts +27 -0
  185. package/src/core/i18n/types.ts +7 -0
  186. package/src/core/i18n/zh-tw.ts +26 -0
  187. package/src/core/identity.test.ts +18 -0
  188. package/src/core/identity.ts +31 -0
  189. package/src/core/judging/confirmation-judge.ts +68 -0
  190. package/src/core/judging/effort-judge.test.ts +112 -0
  191. package/src/core/judging/effort-judge.ts +158 -0
  192. package/src/core/judging/model-judge.test.ts +126 -0
  193. package/src/core/judging/model-judge.ts +193 -0
  194. package/src/core/log.ts +45 -0
  195. package/src/core/models.ts +52 -0
  196. package/src/core/modules/background/background-turns.ts +78 -0
  197. package/src/core/modules/delegation/delegate.ts +54 -0
  198. package/src/core/modules/delegation/delegator.test.ts +170 -0
  199. package/src/core/modules/delegation/delegator.ts +191 -0
  200. package/src/core/modules/delegation/sol-worker.ts +78 -0
  201. package/src/core/modules/discord-admin/discord-admin.ts +288 -0
  202. package/src/core/modules/host-shell/shell-policy.ts +386 -0
  203. package/src/core/modules/memory/owner-memory-store.test.ts +244 -0
  204. package/src/core/modules/memory/owner-memory-store.ts +228 -0
  205. package/src/core/modules/memory/owner-memory.ts +153 -0
  206. package/src/core/modules/notify/notify.ts +26 -0
  207. package/src/core/modules/schedules/recurrence.ts +206 -0
  208. package/src/core/modules/schedules/schedule-store.ts +213 -0
  209. package/src/core/modules/schedules/schedule-tools.ts +196 -0
  210. package/src/core/modules/schedules/schedule.test.ts +358 -0
  211. package/src/core/modules/schedules/scheduler.ts +121 -0
  212. package/src/core/modules/schedules/schedules.ts +87 -0
  213. package/src/core/modules/skills/repo-name.ts +11 -0
  214. package/src/core/modules/skills/skill-kind.test.ts +55 -0
  215. package/src/core/modules/skills/skill-link.ts +69 -0
  216. package/src/core/modules/skills/skill-listing.ts +128 -0
  217. package/src/core/modules/skills/skill-registry.test.ts +376 -0
  218. package/src/core/modules/skills/skill-registry.ts +323 -0
  219. package/src/core/modules/skills/skill-rules.ts +120 -0
  220. package/src/core/modules/skills/skill-store.ts +285 -0
  221. package/src/core/modules/skills/skill-tools.ts +207 -0
  222. package/src/core/ops/error-reporter.test.ts +283 -0
  223. package/src/core/ops/error-reporter.ts +271 -0
  224. package/src/core/plugin.ts +200 -0
  225. package/src/core/presentation/card-cadence.ts +41 -0
  226. package/src/core/presentation/headline.ts +34 -0
  227. package/src/core/presentation/presentation.test.ts +166 -0
  228. package/src/core/presentation/quiet-links.ts +36 -0
  229. package/src/core/presentation/reply-splitter.ts +110 -0
  230. package/src/core/presentation/thinking-line.ts +24 -0
  231. package/src/core/public-entry.test.ts +59 -0
  232. package/src/core/registry/contributions.test.ts +215 -0
  233. package/src/core/registry/contributions.ts +249 -0
  234. package/src/core/registry/interactions.test.ts +87 -0
  235. package/src/core/registry/interactions.ts +55 -0
  236. package/src/core/registry/providers.test.ts +103 -0
  237. package/src/core/registry/providers.ts +55 -0
  238. package/src/core/routing/channel-queue.test.ts +31 -0
  239. package/src/core/routing/channel-queue.ts +44 -0
  240. package/src/core/routing/channel-router.test.ts +326 -0
  241. package/src/core/routing/channel-router.ts +157 -0
  242. package/src/core/routing/conversation-kind.ts +14 -0
  243. package/src/core/routing/forward-join.ts +65 -0
  244. package/src/core/routing/message-text.ts +16 -0
  245. package/src/core/routing/settle-turn.test.ts +21 -0
  246. package/src/core/routing/settle-turn.ts +26 -0
  247. package/src/core/runtime/compaction-tiers.test.ts +227 -0
  248. package/src/core/runtime/compaction-tiers.ts +203 -0
  249. package/src/core/runtime/conversation-sessions.ts +162 -0
  250. package/src/core/runtime/extensions/agent-prompt.ts +16 -0
  251. package/src/core/runtime/extensions/ask-user.test.ts +114 -0
  252. package/src/core/runtime/extensions/ask-user.ts +98 -0
  253. package/src/core/runtime/extensions/confirmation-gate.ts +247 -0
  254. package/src/core/runtime/extensions/self-compact-guard.test.ts +50 -0
  255. package/src/core/runtime/extensions/self-compact-guard.ts +23 -0
  256. package/src/core/runtime/mcp.ts +23 -0
  257. package/src/core/runtime/pending-confirmation-store.test.ts +52 -0
  258. package/src/core/runtime/pending-confirmation-store.ts +75 -0
  259. package/src/core/runtime/pi-agent-runtime.ts +477 -0
  260. package/src/core/runtime/prompt-slot.ts +81 -0
  261. package/src/core/runtime/runtime-types.ts +150 -0
  262. package/src/core/runtime/session-archive.test.ts +20 -0
  263. package/src/core/runtime/session-archive.ts +17 -0
  264. package/src/core/runtime/session-factory.ts +291 -0
  265. package/src/core/runtime/steerable-run.test.ts +264 -0
  266. package/src/core/runtime/steerable-run.ts +109 -0
  267. package/src/core/runtime/text-tools.test.ts +110 -0
  268. package/src/core/runtime/text-tools.ts +66 -0
  269. package/src/core/runtime/turn-answer.test.ts +72 -0
  270. package/src/core/runtime/turn-answer.ts +79 -0
  271. package/src/core/runtime/worker-task.test.ts +82 -0
  272. package/src/core/runtime/worker-task.ts +65 -0
  273. package/src/core/services.test.ts +28 -0
  274. package/src/core/services.ts +107 -0
  275. package/src/core/sessions.test.ts +85 -0
  276. package/src/core/sessions.ts +149 -0
  277. package/src/core/shared/attachment-reader.ts +91 -0
  278. package/src/core/shared/delegate-tool.ts +15 -0
  279. package/src/core/shared/mcp-adapter.ts +58 -0
  280. package/src/core/shared/package-dir.ts +9 -0
  281. package/src/core/shared/profile-tools.ts +16 -0
  282. package/src/core/shared/read-attachment-tool.ts +43 -0
  283. package/src/core/shared/schedule-tools.ts +118 -0
  284. package/src/core/shared/session-messages.ts +50 -0
  285. package/src/core/shared/tool-result.ts +9 -0
  286. package/src/core/shared/unix-server.ts +18 -0
  287. package/src/core/size.test.ts +9 -0
  288. package/src/core/speakers.test.ts +78 -0
  289. package/src/core/speakers.ts +143 -0
  290. package/src/core/testing/database.ts +52 -0
  291. package/src/core/testing/file-size.ts +34 -0
  292. package/src/core/testing/locale.ts +8 -0
  293. package/src/core/testing/modules.ts +97 -0
  294. package/src/core/testing/owner.ts +15 -0
  295. package/src/core/testing/thread-host.ts +60 -0
  296. package/src/core/time.test.ts +166 -0
  297. package/src/core/time.ts +67 -0
  298. package/src/core/tool-tiers.test.ts +117 -0
  299. package/src/core/tool-tiers.ts +127 -0
  300. package/src/entries.test.ts +112 -0
  301. package/src/index.ts +28 -0
  302. package/src/testing.test.ts +156 -0
  303. package/src/testing.ts +182 -0
  304. package/templates/.env.example +22 -0
  305. package/templates/README.md +11 -0
  306. package/templates/_gitignore +3 -0
  307. package/templates/agents.ts +12 -0
  308. package/templates/biome.json.tmpl +7 -0
  309. package/templates/docker-compose.yml +16 -0
  310. package/templates/package.json.tmpl +24 -0
  311. package/templates/persona/shared.md +2 -0
  312. package/templates/plugin/plugin.test.ts.tmpl +13 -0
  313. package/templates/plugin/plugin.ts +18 -0
  314. package/templates/roundtable.config.ts +24 -0
  315. package/templates/tsconfig.json.tmpl +16 -0
@@ -0,0 +1,96 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** The agent panel of `/<root> profile`: its text, buttons, and forms. */
4
+ export function agentPanelEn(ctx: CatalogContext) {
5
+ return {
6
+ agentSkillBuiltin: (name: string) => `\`${name}\` (built in)`,
7
+ agentSkills: (names: readonly string[]) =>
8
+ `**Skills** · ${names.join(", ")}`,
9
+ agentSkillMissing: (name: string, reason: string) =>
10
+ `⚠️ \`${name}\` could not be loaded: ${reason}`,
11
+ agentGroupChannel: `This is a group channel with no agent of its own. See its members and host with \`/${ctx.root} status\`; to change a member, use \`/${ctx.root} profile\` in its channel.`,
12
+ agentNoAgent: `This channel has no agent. \`/${ctx.root} profile\` works in an agent's channel in the agent server.`,
13
+ agentFollowsAssistant: `(follows ${ctx.assistant})`,
14
+ agentHeader: (v: {
15
+ displayName: string;
16
+ name: string;
17
+ model: string;
18
+ thinking: string;
19
+ follows: string;
20
+ skills: string;
21
+ }) =>
22
+ `### ${v.displayName}\n-# agent name \`${v.name}\`, cannot be changed\n**Model** · \`${v.model}\` · thinking \`${v.thinking}\`${v.follows}\n${v.skills}\n-# To add or remove a skill, ask any agent to use agent_skills.`,
23
+ agentAvatarPromptText: (prompt: string) => `**Avatar prompt**\n${prompt}`,
24
+ agentPromptInline: (prompt: string) =>
25
+ `**Prompt**\n\`\`\`\n${prompt}\n\`\`\``,
26
+ agentPromptFile: (length: number) =>
27
+ `**Prompt** · ${length} characters, the full text is in the file below.`,
28
+ agentEditPromptLabel: "Edit prompt",
29
+ agentRedrawLabel: "Redraw avatar",
30
+ agentNewAvatarPromptLabel: "New avatar prompt",
31
+ agentEditAvatarLabel: "Edit avatar",
32
+ agentModelLabel: "Model",
33
+ agentDefaultNote:
34
+ "A changed prompt takes effect from this agent's next turn; the conversation history is kept.",
35
+ agentNotFound: "That agent was not found.",
36
+ agentFollowAssistant: (value: string) =>
37
+ `Follow ${ctx.assistant} (${value})`,
38
+ agentModelModalTitle: (displayName: string) => `${displayName}'s model`,
39
+ agentEditModalTitle: (displayName: string) => `Edit ${displayName}`,
40
+ agentDisplayNameLabel: "Display name",
41
+ agentPromptLabel: "Prompt",
42
+ agentAvatarPromptLabel: "Avatar prompt (state the background colour)",
43
+ agentEditAvatarField: "How to change it (for example: add a hat)",
44
+ agentSaved: "Saved; it takes effect from the next turn.",
45
+ agentAvatarRedrawn: "The new avatar shows from this agent's next message.",
46
+ agentAvatarFailed: (message: string) =>
47
+ `The avatar was not drawn: ${message}`,
48
+ };
49
+ }
50
+
51
+ export function agentPanelZhTW(
52
+ ctx: CatalogContext,
53
+ ): ReturnType<typeof agentPanelEn> {
54
+ return {
55
+ agentSkillBuiltin: (name: string) => `\`${name}\`(內建)`,
56
+ agentSkills: (names: readonly string[]) => `**Skill** ${names.join("、")}`,
57
+ agentSkillMissing: (name: string, reason: string) =>
58
+ `⚠️ \`${name}\` 無法載入:${reason}`,
59
+ agentGroupChannel: `這是群組頻道,沒有自己的 agent。成員和主持人請用 \`/${ctx.root} status\` 查看;要改某個成員,到它的頻道用 \`/${ctx.root} profile\`。`,
60
+ agentNoAgent: `這個頻道沒有 agent。\`/${ctx.root} profile\` 要在 agent 伺服器裡某個 agent 的頻道使用。`,
61
+ agentFollowsAssistant: `(跟隨 ${ctx.assistant})`,
62
+ agentHeader: (v: {
63
+ displayName: string;
64
+ name: string;
65
+ model: string;
66
+ thinking: string;
67
+ follows: string;
68
+ skills: string;
69
+ }) =>
70
+ `### ${v.displayName}\n-# agent 名稱 \`${v.name}\`,不能更改\n**模型** \`${v.model}\` thinking \`${v.thinking}\`${v.follows}\n${v.skills}\n-# 要增減 skill,請任一個 agent 用 agent_skills。`,
71
+ agentAvatarPromptText: (prompt: string) => `**頭像提示詞**\n${prompt}`,
72
+ agentPromptInline: (prompt: string) =>
73
+ `**提示詞**\n\`\`\`\n${prompt}\n\`\`\``,
74
+ agentPromptFile: (length: number) =>
75
+ `**提示詞** ${length} 字,完整內容在下面的檔案。`,
76
+ agentEditPromptLabel: "編輯提示詞",
77
+ agentRedrawLabel: "重畫頭像",
78
+ agentNewAvatarPromptLabel: "新頭像提示詞",
79
+ agentEditAvatarLabel: "修改頭像",
80
+ agentModelLabel: "模型",
81
+ agentDefaultNote:
82
+ "提示詞改完,從這個 agent 的下一回合開始生效;對話紀錄保留。",
83
+ agentNotFound: "找不到這個 agent。",
84
+ agentFollowAssistant: (value: string) =>
85
+ `跟隨 ${ctx.assistant}(${value})`,
86
+ agentModelModalTitle: (displayName: string) => `${displayName} 的模型`,
87
+ agentEditModalTitle: (displayName: string) => `編輯 ${displayName}`,
88
+ agentDisplayNameLabel: "顯示名",
89
+ agentPromptLabel: "提示詞",
90
+ agentAvatarPromptLabel: "頭像提示詞(寫明背景色)",
91
+ agentEditAvatarField: "要怎麼改(例如:加一頂帽子)",
92
+ agentSaved: "已儲存,從下一回合開始生效。",
93
+ agentAvatarRedrawn: "新頭像從這個 agent 的下一則訊息開始顯示。",
94
+ agentAvatarFailed: (message: string) => `頭像沒有畫成:${message}`,
95
+ };
96
+ }
@@ -0,0 +1,64 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** The owner's approval and question cards. */
4
+ export function cardsEn(ctx: CatalogContext) {
5
+ return {
6
+ cardRunLabel: "Run",
7
+ cardCancelLabel: "Cancel",
8
+ cardApprovalFooter:
9
+ "If nobody answers within 30 minutes, it falls back to waiting for your confirmation in text.",
10
+ cardQuestionFooter: "Valid for 30 minutes.",
11
+ cardStopped: "⏹️ This round was stopped.",
12
+ cardExpired: "⌛ Timed out with no answer.",
13
+ cardOwnerOnly: `Only ${ctx.assistant}'s owner can answer this card.`,
14
+ cardApproversNote: (minTier: string) =>
15
+ `-# Who can approve: ${minTier} and above`,
16
+ cardApproversRefusal: (minTier: string) =>
17
+ `This card needs ${minTier} or above to answer.`,
18
+ cardAskerNote: (userId: string) => `-# Who can answer: <@${userId}>`,
19
+ cardAskerRefusal: "This question is for another speaker.",
20
+ cardInactive: `This card is no longer active (it timed out, or ${ctx.assistant} restarted); reply in text if you need to.`,
21
+ cardApproved: "✅ Approved to run.",
22
+ cardDeclined: "❌ Cancelled.",
23
+ cardAnswerLabel: "Answer…",
24
+ cardOtherLabel: "Other…",
25
+ cardOtherDescription: "Write your own answer",
26
+ cardPickManyPlaceholder: "Pick as many as you like",
27
+ cardPickOnePlaceholder: "Pick one",
28
+ cardModalTitle: "Answer",
29
+ cardModalField: "Your answer",
30
+ cardQuote: (text: string) => `“${text}”`,
31
+ cardAnswered: (parts: readonly string[]) =>
32
+ `Answered: ${parts.join(", ").slice(0, 300)}`,
33
+ };
34
+ }
35
+
36
+ export function cardsZhTW(ctx: CatalogContext): ReturnType<typeof cardsEn> {
37
+ return {
38
+ cardRunLabel: "執行",
39
+ cardCancelLabel: "取消",
40
+ cardApprovalFooter: "30 分鐘內沒回覆,就改成等你用文字確認。",
41
+ cardQuestionFooter: "30 分鐘內有效。",
42
+ cardStopped: "⏹️ 這一輪已停止。",
43
+ cardExpired: "⌛ 已逾時,沒有回答。",
44
+ cardOwnerOnly: `只有 ${ctx.assistant} 的擁有者能回答這張卡片。`,
45
+ cardApproversNote: (minTier: string) => `-# 可以核准的人:${minTier} 以上`,
46
+ cardApproversRefusal: (minTier: string) =>
47
+ `這張卡片要 ${minTier} 以上才能回答。`,
48
+ cardAskerNote: (userId: string) => `-# 可以回答的人:<@${userId}>`,
49
+ cardAskerRefusal: "這張問題是問另一位發話者的。",
50
+ cardInactive: `這張卡片已經失效(逾時,或 ${ctx.assistant} 重新啟動過);需要的話直接打字回覆。`,
51
+ cardApproved: "✅ 已核准執行。",
52
+ cardDeclined: "❌ 已取消。",
53
+ cardAnswerLabel: "回答…",
54
+ cardOtherLabel: "其他…",
55
+ cardOtherDescription: "自己寫答案",
56
+ cardPickManyPlaceholder: "可以選好幾個",
57
+ cardPickOnePlaceholder: "選一個",
58
+ cardModalTitle: "回答",
59
+ cardModalField: "你的回答",
60
+ cardQuote: (text: string) => `「${text}」`,
61
+ cardAnswered: (parts: readonly string[]) =>
62
+ `已回答:${parts.join("、").slice(0, 300)}`,
63
+ };
64
+ }
@@ -0,0 +1,28 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** What each channel operation is called in the owner's grant panels. */
4
+ export function channelsEn(_ctx: CatalogContext) {
5
+ return {
6
+ opRead: "Read messages and channel data",
7
+ opSend: "Send text, images, and attachments",
8
+ opEdit: "Edit the bot's messages and attachments",
9
+ opPin: "Pin and unpin",
10
+ opDelete: "Delete messages",
11
+ opChannel: "Change channel settings",
12
+ opPermissions: "Change channel permissions",
13
+ };
14
+ }
15
+
16
+ export function channelsZhTW(
17
+ _ctx: CatalogContext,
18
+ ): ReturnType<typeof channelsEn> {
19
+ return {
20
+ opRead: "讀取訊息與頻道資料",
21
+ opSend: "發送文字、圖片與附件",
22
+ opEdit: "修改 Bot 訊息與附件",
23
+ opPin: "置頂與取消置頂",
24
+ opDelete: "刪除訊息",
25
+ opChannel: "修改頻道設定",
26
+ opPermissions: "修改頻道權限",
27
+ };
28
+ }
@@ -0,0 +1,49 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** Notices, thread lines, and titles a conversation posts on its own. */
4
+ export function conversationEn(ctx: CatalogContext) {
5
+ return {
6
+ failureNotice:
7
+ "Sorry, this reply did not go through. Please try again later.",
8
+ stoppedNotice: "-# Stopped.",
9
+ roundLimitNotice:
10
+ "-# This round already has 8 replies, so it stops here; call us again if you need more.",
11
+ restartNotice: `-# ${ctx.assistant} restarted before this job finished, so nothing more will be reported here.`,
12
+ threadStarted: (name: string, thread: string | undefined) =>
13
+ thread ? `🧵 Started: ${name} → ${thread}` : `🧵 Started: ${name}`,
14
+ delegationTask: (task: string) => `📋 Task:\n${task}`,
15
+ delegationDone: (report: string) => `✅ Done\n\n${report}`,
16
+ delegationFailed: (error: string) => `❌ Failed: ${error}`,
17
+ agentNoReply: (message: string) => `❌ No reply: ${message}`,
18
+ messageDelivered: (displayName: string, text: string) =>
19
+ `📨 To **${displayName}**:\n${text}`,
20
+ answerReturned: (text: string) => `↩️ Reply:\n${text}`,
21
+ thinkingAuto: "auto",
22
+ confirmTitle: (asker: string) => `${asker} wants to run this action`,
23
+ askTitle: (asker: string) => `${asker} has a question for you`,
24
+ };
25
+ }
26
+
27
+ export function conversationZhTW(
28
+ ctx: CatalogContext,
29
+ ): ReturnType<typeof conversationEn> {
30
+ return {
31
+ failureNotice: "抱歉,這次沒能完成回覆,請稍後再試一次。",
32
+ stoppedNotice: "-# 已停止。",
33
+ roundLimitNotice:
34
+ "-# 這一輪已經有 8 則回覆,先停在這裡;需要的話請再叫我們。",
35
+ restartNotice: `-# ${ctx.assistant} 在這項工作結束前重新啟動了,這裡不會再有回報。`,
36
+ threadStarted: (name: string, thread: string | undefined) =>
37
+ thread ? `🧵 開始:${name} → ${thread}` : `🧵 開始:${name}`,
38
+ delegationTask: (task: string) => `📋 任務:\n${task}`,
39
+ delegationDone: (report: string) => `✅ 完成\n\n${report}`,
40
+ delegationFailed: (error: string) => `❌ 失敗:${error}`,
41
+ agentNoReply: (message: string) => `❌ 沒有回覆:${message}`,
42
+ messageDelivered: (displayName: string, text: string) =>
43
+ `📨 給 **${displayName}**:\n${text}`,
44
+ answerReturned: (text: string) => `↩️ 回覆:\n${text}`,
45
+ thinkingAuto: "自動",
46
+ confirmTitle: (asker: string) => `${asker} 要執行這個動作`,
47
+ askTitle: (asker: string) => `${asker} 想問你`,
48
+ };
49
+ }
@@ -0,0 +1,72 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** The dashboard message, its channel topic, group topics, and the assistant's webhook name. */
4
+ export function dashboardEn(ctx: CatalogContext) {
5
+ return {
6
+ dashTitle: "## Agent server",
7
+ dashUpdated: (time: string) =>
8
+ `-# Updated ${time}; refreshed when a turn starts or ends, at most every 10 seconds`,
9
+ dashGroupsHeading: "### Groups",
10
+ dashContextNone: "context —",
11
+ dashContextCompacted: (limit: string) =>
12
+ `context just compacted, updates next turn (limit ${limit})`,
13
+ dashContext: (tokens: string, limit: string, percent: number) =>
14
+ `context ${tokens} / ${limit} (${percent}%)`,
15
+ dashWorking: (channelId: string, waiting: number) =>
16
+ `🟠 Working (<#${channelId}>)${waiting > 0 ? `, ${waiting} queued` : ""}`,
17
+ dashQueued: (waiting: number) => `🟡 ${waiting} queued`,
18
+ dashIdle: "🟢 Idle",
19
+ dashLastActive: (time: string) => `Last active ${time}`,
20
+ dashNotActive: "No activity since startup",
21
+ dashAgentFooter: (schedules: number, last: string) =>
22
+ `-# Schedules ${schedules} · ${last}`,
23
+ dashGroupBusy: (busy: number) => `🟠 ${busy} in progress`,
24
+ dashGroupMembers: (members: readonly string[], host: string) =>
25
+ `Members: ${members.join(", ")} | Host: ${host}`,
26
+ dashMore: (count: number) => `-# ${count} more not listed.`,
27
+ dashNone: "-# None.",
28
+ dashTopic: `Status of ${ctx.assistant}'s agent server; this message updates itself.`,
29
+ groupTopic: (
30
+ displayName: string,
31
+ members: readonly string[],
32
+ host: string,
33
+ ) => `${displayName} | Members: ${members.join(", ")} | Host: ${host}`,
34
+ webhookName: `${ctx.assistant} agents`,
35
+ };
36
+ }
37
+
38
+ export function dashboardZhTW(
39
+ ctx: CatalogContext,
40
+ ): ReturnType<typeof dashboardEn> {
41
+ return {
42
+ dashTitle: "## Agent 伺服器",
43
+ dashUpdated: (time: string) =>
44
+ `-# 更新於 ${time};回合開始、結束時更新,最快每 10 秒一次`,
45
+ dashGroupsHeading: "### 群組",
46
+ dashContextNone: "context —",
47
+ dashContextCompacted: (limit: string) =>
48
+ `context 剛壓縮,下一回合更新(上限 ${limit})`,
49
+ dashContext: (tokens: string, limit: string, percent: number) =>
50
+ `context ${tokens} / ${limit}(${percent}%)`,
51
+ dashWorking: (channelId: string, waiting: number) =>
52
+ `🟠 工作中(<#${channelId}>)${waiting > 0 ? `,排隊 ${waiting}` : ""}`,
53
+ dashQueued: (waiting: number) => `🟡 排隊 ${waiting}`,
54
+ dashIdle: "🟢 閒置",
55
+ dashLastActive: (time: string) => `最後活動 ${time}`,
56
+ dashNotActive: "啟動後尚未活動",
57
+ dashAgentFooter: (schedules: number, last: string) =>
58
+ `-# 排程 ${schedules} · ${last}`,
59
+ dashGroupBusy: (busy: number) => `🟠 進行中(${busy} 則)`,
60
+ dashGroupMembers: (members: readonly string[], host: string) =>
61
+ `成員:${members.join("、")}|主持:${host}`,
62
+ dashMore: (count: number) => `-# 還有 ${count} 項沒列出。`,
63
+ dashNone: "-# 沒有。",
64
+ dashTopic: `${ctx.assistant} 的 agent 伺服器狀態,這則訊息會自動更新。`,
65
+ groupTopic: (
66
+ displayName: string,
67
+ members: readonly string[],
68
+ host: string,
69
+ ) => `${displayName}|成員:${members.join("、")}|主持:${host}`,
70
+ webhookName: `${ctx.assistant} agents`,
71
+ };
72
+ }
@@ -0,0 +1,23 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /**
4
+ * Audit-log reasons and the refusals of the owner's Discord tools. They read as English in both
5
+ * locales: the audit log and the tools' answers are read by the owner and the model alike.
6
+ */
7
+ export function discordEn(ctx: CatalogContext) {
8
+ return {
9
+ auditArranged: `${ctx.assistant} arranged the agent channels`,
10
+ auditArchived: `${ctx.assistant} archived this channel's agent or group`,
11
+ auditWebhook: `${ctx.assistant} agents speak here`,
12
+ auditRequested: (ownerName: string) =>
13
+ `${ctx.assistant}, at ${ownerName}'s request`,
14
+ refuseNotInServer: `${ctx.assistant} is not in that server`,
15
+ refuseNotVisible: `not a server channel ${ctx.assistant} can see`,
16
+ refuseAssistantLacks: (names: string) =>
17
+ `${ctx.assistant} lacks ${names} there`,
18
+ };
19
+ }
20
+
21
+ export function discordZhTW(ctx: CatalogContext): ReturnType<typeof discordEn> {
22
+ return discordEn(ctx);
23
+ }
@@ -0,0 +1,27 @@
1
+ import { agentPanelEn } from "./agent-panel.ts";
2
+ import { cardsEn } from "./cards.ts";
3
+ import { channelsEn } from "./channels.ts";
4
+ import { conversationEn } from "./conversation.ts";
5
+ import { dashboardEn } from "./dashboard.ts";
6
+ import { discordEn } from "./discord.ts";
7
+ import { ownerEn } from "./owner.ts";
8
+ import { schedulesEn } from "./schedules.ts";
9
+ import { timeZonesEn } from "./time-zones.ts";
10
+ import type { CatalogContext } from "./types.ts";
11
+
12
+ export type Messages = ReturnType<typeof createEn>;
13
+
14
+ /** The English catalog, the package's default. */
15
+ export function createEn(ctx: CatalogContext) {
16
+ return {
17
+ ...cardsEn(ctx),
18
+ ...ownerEn(ctx),
19
+ ...schedulesEn(ctx),
20
+ ...timeZonesEn(),
21
+ ...agentPanelEn(ctx),
22
+ ...conversationEn(ctx),
23
+ ...dashboardEn(ctx),
24
+ ...channelsEn(ctx),
25
+ ...discordEn(ctx),
26
+ };
27
+ }
@@ -0,0 +1,139 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Glob } from "bun";
5
+ import { useTestLocale } from "../testing/locale.ts";
6
+ import { createEn } from "./en.ts";
7
+ import { LOCALES, type Messages, messages, setLocale } from "./index.ts";
8
+ import { createZhTW } from "./zh-tw.ts";
9
+
10
+ const CTX = { assistant: "Roundtable", root: "roundtable" };
11
+ const CJK = /[\u3400-\u9fff\u3000-\u303f\uff00-\uffef]/;
12
+
13
+ type Entry = string | ((...args: never[]) => unknown);
14
+
15
+ /** Calls a message function with plain arguments of one shape; the first shape it accepts wins. */
16
+ function render(entry: (...values: unknown[]) => unknown): string {
17
+ for (const arg of ["x", ["x"], 1, { x: "x" }]) {
18
+ try {
19
+ return JSON.stringify(
20
+ entry(...Array.from({ length: entry.length }, () => arg)),
21
+ );
22
+ } catch {
23
+ // Try the next shape.
24
+ }
25
+ }
26
+ throw new Error(`no plain arguments fit ${entry.toString().slice(0, 60)}`);
27
+ }
28
+
29
+ /** Every entry of a catalog as text: a string as it is, a function called with plain arguments. */
30
+ function rendered(catalog: Messages): [string, string][] {
31
+ return Object.entries(catalog as Record<string, unknown>).map(
32
+ ([key, entry]) => [
33
+ key,
34
+ typeof entry === "string"
35
+ ? entry
36
+ : typeof entry === "function"
37
+ ? render(entry as (...values: unknown[]) => unknown)
38
+ : JSON.stringify(entry),
39
+ ],
40
+ );
41
+ }
42
+
43
+ const kind = (entry: unknown): string =>
44
+ typeof entry === "function" ? `function/${entry.length}` : typeof entry;
45
+
46
+ afterEach(() => {
47
+ useTestLocale();
48
+ });
49
+
50
+ describe("catalogs", () => {
51
+ test("the English and Traditional Chinese catalogs have the same keys, and functions the same arity", () => {
52
+ const en = createEn(CTX) as Record<string, Entry>;
53
+ const zh = createZhTW(CTX) as Record<string, Entry>;
54
+ expect(Object.keys(zh).sort()).toEqual(Object.keys(en).sort());
55
+ const shapes = (catalog: Record<string, Entry>) =>
56
+ Object.fromEntries(
57
+ Object.entries(catalog).map(([key, entry]) => [key, kind(entry)]),
58
+ );
59
+ expect(shapes(zh)).toEqual(shapes(en));
60
+ });
61
+
62
+ test("the English catalog has no Chinese text or fullwidth punctuation", () => {
63
+ const offenders = rendered(createEn(CTX))
64
+ .filter(([, text]) => CJK.test(text))
65
+ .map(([key]) => key);
66
+ expect(offenders).toEqual([]);
67
+ });
68
+
69
+ test("the Traditional Chinese catalog is not the English one", () => {
70
+ const en = new Map(rendered(createEn(CTX)));
71
+ const same = rendered(createZhTW(CTX)).filter(
72
+ ([key, text]) => en.get(key) === text,
73
+ );
74
+ // A few entries are the same in both languages, such as a bare command name.
75
+ expect(same.length).toBeLessThan(en.size / 5);
76
+ });
77
+ });
78
+
79
+ describe("setLocale", () => {
80
+ test("names the assistant and the root command in the text", () => {
81
+ setLocale("en", { assistant: "Robin", root: "robin" });
82
+ const text = JSON.stringify(rendered(messages()));
83
+ expect(text).toContain("Robin");
84
+ expect(text).toContain("/robin");
85
+ expect(text).not.toContain("Roundtable");
86
+ });
87
+
88
+ test("shows English by default-style settings and Chinese for zh-TW", () => {
89
+ setLocale("en", CTX);
90
+ expect(messages().stopLabel).toBe("Stop");
91
+ setLocale("zh-TW", CTX);
92
+ expect(messages().stopLabel).toBe("停止");
93
+ });
94
+
95
+ test("an override replaces one message and leaves the others", () => {
96
+ setLocale("en", { ...CTX, overrides: { stopLabel: "Halt" } });
97
+ expect(messages().stopLabel).toBe("Halt");
98
+ expect(messages().ownerFailedTitle).toBe("Could not complete");
99
+ });
100
+
101
+ test("an unknown locale is refused", () => {
102
+ expect(() => setLocale("fr" as never, CTX)).toThrow("Unknown locale: fr");
103
+ expect(LOCALES).toEqual(["en", "zh-TW"]);
104
+ });
105
+ });
106
+
107
+ describe("what the core shows", () => {
108
+ const files = (root: string, pattern: string) =>
109
+ [...new Glob(pattern).scanSync({ cwd: root })].map((f) => join(root, f));
110
+
111
+ test("no Chinese text outside the catalogs and the tests", () => {
112
+ const offenders = files("src/core", "**/*.ts")
113
+ .filter((f) => !f.includes("/i18n/") && !/\.test\.ts$/.test(f))
114
+ .flatMap((f) =>
115
+ readFileSync(f, "utf8")
116
+ .split("\n")
117
+ .map((line, index) => ({ f, line, index }))
118
+ // Regexes may match fullwidth punctuation; only ideographs are text.
119
+ .filter(({ line }) => /[\u3400-\u9fff]/.test(line))
120
+ .map(({ f: file, index }) => `${file}:${index + 1}`),
121
+ );
122
+ expect(offenders).toEqual([]);
123
+ });
124
+
125
+ test("no source in the core names its first consumer", () => {
126
+ // Spelled in pieces so this test does not itself name the consumer.
127
+ const consumer = new RegExp(["mer", "lin"].join(""), "i");
128
+ const offenders = files("src/core", "**/*.ts")
129
+ .filter((f) => !/\.test\.ts$/.test(f) && !f.includes("/testing/"))
130
+ .flatMap((f) =>
131
+ readFileSync(f, "utf8")
132
+ .split("\n")
133
+ .map((line, index) => ({ f, line, index }))
134
+ .filter(({ line }) => consumer.test(line))
135
+ .map(({ f: file, index }) => `${file}:${index + 1}`),
136
+ );
137
+ expect(offenders).toEqual([]);
138
+ });
139
+ });
@@ -0,0 +1,58 @@
1
+ import { createEn, type Messages } from "./en.ts";
2
+ import type { CatalogContext } from "./types.ts";
3
+ import { createZhTW } from "./zh-tw.ts";
4
+
5
+ export type { CatalogContext } from "./types.ts";
6
+
7
+ export type { Messages };
8
+
9
+ export type Locale = "en" | "zh-TW";
10
+
11
+ export const LOCALES: readonly Locale[] = ["en", "zh-TW"];
12
+
13
+ export function isLocale(value: unknown): value is Locale {
14
+ return LOCALES.some((locale) => locale === value);
15
+ }
16
+
17
+ export interface LocaleSettings extends CatalogContext {
18
+ /** Entries that replace the locale's own, such as one operator's wording of a card. */
19
+ overrides?: Partial<Messages>;
20
+ }
21
+
22
+ const DEFAULTS: CatalogContext = {
23
+ assistant: "Roundtable",
24
+ root: "roundtable",
25
+ };
26
+
27
+ function build(locale: Locale, settings: LocaleSettings): Messages {
28
+ const ctx: CatalogContext = {
29
+ assistant: settings.assistant,
30
+ root: settings.root,
31
+ };
32
+ const catalog = locale === "zh-TW" ? createZhTW(ctx) : createEn(ctx);
33
+ return { ...catalog, ...settings.overrides };
34
+ }
35
+
36
+ let active: Messages = build("en", DEFAULTS);
37
+ let names: CatalogContext = DEFAULTS;
38
+
39
+ /**
40
+ * Chooses the catalog of Discord text for the whole process; the host calls it once at startup,
41
+ * before anything shows text. `assistant` is the assistant's display name and `root` the name of
42
+ * its root slash command, without the slash.
43
+ */
44
+ export function setLocale(locale: Locale, settings: LocaleSettings): void {
45
+ if (!isLocale(locale)) throw new Error(`Unknown locale: ${String(locale)}`);
46
+ active = build(locale, settings);
47
+ names = { assistant: settings.assistant, root: settings.root };
48
+ }
49
+
50
+ /** The active catalog. Read it when the text is shown, never at import time. */
51
+ export function messages(): Messages {
52
+ return active;
53
+ }
54
+
55
+ /** The assistant's display name, for text that is not part of a catalog, such as a tool description. */
56
+ export function assistantName(): string {
57
+ return names.assistant;
58
+ }
@@ -0,0 +1,35 @@
1
+ import type { CatalogContext } from "./types.ts";
2
+
3
+ /** The owner's root command, the stop button, and the failure line of an interaction. */
4
+ export function ownerEn(ctx: CatalogContext) {
5
+ return {
6
+ ownerRootDescription: `${ctx.assistant} control panel (owner only)`,
7
+ ownerFailedTitle: "Could not complete",
8
+ ownerErrorTitle: "Something went wrong",
9
+ ownerErrorBody:
10
+ "That did not finish; try again later. The details are in the log.",
11
+ ownerRefusalTitle: "Owner only",
12
+ ownerRefusalBody: `\`/${ctx.root}\` is the control panel of ${ctx.assistant}'s owner.`,
13
+ surfaceActionFailed: "Sorry, that action did not work.",
14
+ stopNote: "-# Working; press Stop to interrupt.",
15
+ stopLabel: "Stop",
16
+ panelPage: (title: string, page: number, pages: number) =>
17
+ `${title} (${page}/${pages})`,
18
+ };
19
+ }
20
+
21
+ export function ownerZhTW(ctx: CatalogContext): ReturnType<typeof ownerEn> {
22
+ return {
23
+ ownerRootDescription: `${ctx.assistant} 控制台(只有擁有者能用)`,
24
+ ownerFailedTitle: "無法完成",
25
+ ownerErrorTitle: "出了點問題",
26
+ ownerErrorBody: "這次沒能完成,請稍後再試;細節已經記在 log 裡。",
27
+ ownerRefusalTitle: "只有擁有者能用",
28
+ ownerRefusalBody: `\`/${ctx.root}\` 是 ${ctx.assistant} 擁有者的控制台。擲骰請用 \`/roll\` 或 \`/dice\`。`,
29
+ surfaceActionFailed: "抱歉,這個操作沒有成功。",
30
+ stopNote: "-# 工作中,想中斷就按停止。",
31
+ stopLabel: "停止",
32
+ panelPage: (title: string, page: number, pages: number) =>
33
+ `${title}(${page}/${pages})`,
34
+ };
35
+ }