@openacp/cli 2026.327.1 → 2026.327.3

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 (119) hide show
  1. package/dist/{adapter-Z435XYBQ.js → adapter-LC2QSDAS.js} +4 -5
  2. package/dist/{adapter-PQGHVG4K.js → adapter-Y55NXX6I.js} +2 -2
  3. package/dist/{api-server-5VEESFOT.js → api-server-7G3ZUZRM.js} +2 -2
  4. package/dist/{api-server-2I7B3MXR.js → api-server-CAYNPUF2.js} +2 -2
  5. package/dist/{chunk-5YW56UJK.js → chunk-2YCW3QDV.js} +21 -5
  6. package/dist/chunk-2YCW3QDV.js.map +1 -0
  7. package/dist/{chunk-366FOUJG.js → chunk-36YQ44D7.js} +2 -2
  8. package/dist/{chunk-UB2QB6DE.js → chunk-3ASUU6WW.js} +2 -2
  9. package/dist/{chunk-ZPTM4NGK.js → chunk-4GMLGCF2.js} +2 -2
  10. package/dist/{chunk-CFM4GJ74.js → chunk-HUWOFP2H.js} +7 -11
  11. package/dist/chunk-HUWOFP2H.js.map +1 -0
  12. package/dist/{chunk-P4SNGQNI.js → chunk-KMMEFXIE.js} +2 -2
  13. package/dist/{chunk-FDLQ5M6W.js → chunk-LP45RCA4.js} +568 -23
  14. package/dist/chunk-LP45RCA4.js.map +1 -0
  15. package/dist/{chunk-L7YNNBI5.js → chunk-QAQDGPB4.js} +1 -75
  16. package/dist/chunk-QAQDGPB4.js.map +1 -0
  17. package/dist/{chunk-V2M243KZ.js → chunk-TRXBJEZ5.js} +55 -53
  18. package/dist/chunk-TRXBJEZ5.js.map +1 -0
  19. package/dist/{chunk-7KGWYNWE.js → chunk-UMT7RU77.js} +45 -118
  20. package/dist/chunk-UMT7RU77.js.map +1 -0
  21. package/dist/{chunk-QUXTZU36.js → chunk-XIBG7LSL.js} +137 -3
  22. package/dist/chunk-XIBG7LSL.js.map +1 -0
  23. package/dist/cli.js +24 -17
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{config-editor-2GYL2SSZ.js → config-editor-3IKBPZA7.js} +2 -2
  26. package/dist/{core-plugins-I6UPXQBL.js → core-plugins-ROU4GPLT.js} +6 -7
  27. package/dist/{dev-loader-RDC5E2CW.js → dev-loader-DRU3R7ZM.js} +7 -18
  28. package/dist/dev-loader-DRU3R7ZM.js.map +1 -0
  29. package/dist/{doctor-H72BZOPA.js → doctor-QZQAP46W.js} +2 -2
  30. package/dist/index.d.ts +115 -5
  31. package/dist/index.js +45 -18
  32. package/dist/index.js.map +1 -1
  33. package/dist/{integrate-APK4OEQF.js → integrate-G6CVXTGT.js} +2 -3
  34. package/dist/integrate-G6CVXTGT.js.map +1 -0
  35. package/dist/{main-EJBK65NS.js → main-UVTZ46WP.js} +20 -147
  36. package/dist/main-UVTZ46WP.js.map +1 -0
  37. package/dist/{plugin-create-LCXXNDK6.js → plugin-create-5HQRF2ID.js} +19 -2
  38. package/dist/plugin-create-5HQRF2ID.js.map +1 -0
  39. package/dist/plugin-installer-GQ2P3Q3E.js +23 -0
  40. package/dist/plugin-installer-GQ2P3Q3E.js.map +1 -0
  41. package/dist/{post-upgrade-2MG3VUDV.js → post-upgrade-3ADZRMYJ.js} +2 -2
  42. package/dist/{setup-N7KT56O7.js → setup-EYAFK2WI.js} +77 -50
  43. package/dist/setup-EYAFK2WI.js.map +1 -0
  44. package/dist/{slack-KH7E3VBS.js → slack-37ZWBDUI.js} +2 -2
  45. package/dist/{telegram-QWMJU3A6.js → telegram-2ZCCCZIY.js} +2 -2
  46. package/dist/{tunnel-M47I7H4B.js → tunnel-45HA72MB.js} +2 -2
  47. package/dist/{tunnel-service-WADYHREX.js → tunnel-service-QJPUYEKU.js} +11 -3
  48. package/dist/tunnel-service-QJPUYEKU.js.map +1 -0
  49. package/package.json +1 -2
  50. package/dist/action-detect-QPA775HB.js +0 -16
  51. package/dist/adapter-IZNL6AK2.js +0 -2394
  52. package/dist/adapter-IZNL6AK2.js.map +0 -1
  53. package/dist/admin-GBPZFFAU.js +0 -23
  54. package/dist/agents-BWU4MRRD.js +0 -15
  55. package/dist/chunk-4KGLKKQK.js +0 -298
  56. package/dist/chunk-4KGLKKQK.js.map +0 -1
  57. package/dist/chunk-5RO42TWV.js +0 -150
  58. package/dist/chunk-5RO42TWV.js.map +0 -1
  59. package/dist/chunk-5YW56UJK.js.map +0 -1
  60. package/dist/chunk-5ZOFBTOR.js +0 -553
  61. package/dist/chunk-5ZOFBTOR.js.map +0 -1
  62. package/dist/chunk-6RXVEXF3.js +0 -23
  63. package/dist/chunk-6RXVEXF3.js.map +0 -1
  64. package/dist/chunk-7KGWYNWE.js.map +0 -1
  65. package/dist/chunk-CFM4GJ74.js.map +0 -1
  66. package/dist/chunk-FDLQ5M6W.js.map +0 -1
  67. package/dist/chunk-GJOY37U7.js +0 -265
  68. package/dist/chunk-GJOY37U7.js.map +0 -1
  69. package/dist/chunk-HVBNCPAY.js +0 -71
  70. package/dist/chunk-HVBNCPAY.js.map +0 -1
  71. package/dist/chunk-I3CGU5W7.js +0 -134
  72. package/dist/chunk-I3CGU5W7.js.map +0 -1
  73. package/dist/chunk-L7YNNBI5.js.map +0 -1
  74. package/dist/chunk-MTSDOSXS.js +0 -219
  75. package/dist/chunk-MTSDOSXS.js.map +0 -1
  76. package/dist/chunk-NAM4ERUW.js +0 -203
  77. package/dist/chunk-NAM4ERUW.js.map +0 -1
  78. package/dist/chunk-O5RG4YZY.js +0 -122
  79. package/dist/chunk-O5RG4YZY.js.map +0 -1
  80. package/dist/chunk-QUXTZU36.js.map +0 -1
  81. package/dist/chunk-V2M243KZ.js.map +0 -1
  82. package/dist/chunk-VO3A2NI4.js +0 -145
  83. package/dist/chunk-VO3A2NI4.js.map +0 -1
  84. package/dist/dev-loader-RDC5E2CW.js.map +0 -1
  85. package/dist/discord-DXDTGVGS.js +0 -8
  86. package/dist/doctor-H72BZOPA.js.map +0 -1
  87. package/dist/doctor-RF6BHMCC.js +0 -15
  88. package/dist/doctor-RF6BHMCC.js.map +0 -1
  89. package/dist/integrate-APK4OEQF.js.map +0 -1
  90. package/dist/main-EJBK65NS.js.map +0 -1
  91. package/dist/new-session-HFO5GHSZ.js +0 -17
  92. package/dist/new-session-HFO5GHSZ.js.map +0 -1
  93. package/dist/plugin-create-LCXXNDK6.js.map +0 -1
  94. package/dist/session-KZFA6Z26.js +0 -20
  95. package/dist/session-KZFA6Z26.js.map +0 -1
  96. package/dist/settings-MFYM7CZO.js +0 -14
  97. package/dist/settings-MFYM7CZO.js.map +0 -1
  98. package/dist/setup-N7KT56O7.js.map +0 -1
  99. package/dist/slack-KH7E3VBS.js.map +0 -1
  100. package/dist/telegram-QWMJU3A6.js.map +0 -1
  101. package/dist/tunnel-M47I7H4B.js.map +0 -1
  102. package/dist/tunnel-service-WADYHREX.js.map +0 -1
  103. package/dist/validators-6CLEZUBD.js +0 -8
  104. package/dist/validators-6CLEZUBD.js.map +0 -1
  105. /package/dist/{action-detect-QPA775HB.js.map → adapter-LC2QSDAS.js.map} +0 -0
  106. /package/dist/{adapter-PQGHVG4K.js.map → adapter-Y55NXX6I.js.map} +0 -0
  107. /package/dist/{adapter-Z435XYBQ.js.map → api-server-7G3ZUZRM.js.map} +0 -0
  108. /package/dist/{admin-GBPZFFAU.js.map → api-server-CAYNPUF2.js.map} +0 -0
  109. /package/dist/{chunk-366FOUJG.js.map → chunk-36YQ44D7.js.map} +0 -0
  110. /package/dist/{chunk-UB2QB6DE.js.map → chunk-3ASUU6WW.js.map} +0 -0
  111. /package/dist/{chunk-ZPTM4NGK.js.map → chunk-4GMLGCF2.js.map} +0 -0
  112. /package/dist/{chunk-P4SNGQNI.js.map → chunk-KMMEFXIE.js.map} +0 -0
  113. /package/dist/{agents-BWU4MRRD.js.map → config-editor-3IKBPZA7.js.map} +0 -0
  114. /package/dist/{api-server-2I7B3MXR.js.map → core-plugins-ROU4GPLT.js.map} +0 -0
  115. /package/dist/{api-server-5VEESFOT.js.map → doctor-QZQAP46W.js.map} +0 -0
  116. /package/dist/{post-upgrade-2MG3VUDV.js.map → post-upgrade-3ADZRMYJ.js.map} +0 -0
  117. /package/dist/{config-editor-2GYL2SSZ.js.map → slack-37ZWBDUI.js.map} +0 -0
  118. /package/dist/{core-plugins-I6UPXQBL.js.map → telegram-2ZCCCZIY.js.map} +0 -0
  119. /package/dist/{discord-DXDTGVGS.js.map → tunnel-45HA72MB.js.map} +0 -0
@@ -1,219 +0,0 @@
1
- import {
2
- log
3
- } from "./chunk-XMMAGAT4.js";
4
-
5
- // src/plugins/discord/commands/admin.ts
6
- import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
7
- async function handleDangerous(interaction, adapter) {
8
- await interaction.deferReply({ ephemeral: true });
9
- const channelId = interaction.channelId;
10
- const session = adapter.core.sessionManager.getSessionByThread(
11
- "discord",
12
- channelId
13
- );
14
- if (session) {
15
- session.dangerousMode = !session.dangerousMode;
16
- adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {
17
- });
18
- log.info(
19
- { sessionId: session.id, dangerousMode: session.dangerousMode },
20
- "[discord-admin] Dangerous mode toggled via command"
21
- );
22
- const msg2 = session.dangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
23
- await interaction.editReply(msg2);
24
- return;
25
- }
26
- const record = adapter.core.sessionManager.getRecordByThread(
27
- "discord",
28
- channelId
29
- );
30
- if (!record || record.status === "cancelled" || record.status === "error") {
31
- await interaction.editReply("\u26A0\uFE0F No active session in this channel.");
32
- return;
33
- }
34
- const newDangerousMode = !(record.dangerousMode ?? false);
35
- adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {
36
- });
37
- log.info(
38
- { sessionId: record.sessionId, dangerousMode: newDangerousMode },
39
- "[discord-admin] Dangerous mode toggled via command (store-only)"
40
- );
41
- const msg = newDangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
42
- await interaction.editReply(msg);
43
- }
44
- async function handleDangerousButton(interaction, adapter) {
45
- const sessionId = interaction.customId.slice(2);
46
- const session = adapter.core.sessionManager.getSession(sessionId);
47
- if (session) {
48
- session.dangerousMode = !session.dangerousMode;
49
- adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
50
- });
51
- log.info(
52
- { sessionId, dangerousMode: session.dangerousMode },
53
- "[discord-admin] Dangerous mode toggled via button"
54
- );
55
- const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
56
- try {
57
- await interaction.update({
58
- components: [
59
- buildSessionControlKeyboard(
60
- sessionId,
61
- session.dangerousMode,
62
- session.voiceMode === "on"
63
- )
64
- ]
65
- });
66
- } catch {
67
- }
68
- try {
69
- await interaction.followUp({ content: toastText2, ephemeral: true });
70
- } catch {
71
- }
72
- return;
73
- }
74
- const record = adapter.core.sessionManager.getSessionRecord(sessionId);
75
- if (!record || record.status === "cancelled" || record.status === "error") {
76
- await interaction.reply({
77
- content: "\u26A0\uFE0F Session not found or already ended.",
78
- ephemeral: true
79
- });
80
- return;
81
- }
82
- const newDangerousMode = !(record.dangerousMode ?? false);
83
- adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
84
- });
85
- log.info(
86
- { sessionId, dangerousMode: newDangerousMode },
87
- "[discord-admin] Dangerous mode toggled via button (store-only)"
88
- );
89
- const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
90
- try {
91
- await interaction.update({
92
- components: [
93
- buildSessionControlKeyboard(sessionId, newDangerousMode, false)
94
- ]
95
- });
96
- } catch {
97
- }
98
- try {
99
- await interaction.followUp({ content: toastText, ephemeral: true });
100
- } catch {
101
- }
102
- }
103
- function buildSessionControlKeyboard(sessionId, dangerousMode, voiceMode) {
104
- return new ActionRowBuilder().addComponents(
105
- new ButtonBuilder().setCustomId(`d:${sessionId}`).setLabel(
106
- dangerousMode ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode"
107
- ).setStyle(dangerousMode ? ButtonStyle.Secondary : ButtonStyle.Danger),
108
- new ButtonBuilder().setCustomId(`v:${sessionId}`).setLabel(voiceMode ? "\u{1F50A} Text to Speech" : "\u{1F507} Text to Speech").setStyle(voiceMode ? ButtonStyle.Success : ButtonStyle.Secondary)
109
- );
110
- }
111
- async function handleTTS(interaction, adapter) {
112
- await interaction.deferReply({ ephemeral: true });
113
- const channelId = interaction.channelId;
114
- const session = adapter.core.sessionManager.getSessionByThread(
115
- "discord",
116
- channelId
117
- );
118
- if (!session) {
119
- await interaction.editReply("\u26A0\uFE0F No active session in this channel.");
120
- return;
121
- }
122
- const mode = interaction.options.getString("mode");
123
- if (mode === "on") {
124
- session.setVoiceMode("on");
125
- await interaction.editReply("\u{1F50A} Text to Speech enabled for this session.");
126
- } else if (mode === "off") {
127
- session.setVoiceMode("off");
128
- await interaction.editReply("\u{1F507} Text to Speech disabled.");
129
- } else {
130
- session.setVoiceMode("next");
131
- await interaction.editReply(
132
- "\u{1F50A} Text to Speech enabled for the next message."
133
- );
134
- }
135
- }
136
- async function handleTTSButton(interaction, adapter) {
137
- const sessionId = interaction.customId.slice(2);
138
- const session = adapter.core.sessionManager.getSession(sessionId);
139
- if (!session) {
140
- await interaction.reply({
141
- content: "\u26A0\uFE0F Session not found or not active.",
142
- ephemeral: true
143
- });
144
- return;
145
- }
146
- const newMode = session.voiceMode === "on" ? "off" : "on";
147
- session.setVoiceMode(newMode);
148
- const toastText = newMode === "on" ? "\u{1F50A} Text to Speech enabled" : "\u{1F507} Text to Speech disabled";
149
- try {
150
- await interaction.update({
151
- components: [
152
- buildSessionControlKeyboard(
153
- sessionId,
154
- session.dangerousMode,
155
- newMode === "on"
156
- )
157
- ]
158
- });
159
- } catch {
160
- }
161
- try {
162
- await interaction.followUp({ content: toastText, ephemeral: true });
163
- } catch {
164
- }
165
- }
166
- async function handleRestart(interaction, adapter) {
167
- await interaction.deferReply({ ephemeral: true });
168
- if (!adapter.core.requestRestart) {
169
- await interaction.editReply(
170
- "\u26A0\uFE0F Restart is not available (no restart handler registered)."
171
- );
172
- return;
173
- }
174
- await interaction.editReply(
175
- "\u{1F504} **Restarting OpenACP...**\nRebuilding and restarting. Be back shortly."
176
- );
177
- await new Promise((r) => setTimeout(r, 500));
178
- await adapter.core.requestRestart();
179
- }
180
- async function handleUpdate(interaction, adapter) {
181
- await interaction.deferReply({ ephemeral: true });
182
- await interaction.editReply(
183
- "\u26A0\uFE0F Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`."
184
- );
185
- }
186
- var VERBOSITY_LABELS = {
187
- low: "\u{1F507} Low",
188
- medium: "\u{1F4CA} Medium",
189
- high: "\u{1F4D6} High"
190
- };
191
- async function handleVerbosity(interaction, adapter) {
192
- await interaction.deferReply({ ephemeral: true });
193
- const level = interaction.options.getString("level", true);
194
- if (level !== "low" && level !== "medium" && level !== "high") {
195
- await interaction.editReply(
196
- "\u26A0\uFE0F Invalid level. Use `low`, `medium`, or `high`."
197
- );
198
- return;
199
- }
200
- await adapter.core.configManager.save(
201
- { channels: { discord: { displayVerbosity: level } } },
202
- "channels.discord.displayVerbosity"
203
- );
204
- await interaction.editReply(
205
- `${VERBOSITY_LABELS[level]} Display verbosity set to **${level}**.`
206
- );
207
- }
208
-
209
- export {
210
- handleDangerous,
211
- handleDangerousButton,
212
- buildSessionControlKeyboard,
213
- handleTTS,
214
- handleTTSButton,
215
- handleRestart,
216
- handleUpdate,
217
- handleVerbosity
218
- };
219
- //# sourceMappingURL=chunk-MTSDOSXS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/plugins/discord/commands/admin.ts"],"sourcesContent":["import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from \"discord.js\";\nimport type {\n ChatInputCommandInteraction,\n ButtonInteraction,\n} from \"discord.js\";\nimport { log } from \"../../../core/utils/log.js\";\nimport type { DiscordAdapter } from \"../adapter.js\";\n\nexport async function handleDangerous(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true });\n\n const channelId = interaction.channelId;\n const session = adapter.core.sessionManager.getSessionByThread(\n \"discord\",\n channelId,\n );\n\n if (session) {\n session.dangerousMode = !session.dangerousMode;\n adapter.core.sessionManager\n .patchRecord(session.id, { dangerousMode: session.dangerousMode })\n .catch(() => {});\n log.info(\n { sessionId: session.id, dangerousMode: session.dangerousMode },\n \"[discord-admin] Dangerous mode toggled via command\",\n );\n\n const msg = session.dangerousMode\n ? \"☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.\"\n : \"🔐 **Dangerous mode disabled** — Permission requests will be shown normally.\";\n await interaction.editReply(msg);\n return;\n }\n\n // Session not in memory — update store directly\n const record = adapter.core.sessionManager.getRecordByThread(\n \"discord\",\n channelId,\n );\n if (!record || record.status === \"cancelled\" || record.status === \"error\") {\n await interaction.editReply(\"⚠️ No active session in this channel.\");\n return;\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false);\n adapter.core.sessionManager\n .patchRecord(record.sessionId, { dangerousMode: newDangerousMode })\n .catch(() => {});\n log.info(\n { sessionId: record.sessionId, dangerousMode: newDangerousMode },\n \"[discord-admin] Dangerous mode toggled via command (store-only)\",\n );\n\n const msg = newDangerousMode\n ? \"☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.\"\n : \"🔐 **Dangerous mode disabled** — Permission requests will be shown normally.\";\n await interaction.editReply(msg);\n}\n\nexport async function handleDangerousButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2); // strip 'd:'\n const session = adapter.core.sessionManager.getSession(sessionId);\n\n // Session live in memory — toggle directly\n if (session) {\n session.dangerousMode = !session.dangerousMode;\n adapter.core.sessionManager\n .patchRecord(sessionId, { dangerousMode: session.dangerousMode })\n .catch(() => {});\n log.info(\n { sessionId, dangerousMode: session.dangerousMode },\n \"[discord-admin] Dangerous mode toggled via button\",\n );\n\n const toastText = session.dangerousMode\n ? \"☠️ Dangerous mode enabled — permissions auto-approved\"\n : \"🔐 Dangerous mode disabled — permissions shown normally\";\n\n try {\n await interaction.update({\n components: [\n buildSessionControlKeyboard(\n sessionId,\n session.dangerousMode,\n session.voiceMode === \"on\",\n ),\n ],\n });\n } catch {\n /* ignore */\n }\n\n try {\n await interaction.followUp({ content: toastText, ephemeral: true });\n } catch {\n /* ignore */\n }\n return;\n }\n\n // Session not in memory — toggle in store\n const record = adapter.core.sessionManager.getSessionRecord(sessionId);\n if (!record || record.status === \"cancelled\" || record.status === \"error\") {\n await interaction.reply({\n content: \"⚠️ Session not found or already ended.\",\n ephemeral: true,\n });\n return;\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false);\n adapter.core.sessionManager\n .patchRecord(sessionId, { dangerousMode: newDangerousMode })\n .catch(() => {});\n log.info(\n { sessionId, dangerousMode: newDangerousMode },\n \"[discord-admin] Dangerous mode toggled via button (store-only)\",\n );\n\n const toastText = newDangerousMode\n ? \"☠️ Dangerous mode enabled — permissions auto-approved\"\n : \"🔐 Dangerous mode disabled — permissions shown normally\";\n\n try {\n // Store-only path: voiceMode unknown, default to off\n await interaction.update({\n components: [\n buildSessionControlKeyboard(sessionId, newDangerousMode, false),\n ],\n });\n } catch {\n /* ignore */\n }\n\n try {\n await interaction.followUp({ content: toastText, ephemeral: true });\n } catch {\n /* ignore */\n }\n}\n\n// ─── TTS ──────────────────────────────────────────────────────────────────────\n\nexport function buildSessionControlKeyboard(\n sessionId: string,\n dangerousMode: boolean,\n voiceMode: boolean,\n): ActionRowBuilder<ButtonBuilder> {\n return new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`d:${sessionId}`)\n .setLabel(\n dangerousMode\n ? \"🔐 Disable Dangerous Mode\"\n : \"☠️ Enable Dangerous Mode\",\n )\n .setStyle(dangerousMode ? ButtonStyle.Secondary : ButtonStyle.Danger),\n new ButtonBuilder()\n .setCustomId(`v:${sessionId}`)\n .setLabel(voiceMode ? \"🔊 Text to Speech\" : \"🔇 Text to Speech\")\n .setStyle(voiceMode ? ButtonStyle.Success : ButtonStyle.Secondary),\n );\n}\n\nexport async function handleTTS(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true });\n\n const channelId = interaction.channelId;\n const session = adapter.core.sessionManager.getSessionByThread(\n \"discord\",\n channelId,\n );\n\n if (!session) {\n await interaction.editReply(\"⚠️ No active session in this channel.\");\n return;\n }\n\n const mode = interaction.options.getString(\"mode\");\n\n if (mode === \"on\") {\n session.setVoiceMode(\"on\");\n await interaction.editReply(\"🔊 Text to Speech enabled for this session.\");\n } else if (mode === \"off\") {\n session.setVoiceMode(\"off\");\n await interaction.editReply(\"🔇 Text to Speech disabled.\");\n } else {\n session.setVoiceMode(\"next\");\n await interaction.editReply(\n \"🔊 Text to Speech enabled for the next message.\",\n );\n }\n}\n\nexport async function handleTTSButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2); // strip 'v:'\n const session = adapter.core.sessionManager.getSession(sessionId);\n\n if (!session) {\n await interaction.reply({\n content: \"⚠️ Session not found or not active.\",\n ephemeral: true,\n });\n return;\n }\n\n const newMode = session.voiceMode === \"on\" ? \"off\" : \"on\";\n session.setVoiceMode(newMode);\n\n const toastText =\n newMode === \"on\"\n ? \"🔊 Text to Speech enabled\"\n : \"🔇 Text to Speech disabled\";\n\n try {\n await interaction.update({\n components: [\n buildSessionControlKeyboard(\n sessionId,\n session.dangerousMode,\n newMode === \"on\",\n ),\n ],\n });\n } catch {\n /* ignore */\n }\n\n try {\n await interaction.followUp({ content: toastText, ephemeral: true });\n } catch {\n /* ignore */\n }\n}\n\nexport async function handleRestart(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true });\n\n if (!adapter.core.requestRestart) {\n await interaction.editReply(\n \"⚠️ Restart is not available (no restart handler registered).\",\n );\n return;\n }\n\n await interaction.editReply(\n \"🔄 **Restarting OpenACP...**\\nRebuilding and restarting. Be back shortly.\",\n );\n await new Promise((r) => setTimeout(r, 500));\n await adapter.core.requestRestart();\n}\n\nexport async function handleUpdate(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true });\n // Stub: not implemented yet\n await interaction.editReply(\n \"⚠️ Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.\",\n );\n}\n\n// ─── Verbosity ─────────────────────────────────────────────────────────────\n\nconst VERBOSITY_LABELS: Record<string, string> = {\n low: \"🔇 Low\",\n medium: \"📊 Medium\",\n high: \"📖 High\",\n};\n\nexport async function handleVerbosity(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true });\n\n const level = interaction.options.getString(\"level\", true);\n if (level !== \"low\" && level !== \"medium\" && level !== \"high\") {\n await interaction.editReply(\n \"⚠️ Invalid level. Use `low`, `medium`, or `high`.\",\n );\n return;\n }\n\n await adapter.core.configManager.save(\n { channels: { discord: { displayVerbosity: level } } },\n \"channels.discord.displayVerbosity\",\n );\n\n await interaction.editReply(\n `${VERBOSITY_LABELS[level]} Display verbosity set to **${level}**.`,\n );\n}\n"],"mappings":";;;;;AAAA,SAAS,kBAAkB,eAAe,mBAAmB;AAQ7D,eAAsB,gBACpB,aACA,SACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,QAAM,YAAY,YAAY;AAC9B,QAAM,UAAU,QAAQ,KAAK,eAAe;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AAEA,MAAI,SAAS;AACX,YAAQ,gBAAgB,CAAC,QAAQ;AACjC,YAAQ,KAAK,eACV,YAAY,QAAQ,IAAI,EAAE,eAAe,QAAQ,cAAc,CAAC,EAChE,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,QAAI;AAAA,MACF,EAAE,WAAW,QAAQ,IAAI,eAAe,QAAQ,cAAc;AAAA,MAC9D;AAAA,IACF;AAEA,UAAMA,OAAM,QAAQ,gBAChB,kGACA;AACJ,UAAM,YAAY,UAAUA,IAAG;AAC/B;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,KAAK,eAAe;AAAA,IACzC;AAAA,IACA;AAAA,EACF;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,eAAe,OAAO,WAAW,SAAS;AACzE,UAAM,YAAY,UAAU,iDAAuC;AACnE;AAAA,EACF;AAEA,QAAM,mBAAmB,EAAE,OAAO,iBAAiB;AACnD,UAAQ,KAAK,eACV,YAAY,OAAO,WAAW,EAAE,eAAe,iBAAiB,CAAC,EACjE,MAAM,MAAM;AAAA,EAAC,CAAC;AACjB,MAAI;AAAA,IACF,EAAE,WAAW,OAAO,WAAW,eAAe,iBAAiB;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,MAAM,mBACR,kGACA;AACJ,QAAM,YAAY,UAAU,GAAG;AACjC;AAEA,eAAsB,sBACpB,aACA,SACe;AACf,QAAM,YAAY,YAAY,SAAS,MAAM,CAAC;AAC9C,QAAM,UAAU,QAAQ,KAAK,eAAe,WAAW,SAAS;AAGhE,MAAI,SAAS;AACX,YAAQ,gBAAgB,CAAC,QAAQ;AACjC,YAAQ,KAAK,eACV,YAAY,WAAW,EAAE,eAAe,QAAQ,cAAc,CAAC,EAC/D,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,QAAI;AAAA,MACF,EAAE,WAAW,eAAe,QAAQ,cAAc;AAAA,MAClD;AAAA,IACF;AAEA,UAAMC,aAAY,QAAQ,gBACtB,yEACA;AAEJ,QAAI;AACF,YAAM,YAAY,OAAO;AAAA,QACvB,YAAY;AAAA,UACV;AAAA,YACE;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ,cAAc;AAAA,UACxB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAEA,QAAI;AACF,YAAM,YAAY,SAAS,EAAE,SAASA,YAAW,WAAW,KAAK,CAAC;AAAA,IACpE,QAAQ;AAAA,IAER;AACA;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,KAAK,eAAe,iBAAiB,SAAS;AACrE,MAAI,CAAC,UAAU,OAAO,WAAW,eAAe,OAAO,WAAW,SAAS;AACzE,UAAM,YAAY,MAAM;AAAA,MACtB,SAAS;AAAA,MACT,WAAW;AAAA,IACb,CAAC;AACD;AAAA,EACF;AAEA,QAAM,mBAAmB,EAAE,OAAO,iBAAiB;AACnD,UAAQ,KAAK,eACV,YAAY,WAAW,EAAE,eAAe,iBAAiB,CAAC,EAC1D,MAAM,MAAM;AAAA,EAAC,CAAC;AACjB,MAAI;AAAA,IACF,EAAE,WAAW,eAAe,iBAAiB;AAAA,IAC7C;AAAA,EACF;AAEA,QAAM,YAAY,mBACd,yEACA;AAEJ,MAAI;AAEF,UAAM,YAAY,OAAO;AAAA,MACvB,YAAY;AAAA,QACV,4BAA4B,WAAW,kBAAkB,KAAK;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,YAAY,SAAS,EAAE,SAAS,WAAW,WAAW,KAAK,CAAC;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAIO,SAAS,4BACd,WACA,eACA,WACiC;AACjC,SAAO,IAAI,iBAAgC,EAAE;AAAA,IAC3C,IAAI,cAAc,EACf,YAAY,KAAK,SAAS,EAAE,EAC5B;AAAA,MACC,gBACI,qCACA;AAAA,IACN,EACC,SAAS,gBAAgB,YAAY,YAAY,YAAY,MAAM;AAAA,IACtE,IAAI,cAAc,EACf,YAAY,KAAK,SAAS,EAAE,EAC5B,SAAS,YAAY,6BAAsB,0BAAmB,EAC9D,SAAS,YAAY,YAAY,UAAU,YAAY,SAAS;AAAA,EACrE;AACF;AAEA,eAAsB,UACpB,aACA,SACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,QAAM,YAAY,YAAY;AAC9B,QAAM,UAAU,QAAQ,KAAK,eAAe;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,YAAY,UAAU,iDAAuC;AACnE;AAAA,EACF;AAEA,QAAM,OAAO,YAAY,QAAQ,UAAU,MAAM;AAEjD,MAAI,SAAS,MAAM;AACjB,YAAQ,aAAa,IAAI;AACzB,UAAM,YAAY,UAAU,oDAA6C;AAAA,EAC3E,WAAW,SAAS,OAAO;AACzB,YAAQ,aAAa,KAAK;AAC1B,UAAM,YAAY,UAAU,oCAA6B;AAAA,EAC3D,OAAO;AACL,YAAQ,aAAa,MAAM;AAC3B,UAAM,YAAY;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,gBACpB,aACA,SACe;AACf,QAAM,YAAY,YAAY,SAAS,MAAM,CAAC;AAC9C,QAAM,UAAU,QAAQ,KAAK,eAAe,WAAW,SAAS;AAEhE,MAAI,CAAC,SAAS;AACZ,UAAM,YAAY,MAAM;AAAA,MACtB,SAAS;AAAA,MACT,WAAW;AAAA,IACb,CAAC;AACD;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,cAAc,OAAO,QAAQ;AACrD,UAAQ,aAAa,OAAO;AAE5B,QAAM,YACJ,YAAY,OACR,qCACA;AAEN,MAAI;AACF,UAAM,YAAY,OAAO;AAAA,MACvB,YAAY;AAAA,QACV;AAAA,UACE;AAAA,UACA,QAAQ;AAAA,UACR,YAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,YAAY,SAAS,EAAE,SAAS,WAAW,WAAW,KAAK,CAAC;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,cACpB,aACA,SACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,MAAI,CAAC,QAAQ,KAAK,gBAAgB;AAChC,UAAM,YAAY;AAAA,MAChB;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,YAAY;AAAA,IAChB;AAAA,EACF;AACA,QAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC3C,QAAM,QAAQ,KAAK,eAAe;AACpC;AAEA,eAAsB,aACpB,aACA,SACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,QAAM,YAAY;AAAA,IAChB;AAAA,EACF;AACF;AAIA,IAAM,mBAA2C;AAAA,EAC/C,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AACR;AAEA,eAAsB,gBACpB,aACA,SACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,QAAM,QAAQ,YAAY,QAAQ,UAAU,SAAS,IAAI;AACzD,MAAI,UAAU,SAAS,UAAU,YAAY,UAAU,QAAQ;AAC7D,UAAM,YAAY;AAAA,MAChB;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,QAAQ,KAAK,cAAc;AAAA,IAC/B,EAAE,UAAU,EAAE,SAAS,EAAE,kBAAkB,MAAM,EAAE,EAAE;AAAA,IACrD;AAAA,EACF;AAEA,QAAM,YAAY;AAAA,IAChB,GAAG,iBAAiB,KAAK,CAAC,+BAA+B,KAAK;AAAA,EAChE;AACF;","names":["msg","toastText"]}
@@ -1,203 +0,0 @@
1
- import {
2
- log
3
- } from "./chunk-XMMAGAT4.js";
4
-
5
- // src/plugins/discord/commands/agents.ts
6
- import {
7
- ActionRowBuilder,
8
- ButtonBuilder,
9
- ButtonStyle
10
- } from "discord.js";
11
- var AGENTS_PER_PAGE = 5;
12
- function buildProgressBar(percent) {
13
- const filled = Math.round(percent / 10);
14
- const empty = 10 - filled;
15
- return "\u2588".repeat(filled) + "\u2591".repeat(empty);
16
- }
17
- function truncate(text, maxLen) {
18
- if (text.length <= maxLen) return text;
19
- return text.slice(0, maxLen - 1) + "\u2026";
20
- }
21
- async function handleAgents(interaction, adapter, page = 0) {
22
- await interaction.deferReply({ ephemeral: true });
23
- const { content, components } = buildAgentsContent(adapter, page);
24
- await interaction.editReply({ content, components });
25
- }
26
- async function showAgentsList(interaction, adapter, page = 0) {
27
- const { content, components } = buildAgentsContent(adapter, page);
28
- await interaction.followUp({ content, components, ephemeral: true });
29
- }
30
- function buildAgentsContent(adapter, page) {
31
- const catalog = adapter.core.agentCatalog;
32
- const items = catalog.getAvailable();
33
- const installed = items.filter((i) => i.installed);
34
- const available = items.filter((i) => !i.installed);
35
- let content = "**\u{1F916} Agents**\n\n";
36
- if (installed.length > 0) {
37
- content += "**Installed:**\n";
38
- for (const item of installed) {
39
- content += `\u2705 **${item.name}**`;
40
- if (item.description) content += ` \u2014 *${truncate(item.description, 50)}*`;
41
- content += "\n";
42
- }
43
- content += "\n";
44
- }
45
- const components = [];
46
- if (available.length > 0) {
47
- const totalPages = Math.ceil(available.length / AGENTS_PER_PAGE);
48
- const safePage = Math.max(0, Math.min(page, totalPages - 1));
49
- const pageItems = available.slice(safePage * AGENTS_PER_PAGE, (safePage + 1) * AGENTS_PER_PAGE);
50
- content += `**Available to install:**`;
51
- if (totalPages > 1) content += ` (${safePage + 1}/${totalPages})`;
52
- content += "\n";
53
- for (const item of pageItems) {
54
- if (item.available) {
55
- content += `\u2B07\uFE0F **${item.name}**`;
56
- } else {
57
- const deps = item.missingDeps?.join(", ") ?? "requirements not met";
58
- content += `\u26A0\uFE0F **${item.name}** *(needs: ${deps})*`;
59
- }
60
- if (item.description) content += `
61
- *${truncate(item.description, 60)}*`;
62
- content += "\n";
63
- }
64
- const installable = pageItems.filter((i) => i.available);
65
- if (installable.length > 0) {
66
- const installRow = new ActionRowBuilder();
67
- for (const item of installable) {
68
- installRow.addComponents(
69
- new ButtonBuilder().setCustomId(`ag:install:${item.key}`).setLabel(`\u2B07\uFE0F ${item.name}`).setStyle(ButtonStyle.Secondary)
70
- );
71
- }
72
- components.push(installRow);
73
- }
74
- if (totalPages > 1) {
75
- const pageRow = new ActionRowBuilder();
76
- if (safePage > 0) {
77
- pageRow.addComponents(
78
- new ButtonBuilder().setCustomId(`ag:page:${safePage - 1}`).setLabel("\u25C0\uFE0F Prev").setStyle(ButtonStyle.Secondary)
79
- );
80
- }
81
- if (safePage < totalPages - 1) {
82
- pageRow.addComponents(
83
- new ButtonBuilder().setCustomId(`ag:page:${safePage + 1}`).setLabel("Next \u25B6\uFE0F").setStyle(ButtonStyle.Secondary)
84
- );
85
- }
86
- if (pageRow.components.length > 0) components.push(pageRow);
87
- }
88
- } else {
89
- content += "*All agents are already installed!*";
90
- }
91
- return { content, components };
92
- }
93
- async function handleInstall(interaction, adapter) {
94
- await interaction.deferReply({ ephemeral: true });
95
- const nameOrId = interaction.options.getString("name", true);
96
- await installAgentWithProgress(interaction, adapter, nameOrId);
97
- }
98
- async function handleAgentButton(interaction, adapter) {
99
- const { customId } = interaction;
100
- if (customId.startsWith("ag:install:")) {
101
- const nameOrId = customId.replace("ag:install:", "");
102
- try {
103
- await interaction.deferReply({ ephemeral: true });
104
- } catch {
105
- }
106
- await installAgentWithProgress(interaction, adapter, nameOrId);
107
- return;
108
- }
109
- if (customId.startsWith("ag:page:")) {
110
- const page = parseInt(customId.replace("ag:page:", ""), 10);
111
- const { content, components } = buildAgentsContent(adapter, page);
112
- try {
113
- await interaction.update({ content, components });
114
- } catch (err) {
115
- log.warn({ err }, "[discord-agents] Failed to update page");
116
- }
117
- }
118
- }
119
- async function installAgentWithProgress(interaction, adapter, nameOrId) {
120
- const catalog = adapter.core.agentCatalog;
121
- let statusText = `\u23F3 Installing **${nameOrId}**...`;
122
- let lastEdit = 0;
123
- const EDIT_THROTTLE_MS = 1500;
124
- const editStatus = async (text) => {
125
- const now = Date.now();
126
- if (now - lastEdit > EDIT_THROTTLE_MS) {
127
- lastEdit = now;
128
- statusText = text;
129
- try {
130
- if (interaction.deferred || interaction.replied) {
131
- await interaction.editReply(text);
132
- }
133
- } catch {
134
- }
135
- } else {
136
- statusText = text;
137
- }
138
- };
139
- try {
140
- if (interaction.deferred || interaction.replied) {
141
- await interaction.editReply(statusText);
142
- }
143
- } catch {
144
- }
145
- const progress = {
146
- onStart(_id, _name) {
147
- },
148
- async onStep(step) {
149
- await editStatus(`\u23F3 **${nameOrId}**: ${step}`);
150
- },
151
- async onDownloadProgress(percent) {
152
- const bar = buildProgressBar(percent);
153
- await editStatus(`\u23F3 **${nameOrId}**
154
- Downloading... ${bar} ${percent}%`);
155
- },
156
- async onSuccess(name) {
157
- const row = new ActionRowBuilder().addComponents(
158
- new ButtonBuilder().setCustomId(`na:${nameOrId}`).setLabel(`\u{1F680} Start session with ${name}`).setStyle(ButtonStyle.Primary)
159
- );
160
- try {
161
- if (interaction.deferred || interaction.replied) {
162
- await interaction.editReply({
163
- content: `\u2705 **${name}** installed!`,
164
- components: [row]
165
- });
166
- }
167
- } catch {
168
- }
169
- },
170
- async onError(error) {
171
- try {
172
- if (interaction.deferred || interaction.replied) {
173
- await interaction.editReply(`\u274C ${error}`);
174
- }
175
- } catch {
176
- }
177
- }
178
- };
179
- const result = await catalog.install(nameOrId, progress);
180
- if (result.ok && result.setupSteps?.length) {
181
- let setupText = `\u{1F4CB} **Setup for ${result.agentKey}:**
182
-
183
- `;
184
- for (const step of result.setupSteps) {
185
- setupText += `\u2192 ${step}
186
- `;
187
- }
188
- setupText += `
189
- *Run in terminal: \`openacp agents info ${result.agentKey}\`*`;
190
- try {
191
- await interaction.followUp({ content: setupText, ephemeral: true });
192
- } catch {
193
- }
194
- }
195
- }
196
-
197
- export {
198
- handleAgents,
199
- showAgentsList,
200
- handleInstall,
201
- handleAgentButton
202
- };
203
- //# sourceMappingURL=chunk-NAM4ERUW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/plugins/discord/commands/agents.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/utils/log.js'\nimport type { InstallProgress } from '../../../core/types.js'\nimport type { DiscordAdapter } from '../adapter.js'\n\nconst AGENTS_PER_PAGE = 5\n\nfunction buildProgressBar(percent: number): string {\n const filled = Math.round(percent / 10)\n const empty = 10 - filled\n return '█'.repeat(filled) + '░'.repeat(empty)\n}\n\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text\n return text.slice(0, maxLen - 1) + '…'\n}\n\nexport async function handleAgents(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n page = 0,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n const { content, components } = buildAgentsContent(adapter, page)\n await interaction.editReply({ content, components })\n}\n\nexport async function showAgentsList(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n page = 0,\n): Promise<void> {\n const { content, components } = buildAgentsContent(adapter, page)\n await interaction.followUp({ content, components, ephemeral: true })\n}\n\nfunction buildAgentsContent(\n adapter: DiscordAdapter,\n page: number,\n): { content: string; components: ActionRowBuilder<ButtonBuilder>[] } {\n const catalog = adapter.core.agentCatalog\n const items = catalog.getAvailable()\n\n const installed = items.filter((i: any) => i.installed)\n const available = items.filter((i: any) => !i.installed)\n\n let content = '**🤖 Agents**\\n\\n'\n\n if (installed.length > 0) {\n content += '**Installed:**\\n'\n for (const item of installed) {\n content += `✅ **${item.name}**`\n if (item.description) content += ` — *${truncate(item.description, 50)}*`\n content += '\\n'\n }\n content += '\\n'\n }\n\n const components: ActionRowBuilder<ButtonBuilder>[] = []\n\n if (available.length > 0) {\n const totalPages = Math.ceil(available.length / AGENTS_PER_PAGE)\n const safePage = Math.max(0, Math.min(page, totalPages - 1))\n const pageItems = available.slice(safePage * AGENTS_PER_PAGE, (safePage + 1) * AGENTS_PER_PAGE)\n\n content += `**Available to install:**`\n if (totalPages > 1) content += ` (${safePage + 1}/${totalPages})`\n content += '\\n'\n\n for (const item of pageItems) {\n if (item.available) {\n content += `⬇️ **${item.name}**`\n } else {\n const deps = item.missingDeps?.join(', ') ?? 'requirements not met'\n content += `⚠️ **${item.name}** *(needs: ${deps})*`\n }\n if (item.description) content += `\\n *${truncate(item.description, 60)}*`\n content += '\\n'\n }\n\n // Install buttons row\n const installable = pageItems.filter((i: any) => i.available)\n if (installable.length > 0) {\n const installRow = new ActionRowBuilder<ButtonBuilder>()\n for (const item of installable) {\n installRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`ag:install:${item.key}`)\n .setLabel(`⬇️ ${item.name}`)\n .setStyle(ButtonStyle.Secondary),\n )\n }\n components.push(installRow)\n }\n\n // Pagination row\n if (totalPages > 1) {\n const pageRow = new ActionRowBuilder<ButtonBuilder>()\n if (safePage > 0) {\n pageRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`ag:page:${safePage - 1}`)\n .setLabel('◀️ Prev')\n .setStyle(ButtonStyle.Secondary),\n )\n }\n if (safePage < totalPages - 1) {\n pageRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`ag:page:${safePage + 1}`)\n .setLabel('Next ▶️')\n .setStyle(ButtonStyle.Secondary),\n )\n }\n if (pageRow.components.length > 0) components.push(pageRow)\n }\n } else {\n content += '*All agents are already installed!*'\n }\n\n return { content, components }\n}\n\nexport async function handleInstall(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const nameOrId = interaction.options.getString('name', true)\n await installAgentWithProgress(interaction, adapter, nameOrId)\n}\n\nexport async function handleAgentButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { customId } = interaction\n\n if (customId.startsWith('ag:install:')) {\n const nameOrId = customId.replace('ag:install:', '')\n try { await interaction.deferReply({ ephemeral: true }) } catch { /* ignore */ }\n await installAgentWithProgress(interaction, adapter, nameOrId)\n return\n }\n\n if (customId.startsWith('ag:page:')) {\n const page = parseInt(customId.replace('ag:page:', ''), 10)\n const { content, components } = buildAgentsContent(adapter, page)\n try {\n await interaction.update({ content, components })\n } catch (err) {\n log.warn({ err }, '[discord-agents] Failed to update page')\n }\n }\n}\n\nasync function installAgentWithProgress(\n interaction: ChatInputCommandInteraction | ButtonInteraction,\n adapter: DiscordAdapter,\n nameOrId: string,\n): Promise<void> {\n const catalog = adapter.core.agentCatalog\n\n // Track the latest status for periodic edits\n let statusText = `⏳ Installing **${nameOrId}**...`\n let lastEdit = 0\n const EDIT_THROTTLE_MS = 1500\n\n const editStatus = async (text: string) => {\n const now = Date.now()\n if (now - lastEdit > EDIT_THROTTLE_MS) {\n lastEdit = now\n statusText = text\n try {\n if (interaction.deferred || interaction.replied) {\n await interaction.editReply(text)\n }\n } catch { /* rate limit or unchanged */ }\n } else {\n statusText = text\n }\n }\n\n // Set initial message\n try {\n if (interaction.deferred || interaction.replied) {\n await interaction.editReply(statusText)\n }\n } catch { /* ignore */ }\n\n const progress: InstallProgress = {\n onStart(_id, _name) { /* initial message already sent */ },\n async onStep(step) { await editStatus(`⏳ **${nameOrId}**: ${step}`) },\n async onDownloadProgress(percent) {\n const bar = buildProgressBar(percent)\n await editStatus(`⏳ **${nameOrId}**\\nDownloading... ${bar} ${percent}%`)\n },\n async onSuccess(name) {\n const row = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`na:${nameOrId}`)\n .setLabel(`🚀 Start session with ${name}`)\n .setStyle(ButtonStyle.Primary),\n )\n try {\n if (interaction.deferred || interaction.replied) {\n await interaction.editReply({\n content: `✅ **${name}** installed!`,\n components: [row],\n })\n }\n } catch { /* ignore */ }\n },\n async onError(error) {\n try {\n if (interaction.deferred || interaction.replied) {\n await interaction.editReply(`❌ ${error}`)\n }\n } catch { /* ignore */ }\n },\n }\n\n const result = await catalog.install(nameOrId, progress)\n\n // Show setup steps as a follow-up message\n if (result.ok && result.setupSteps?.length) {\n let setupText = `📋 **Setup for ${result.agentKey}:**\\n\\n`\n for (const step of result.setupSteps) {\n setupText += `→ ${step}\\n`\n }\n setupText += `\\n*Run in terminal: \\`openacp agents info ${result.agentKey}\\`*`\n try {\n await interaction.followUp({ content: setupText, ephemeral: true })\n } catch { /* ignore */ }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,122 +0,0 @@
1
- import {
2
- log
3
- } from "./chunk-XMMAGAT4.js";
4
-
5
- // src/plugins/discord/forums.ts
6
- import { ChannelType } from "discord.js";
7
- async function ensureForums(guild, config, saveConfig) {
8
- let forumChannelId = config.forumChannelId;
9
- let notificationChannelId = config.notificationChannelId;
10
- let forumChannel = null;
11
- if (forumChannelId) {
12
- try {
13
- const ch = guild.channels.cache.get(forumChannelId) ?? await guild.channels.fetch(forumChannelId);
14
- if (ch && (ch.type === ChannelType.GuildForum || ch.type === ChannelType.GuildText)) {
15
- forumChannel = ch;
16
- log.info({ forumChannelId, type: ch.type }, "[forums] Reusing existing sessions channel");
17
- }
18
- } catch {
19
- log.warn({ forumChannelId }, "[forums] Saved sessions channel not found, recreating...");
20
- }
21
- }
22
- if (!forumChannel) {
23
- if (guild.features.includes("COMMUNITY")) {
24
- const channel = await guild.channels.create({
25
- name: "openacp-sessions",
26
- type: ChannelType.GuildForum
27
- });
28
- forumChannel = channel;
29
- log.info({ forumChannelId: channel.id }, "[forums] Created forum channel");
30
- } else {
31
- const channel = await guild.channels.create({
32
- name: "openacp-sessions",
33
- type: ChannelType.GuildText
34
- });
35
- forumChannel = channel;
36
- log.info({ forumChannelId: channel.id }, "[forums] Created text channel (Community mode not enabled, using threads fallback)");
37
- }
38
- await saveConfig({ channels: { discord: { forumChannelId: forumChannel.id } } });
39
- }
40
- let notificationChannel = null;
41
- if (notificationChannelId) {
42
- try {
43
- const ch = guild.channels.cache.get(notificationChannelId) ?? await guild.channels.fetch(notificationChannelId);
44
- if (ch && ch.type === ChannelType.GuildText) {
45
- notificationChannel = ch;
46
- log.info({ notificationChannelId }, "[forums] Reusing existing notification channel");
47
- }
48
- } catch {
49
- log.warn({ notificationChannelId }, "[forums] Saved notification channel not found, recreating...");
50
- }
51
- }
52
- if (!notificationChannel) {
53
- const channel = await guild.channels.create({
54
- name: "openacp-notifications",
55
- type: ChannelType.GuildText
56
- });
57
- notificationChannel = channel;
58
- await saveConfig({ channels: { discord: { notificationChannelId: channel.id } } });
59
- log.info({ notificationChannelId: channel.id }, "[forums] Created notification channel");
60
- }
61
- return { forumChannel, notificationChannel };
62
- }
63
- async function createSessionThread(forumChannel, name) {
64
- if (forumChannel.type === ChannelType.GuildForum) {
65
- const thread2 = await forumChannel.threads.create({
66
- name,
67
- message: { content: "\u23F3 Setting up..." }
68
- });
69
- return thread2;
70
- }
71
- const textChannel = forumChannel;
72
- const msg = await textChannel.send({ content: `\u{1F4C2} **${name}** \u2014 \u23F3 Setting up...` });
73
- const thread = await msg.startThread({ name });
74
- return thread;
75
- }
76
- async function renameSessionThread(guild, threadId, newName) {
77
- try {
78
- const channel = guild.channels.cache.get(threadId) ?? await guild.channels.fetch(threadId);
79
- if (channel && "setName" in channel) {
80
- await channel.setName(newName);
81
- }
82
- } catch {
83
- }
84
- }
85
- async function deleteSessionThread(guild, threadId) {
86
- try {
87
- const channel = guild.channels.cache.get(threadId) ?? await guild.channels.fetch(threadId);
88
- if (channel && channel.isThread()) {
89
- const thread = channel;
90
- if (!thread.archived) {
91
- await thread.setArchived(true);
92
- }
93
- if (!thread.locked) {
94
- await thread.setLocked(true);
95
- }
96
- }
97
- } catch {
98
- }
99
- }
100
- async function ensureUnarchived(thread) {
101
- if (thread.archived) {
102
- try {
103
- await thread.setArchived(false);
104
- } catch (err) {
105
- log.warn({ err, threadId: thread.id }, "[forums] Failed to unarchive thread");
106
- }
107
- }
108
- }
109
- function buildDeepLink(guildId, channelId, messageId) {
110
- const base = `https://discord.com/channels/${guildId}/${channelId}`;
111
- return messageId ? `${base}/${messageId}` : base;
112
- }
113
-
114
- export {
115
- ensureForums,
116
- createSessionThread,
117
- renameSessionThread,
118
- deleteSessionThread,
119
- ensureUnarchived,
120
- buildDeepLink
121
- };
122
- //# sourceMappingURL=chunk-O5RG4YZY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/plugins/discord/forums.ts"],"sourcesContent":["import { ChannelType } from 'discord.js'\nimport type { ForumChannel, ThreadChannel, Guild, TextChannel } from 'discord.js'\nimport { log } from '../../core/utils/log.js'\n\n// ─── ensureForums ─────────────────────────────────────────────────────────────\n\n/**\n * Ensures both the forum channel and notification channel exist.\n * Creates them if their IDs are null, then persists the IDs via saveConfig.\n *\n * saveConfig uses nested object path: { channels: { discord: { forumChannelId: ... } } }\n */\nexport async function ensureForums(\n guild: Guild,\n config: {\n forumChannelId: string | null\n notificationChannelId: string | null\n },\n saveConfig: (updates: Record<string, unknown>) => Promise<void>,\n): Promise<{ forumChannel: ForumChannel | TextChannel; notificationChannel: TextChannel }> {\n let forumChannelId = config.forumChannelId\n let notificationChannelId = config.notificationChannelId\n\n // Ensure forum/sessions channel exists — fetch existing or create new\n let forumChannel: ForumChannel | TextChannel | null = null\n if (forumChannelId) {\n try {\n const ch = guild.channels.cache.get(forumChannelId)\n ?? await guild.channels.fetch(forumChannelId)\n if (ch && (ch.type === ChannelType.GuildForum || ch.type === ChannelType.GuildText)) {\n forumChannel = ch as ForumChannel | TextChannel\n log.info({ forumChannelId, type: ch.type }, '[forums] Reusing existing sessions channel')\n }\n } catch {\n log.warn({ forumChannelId }, '[forums] Saved sessions channel not found, recreating...')\n }\n }\n if (!forumChannel) {\n // Prefer Forum Channel (requires Community mode), fallback to Text Channel with threads\n if (guild.features.includes('COMMUNITY')) {\n const channel = await guild.channels.create({\n name: 'openacp-sessions',\n type: ChannelType.GuildForum,\n })\n forumChannel = channel as ForumChannel\n log.info({ forumChannelId: channel.id }, '[forums] Created forum channel')\n } else {\n const channel = await guild.channels.create({\n name: 'openacp-sessions',\n type: ChannelType.GuildText,\n })\n forumChannel = channel as TextChannel\n log.info({ forumChannelId: channel.id }, '[forums] Created text channel (Community mode not enabled, using threads fallback)')\n }\n await saveConfig({ channels: { discord: { forumChannelId: forumChannel.id } } })\n }\n\n // Ensure notification channel exists — fetch existing or create new\n let notificationChannel: TextChannel | null = null\n if (notificationChannelId) {\n try {\n const ch = guild.channels.cache.get(notificationChannelId)\n ?? await guild.channels.fetch(notificationChannelId)\n if (ch && ch.type === ChannelType.GuildText) {\n notificationChannel = ch as TextChannel\n log.info({ notificationChannelId }, '[forums] Reusing existing notification channel')\n }\n } catch {\n log.warn({ notificationChannelId }, '[forums] Saved notification channel not found, recreating...')\n }\n }\n if (!notificationChannel) {\n const channel = await guild.channels.create({\n name: 'openacp-notifications',\n type: ChannelType.GuildText,\n })\n notificationChannel = channel as TextChannel\n await saveConfig({ channels: { discord: { notificationChannelId: channel.id } } })\n log.info({ notificationChannelId: channel.id }, '[forums] Created notification channel')\n }\n\n return { forumChannel, notificationChannel }\n}\n\n// ─── createSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Creates a new thread for a session.\n * - Forum Channel: creates a forum post (thread with initial message)\n * - Text Channel: creates a public thread\n */\nexport async function createSessionThread(\n forumChannel: ForumChannel | TextChannel,\n name: string,\n): Promise<ThreadChannel> {\n if (forumChannel.type === ChannelType.GuildForum) {\n // Forum channel: create a post (thread with initial message)\n const thread = await (forumChannel as ForumChannel).threads.create({\n name,\n message: { content: '⏳ Setting up...' },\n })\n return thread\n }\n\n // Text channel fallback: send a message first, then create a thread on it\n const textChannel = forumChannel as TextChannel\n const msg = await textChannel.send({ content: `📂 **${name}** — ⏳ Setting up...` })\n const thread = await msg.startThread({ name })\n return thread\n}\n\n// ─── renameSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Fetches and renames a thread. Ignores all errors (thread may be deleted/archived).\n */\nexport async function renameSessionThread(\n guild: Guild,\n threadId: string,\n newName: string,\n): Promise<void> {\n try {\n const channel = guild.channels.cache.get(threadId)\n ?? await guild.channels.fetch(threadId)\n if (channel && 'setName' in channel) {\n await (channel as ThreadChannel).setName(newName)\n }\n } catch {\n // Ignore — thread may be deleted or archived\n }\n}\n\n// ─── deleteSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Archives and locks a thread instead of permanently deleting it.\n * Unlike Telegram (which just closes a topic), Discord delete is permanent\n * and destroys all messages. Archiving preserves the conversation history.\n */\nexport async function deleteSessionThread(\n guild: Guild,\n threadId: string,\n): Promise<void> {\n try {\n const channel = guild.channels.cache.get(threadId)\n ?? await guild.channels.fetch(threadId)\n if (channel && channel.isThread()) {\n const thread = channel as ThreadChannel\n if (!thread.archived) {\n await thread.setArchived(true)\n }\n if (!thread.locked) {\n await thread.setLocked(true)\n }\n }\n } catch {\n // Ignore — thread may already be deleted or inaccessible\n }\n}\n\n// ─── ensureUnarchived ─────────────────────────────────────────────────────────\n\n/**\n * If the thread is archived, unarchives it.\n */\nexport async function ensureUnarchived(thread: ThreadChannel): Promise<void> {\n if (thread.archived) {\n try {\n await thread.setArchived(false)\n } catch (err) {\n log.warn({ err, threadId: thread.id }, '[forums] Failed to unarchive thread')\n }\n }\n}\n\n// ─── buildDeepLink ────────────────────────────────────────────────────────────\n\n/**\n * Builds a Discord deep link URL to a channel/thread, optionally to a specific message.\n */\nexport function buildDeepLink(\n guildId: string,\n channelId: string,\n messageId?: string,\n): string {\n const base = `https://discord.com/channels/${guildId}/${channelId}`\n return messageId ? `${base}/${messageId}` : base\n}\n"],"mappings":"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","names":["thread"]}