cli-jaw 2.2.9 → 2.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bin/commands/doctor.js +66 -1
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +107 -8
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/lib/upload.js +5 -1
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/src/agent/alert-escalation.js +1 -1
- package/dist/src/agent/alert-escalation.js.map +1 -1
- package/dist/src/agent/codex-app-catalog.js +57 -0
- package/dist/src/agent/codex-app-catalog.js.map +1 -0
- package/dist/src/agent/codex-app-client.js +89 -7
- package/dist/src/agent/codex-app-client.js.map +1 -1
- package/dist/src/agent/pi-rpc-verdict.js +22 -0
- package/dist/src/agent/pi-rpc-verdict.js.map +1 -0
- package/dist/src/agent/pi-runtime.js +240 -0
- package/dist/src/agent/pi-runtime.js.map +1 -1
- package/dist/src/agent/runtime-pool.js +445 -0
- package/dist/src/agent/runtime-pool.js.map +1 -0
- package/dist/src/agent/spawn.js +395 -240
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/command-context.js +5 -2
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/commands.js +43 -39
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +15 -7
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers-search.js +1 -1
- package/dist/src/cli/handlers-search.js.map +1 -1
- package/dist/src/cli/handlers-skill-invoke.js +1 -1
- package/dist/src/cli/handlers-skill-invoke.js.map +1 -1
- package/dist/src/cli/handlers-workflows.js +1 -1
- package/dist/src/cli/handlers-workflows.js.map +1 -1
- package/dist/src/cli/handlers.js +1 -1
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/opencodex-models.js +40 -7
- package/dist/src/cli/opencodex-models.js.map +1 -1
- package/dist/src/cli/registry-live.js +6 -5
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/command-contract/catalog.js +4 -0
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/config.js +69 -4
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/runtime-settings.js +17 -0
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/settings-merge.js +1 -1
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/discord/bot.js +29 -14
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +5 -4
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/discord-file.js +8 -2
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/forwarder.js +17 -19
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/discord/send-only-client.js +9 -1
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/manager/dashboard-service.js +38 -1
- package/dist/src/manager/dashboard-service.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +47 -29
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/messaging/channel-health.js +32 -0
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/chunk.js +376 -0
- package/dist/src/messaging/chunk.js.map +1 -0
- package/dist/src/messaging/dedupe.js +104 -0
- package/dist/src/messaging/dedupe.js.map +1 -0
- package/dist/src/messaging/fold.js +210 -0
- package/dist/src/messaging/fold.js.map +1 -0
- package/dist/src/messaging/redact.js +577 -0
- package/dist/src/messaging/redact.js.map +1 -0
- package/dist/src/messaging/retry.js +76 -0
- package/dist/src/messaging/retry.js.map +1 -0
- package/dist/src/messaging/runtime.js +6 -3
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +45 -15
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/slack-target.js +50 -0
- package/dist/src/messaging/slack-target.js.map +1 -0
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +4 -4
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/routes/command.js +11 -7
- package/dist/src/routes/command.js.map +1 -1
- package/dist/src/routes/messaging.js +28 -12
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/settings.js +22 -6
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/slack/api.js +116 -0
- package/dist/src/slack/api.js.map +1 -0
- package/dist/src/slack/bot.js +262 -0
- package/dist/src/slack/bot.js.map +1 -0
- package/dist/src/slack/commands.js +98 -0
- package/dist/src/slack/commands.js.map +1 -0
- package/dist/src/slack/events.js +116 -0
- package/dist/src/slack/events.js.map +1 -0
- package/dist/src/slack/format.js +58 -0
- package/dist/src/slack/format.js.map +1 -0
- package/dist/src/slack/forwarder.js +57 -0
- package/dist/src/slack/forwarder.js.map +1 -0
- package/dist/src/slack/register.js +13 -0
- package/dist/src/slack/register.js.map +1 -0
- package/dist/src/slack/send-handler.js +41 -0
- package/dist/src/slack/send-handler.js.map +1 -0
- package/dist/src/slack/send-only-client.js +46 -0
- package/dist/src/slack/send-only-client.js.map +1 -0
- package/dist/src/slack/slack-file.js +72 -0
- package/dist/src/slack/slack-file.js.map +1 -0
- package/dist/src/slack/socket.js +341 -0
- package/dist/src/slack/socket.js.map +1 -0
- package/dist/src/telegram/bot.js +117 -37
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/elicitation-buttons.js +7 -2
- package/dist/src/telegram/elicitation-buttons.js.map +1 -1
- package/dist/src/telegram/forwarder.js +15 -7
- package/dist/src/telegram/forwarder.js.map +1 -1
- package/dist/src/telegram/rich-message.js +209 -24
- package/dist/src/telegram/rich-message.js.map +1 -1
- package/dist/src/telegram/telegram-file.js +35 -4
- package/dist/src/telegram/telegram-file.js.map +1 -1
- package/dist/src/telegram/voice.js +4 -3
- package/dist/src/telegram/voice.js.map +1 -1
- package/package.json +5 -1
- package/public/assets/providers/slack.svg +1 -0
- package/public/dist/assets/ChannelsDiscord-D9FLnZso.js +1 -0
- package/public/dist/assets/ChannelsSlack-ybvupgdK.js +1 -0
- package/public/dist/assets/ChannelsTelegram-BOCOPrBc.js +1 -0
- package/public/dist/assets/HealthBadge-megTlF4n.js +1 -0
- package/public/dist/assets/TransportStatusChips-CjEySMis.js +1 -0
- package/public/dist/assets/app-BGIAy4he.js +55 -0
- package/public/dist/assets/{bgtask-badge-CdBqBy1L.js → bgtask-badge-DXca8E7c.js} +1 -1
- package/public/dist/assets/bgtask-badge-GRSiNILQ.js +1 -0
- package/public/dist/assets/{employees-C8qufeJl.js → employees-BL4qMxJj.js} +1 -1
- package/public/dist/assets/iframe-renderer-BUSk0Rw9.js +1 -0
- package/public/dist/assets/{iframe-renderer-CZ67aeTO.js → iframe-renderer-DUI0LYBy.js} +2 -2
- package/public/dist/assets/{manager-CLXT60M5.js → manager-B-gpf-3j.js} +3 -3
- package/public/dist/assets/{memory-lWramLVw.js → memory-B_Y0L0KD.js} +1 -1
- package/public/dist/assets/memory-CX6YahTc.js +1 -0
- package/public/dist/assets/message-history-Det7u76S.js +1 -0
- package/public/dist/assets/{message-history-2NUN3UxM.js → message-history-Dl8dJzez.js} +1 -1
- package/public/dist/assets/{provider-icons-stsNmQKx.js → provider-icons-VDfahiUp.js} +3 -2
- package/public/dist/assets/{render-CsF-ojLe.js → render-BI1aqKoc.js} +1 -1
- package/public/dist/assets/{settings-BMLowRCh.js → settings-B0Sj4sUH.js} +1 -1
- package/public/dist/assets/settings-C7NutP6X.js +1 -0
- package/public/dist/assets/settings-core-CyX-px6_.js +100 -0
- package/public/dist/assets/settings-core-DVpFmrhY.js +1 -0
- package/public/dist/assets/{sidebar-gKjYLv49.js → sidebar-DOR9fTgN.js} +3 -3
- package/public/dist/assets/skills-BWhKreQ2.js +1 -0
- package/public/dist/assets/{skills-CKTbomdm.js → skills-wt0fkV9Y.js} +1 -1
- package/public/dist/assets/slash-commands-BZbqTg3v.js +20 -0
- package/public/dist/assets/slash-commands-Di4NDA4w.js +1 -0
- package/public/dist/assets/{trace-drawer-C3G4mmB1.js → trace-drawer-kMYzaVFW.js} +1 -1
- package/public/dist/assets/ui-BghexWrB.js +1 -0
- package/public/dist/assets/{ui-CVVC_flX.js → ui-D4D5HUfo.js} +1 -1
- package/public/dist/index.html +62 -1
- package/public/dist/manager/index.html +1 -1
- package/public/index.html +61 -0
- package/public/js/features/command-info.ts +1 -0
- package/public/js/features/help-content.ts +8 -0
- package/public/js/features/settings-channel.ts +5 -1
- package/public/js/features/settings-core.ts +2 -0
- package/public/js/features/settings-slack.ts +74 -0
- package/public/js/features/settings-types.ts +5 -1
- package/public/js/features/settings.ts +1 -0
- package/public/js/features/transport-status-row.ts +15 -3
- package/public/js/main.ts +19 -0
- package/public/js/provider-icons.ts +4 -1
- package/public/js/ws.ts +1 -1
- package/public/locales/en.json +21 -0
- package/public/locales/ja.json +21 -0
- package/public/locales/ko.json +21 -0
- package/public/locales/zh.json +21 -0
- package/public/manager/src/settings/SettingsShell.tsx +1 -0
- package/public/manager/src/settings/SettingsSidebar.tsx +1 -0
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +323 -0
- package/public/manager/src/settings/pages/components/ActiveChannelToggle.tsx +2 -1
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +13 -4
- package/public/manager/src/settings/types.ts +1 -0
- package/scripts/check-redaction-sinks.mjs +276 -0
- package/scripts/pi-rpc-probe.mts +361 -0
- package/scripts/release-gates.mjs +83 -0
- package/scripts/release-preview.sh +8 -1
- package/scripts/release.sh +13 -2
- package/scripts/sync-electron-version.cjs +93 -0
- package/public/dist/assets/ChannelsDiscord-Bt1_rtNC.js +0 -1
- package/public/dist/assets/ChannelsTelegram-BLV0m1oq.js +0 -1
- package/public/dist/assets/TransportStatusChips-Bld0yXi7.js +0 -1
- package/public/dist/assets/app-iSJhsP20.js +0 -55
- package/public/dist/assets/bgtask-badge-CSkSsoB6.js +0 -1
- package/public/dist/assets/iframe-renderer-DXdo0YXN.js +0 -1
- package/public/dist/assets/memory-DeKUlD2M.js +0 -1
- package/public/dist/assets/message-history-CWTQ2woA.js +0 -1
- package/public/dist/assets/settings-CDjs7hTT.js +0 -1
- package/public/dist/assets/settings-core-Cs_oq-b3.js +0 -99
- package/public/dist/assets/settings-core-D-TOh1a6.js +0 -1
- package/public/dist/assets/skills-GuIfNASs.js +0 -1
- package/public/dist/assets/slash-commands-BtZa093n.js +0 -20
- package/public/dist/assets/slash-commands-DGJKfpMT.js +0 -1
- package/public/dist/assets/ui-DJg5Q_NJ.js +0 -1
- /package/public/dist/assets/{DashboardMeta-B7CVqXcU.js → DashboardMeta-DennaaA_.js} +0 -0
- /package/public/dist/assets/{Display-BF-rACK6.js → Display-hGuSn_W5.js} +0 -0
- /package/public/dist/assets/{Employees-D3hnciP0.js → Employees-B5YXk5ma.js} +0 -0
- /package/public/dist/assets/{Heartbeat-D4Pn5O3O.js → Heartbeat-vDGhPfct.js} +0 -0
- /package/public/dist/assets/{Mcp-SfytKWGX.js → Mcp-BHWnJ6SP.js} +0 -0
- /package/public/dist/assets/{Memory-DNxBOQ7N.js → Memory-B_UZ-iCA.js} +0 -0
- /package/public/dist/assets/{ModelProvider-CXwtlG7l.js → ModelProvider-CgUvs6pE.js} +0 -0
- /package/public/dist/assets/{Network-DISUhige.js → Network-AWpSkALD.js} +0 -0
- /package/public/dist/assets/{Profile-B8yBO3Ju.js → Profile-DnIE2ios.js} +0 -0
- /package/public/dist/assets/{Prompts-5lNQ5qOm.js → Prompts-B9aKLall.js} +0 -0
- /package/public/dist/assets/{SpeechKeys-BepaIHBa.js → SpeechKeys-K4Be_o7z.js} +0 -0
- /package/public/dist/assets/{TelegramHub--DNjRAwW.js → TelegramHub-CFwSkzHP.js} +0 -0
- /package/public/dist/assets/{TerminalPanel-DQovYfwz.js → TerminalPanel-CdlFZHAK.js} +0 -0
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
export interface PerCliConfig { provider?: string; model?: string; effort?: string; fastMode?: boolean; contextWindow?: boolean; contextWindowSize?: number; contextCompactLimit?: number; }
|
|
4
4
|
export interface TelegramConfig { enabled?: boolean; token?: string; allowedChatIds?: number[]; forwardAll?: boolean; mentionOnly?: boolean; }
|
|
5
5
|
export interface DiscordConfig { enabled?: boolean; token?: string; guildId?: string; channelIds?: string[]; forwardAll?: boolean; allowBots?: boolean; mentionOnly?: boolean; }
|
|
6
|
+
// Slack needs TWO distinctly-scoped tokens: the bot token drives the Web API,
|
|
7
|
+
// the app-level token opens the Socket Mode connection for inbound events.
|
|
8
|
+
export interface SlackConfig { enabled?: boolean; botToken?: string; appToken?: string; teamId?: string; channelIds?: string[]; forwardAll?: boolean; allowBots?: boolean; mentionOnly?: boolean; replyInThread?: boolean; }
|
|
6
9
|
export interface QuotaWindow {
|
|
7
10
|
label: string;
|
|
8
11
|
percent: number;
|
|
@@ -49,7 +52,8 @@ export interface SettingsData {
|
|
|
49
52
|
activeOverrides?: Record<string, PerCliConfig>;
|
|
50
53
|
telegram?: TelegramConfig;
|
|
51
54
|
discord?: DiscordConfig;
|
|
52
|
-
|
|
55
|
+
slack?: SlackConfig;
|
|
56
|
+
channel?: 'telegram' | 'discord' | 'slack';
|
|
53
57
|
fallbackOrder?: string[];
|
|
54
58
|
memory?: { cli?: string };
|
|
55
59
|
projectDirs?: string[] | null;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { loadSettings, updateSettings, setPerm, getModelValue, handleModelSelect, applyCustomModel, onPerCliProviderChange, onCliChange, saveActiveCliSettings, savePerCli, onFlushCliChange, loadFlushAgentSidebar } from './settings-core.js';
|
|
3
3
|
export { setTelegram, setForwardAll, setTelegramMentionOnly, saveTelegramSettings } from './settings-telegram.js';
|
|
4
4
|
export { setDiscord, setDiscordForwardAll, setDiscordAllowBots, setDiscordMentionOnly, saveDiscordSettings } from './settings-discord.js';
|
|
5
|
+
export { setSlack, setSlackForwardAll, setSlackAllowBots, setSlackMentionOnly, setSlackReplyInThread, saveSlackSettings } from './settings-slack.js';
|
|
5
6
|
export { setActiveChannel, loadFallbackOrder, saveFallbackOrder } from './settings-channel.js';
|
|
6
7
|
export { loadMcpServers, syncMcpServers, installMcpGlobal, openMcpModal, initMcpModal } from './settings-mcp.js';
|
|
7
8
|
export { loadCliStatus, scheduleCliStatusRefresh, setCliStatusInterval } from './settings-cli-status.js';
|
|
@@ -10,9 +10,10 @@ export type TransportStatus = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export type ChannelHealth = {
|
|
13
|
-
activeInbound: 'telegram' | 'discord';
|
|
13
|
+
activeInbound: 'telegram' | 'discord' | 'slack';
|
|
14
14
|
telegram: TransportStatus;
|
|
15
15
|
discord: TransportStatus;
|
|
16
|
+
slack: TransportStatus;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
function isTransportStatus(value: unknown): value is TransportStatus {
|
|
@@ -28,12 +29,22 @@ export function parseChannelHealth(payload: unknown): ChannelHealth | null {
|
|
|
28
29
|
const channels = (payload as { channels?: unknown }).channels;
|
|
29
30
|
if (!channels || typeof channels !== 'object') return null;
|
|
30
31
|
const row = channels as Record<string, unknown>;
|
|
31
|
-
|
|
32
|
+
// Extract to a local so TypeScript narrows it for the return below; a
|
|
33
|
+
// repeated property read is not narrowed.
|
|
34
|
+
const active = row['activeInbound'];
|
|
35
|
+
if (active !== 'telegram' && active !== 'discord' && active !== 'slack') return null;
|
|
32
36
|
if (!isTransportStatus(row['telegram']) || !isTransportStatus(row['discord'])) return null;
|
|
37
|
+
// Slack is tolerated as absent: a newer bundle can be served against an
|
|
38
|
+
// older running server during a rolling update, and rejecting the whole
|
|
39
|
+
// payload would hide Telegram and Discord health too.
|
|
40
|
+
const slack = isTransportStatus(row['slack'])
|
|
41
|
+
? row['slack']
|
|
42
|
+
: { configured: false, activeInbound: false, sendCapable: false, reason: 'unavailable' };
|
|
33
43
|
return {
|
|
34
|
-
activeInbound:
|
|
44
|
+
activeInbound: active,
|
|
35
45
|
telegram: row['telegram'],
|
|
36
46
|
discord: row['discord'],
|
|
47
|
+
slack,
|
|
37
48
|
};
|
|
38
49
|
}
|
|
39
50
|
|
|
@@ -71,6 +82,7 @@ export function renderTransportStatusRow(container: HTMLElement, health: Channel
|
|
|
71
82
|
</h4>
|
|
72
83
|
${renderTransportBlock('Telegram', health.telegram, true)}
|
|
73
84
|
${renderTransportBlock('Discord', health.discord, true)}
|
|
85
|
+
${renderTransportBlock('Slack', health.slack, true)}
|
|
74
86
|
`;
|
|
75
87
|
}
|
|
76
88
|
|
package/public/js/main.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
initCliStatusToggle, initCliStatusPreviewHooks, isCliStatusExpanded, expandCliStatus, isEmbeddedPreviewFrame,
|
|
57
57
|
setTelegram, setForwardAll, setTelegramMentionOnly, saveTelegramSettings,
|
|
58
58
|
setDiscord, setDiscordForwardAll, setDiscordAllowBots, setDiscordMentionOnly, saveDiscordSettings, setActiveChannel,
|
|
59
|
+
setSlack, setSlackForwardAll, setSlackAllowBots, setSlackMentionOnly, setSlackReplyInThread, saveSlackSettings,
|
|
59
60
|
saveFallbackOrder,
|
|
60
61
|
openTemplateModal, saveTemplateFromModal, closeTemplateModal, templateGoBack, toggleDevMode
|
|
61
62
|
} from './features/settings.js';
|
|
@@ -282,6 +283,24 @@ document.getElementById('dcMentionOn')?.addEventListener('click', () => setDisco
|
|
|
282
283
|
document.getElementById('dcToken')?.addEventListener('change', saveDiscordSettings);
|
|
283
284
|
document.getElementById('dcGuildId')?.addEventListener('change', saveDiscordSettings);
|
|
284
285
|
document.getElementById('dcChannelIds')?.addEventListener('change', saveDiscordSettings);
|
|
286
|
+
|
|
287
|
+
// Slack
|
|
288
|
+
document.getElementById('chSlack')?.addEventListener('click', () => setActiveChannel('slack'));
|
|
289
|
+
document.getElementById('slOff')?.addEventListener('click', () => setSlack(false));
|
|
290
|
+
document.getElementById('slOn')?.addEventListener('click', () => setSlack(true));
|
|
291
|
+
document.getElementById('slForwardOff')?.addEventListener('click', () => setSlackForwardAll(false));
|
|
292
|
+
document.getElementById('slForwardOn')?.addEventListener('click', () => setSlackForwardAll(true));
|
|
293
|
+
document.getElementById('slAllowBotsOff')?.addEventListener('click', () => setSlackAllowBots(false));
|
|
294
|
+
document.getElementById('slAllowBotsOn')?.addEventListener('click', () => setSlackAllowBots(true));
|
|
295
|
+
document.getElementById('slMentionOff')?.addEventListener('click', () => setSlackMentionOnly(false));
|
|
296
|
+
document.getElementById('slMentionOn')?.addEventListener('click', () => setSlackMentionOnly(true));
|
|
297
|
+
document.getElementById('slThreadOff')?.addEventListener('click', () => setSlackReplyInThread(false));
|
|
298
|
+
document.getElementById('slThreadOn')?.addEventListener('click', () => setSlackReplyInThread(true));
|
|
299
|
+
document.getElementById('slBotToken')?.addEventListener('change', saveSlackSettings);
|
|
300
|
+
document.getElementById('slAppToken')?.addEventListener('change', saveSlackSettings);
|
|
301
|
+
document.getElementById('slTeamId')?.addEventListener('change', saveSlackSettings);
|
|
302
|
+
document.getElementById('slChannelIds')?.addEventListener('change', saveSlackSettings);
|
|
303
|
+
|
|
285
304
|
document.getElementById('fallbackOrderList')?.addEventListener('change', saveFallbackOrder);
|
|
286
305
|
|
|
287
306
|
// Codex fast mode toggle
|
|
@@ -22,10 +22,11 @@ import kiroMonoSvg from '../assets/providers/kiro.svg?raw';
|
|
|
22
22
|
|
|
23
23
|
// Service icons (Discord, Telegram)
|
|
24
24
|
import discordSvg from '../assets/providers/discord.svg?raw';
|
|
25
|
+
import slackSvg from '../assets/providers/slack.svg?raw';
|
|
25
26
|
import telegramSvg from '../assets/providers/telegram.svg?raw';
|
|
26
27
|
import opencodeSvg from '../assets/providers/opencode.svg?raw';
|
|
27
28
|
|
|
28
|
-
export type ProviderSlug = 'ai-e' | 'claude' | 'openai' | 'gemini' | 'antigravity' | 'grok' | 'copilot' | 'cursor' | 'kiro-code' | 'codex' | 'codex-app' | 'opencode' | 'discord' | 'telegram';
|
|
29
|
+
export type ProviderSlug = 'ai-e' | 'claude' | 'openai' | 'gemini' | 'antigravity' | 'grok' | 'copilot' | 'cursor' | 'kiro-code' | 'codex' | 'codex-app' | 'opencode' | 'discord' | 'telegram' | 'slack';
|
|
29
30
|
|
|
30
31
|
interface ProviderIcon {
|
|
31
32
|
color: string;
|
|
@@ -50,6 +51,7 @@ const PROVIDER_ICONS: Record<ProviderSlug, ProviderIcon> = {
|
|
|
50
51
|
opencode: { color: opencodeSvg, mono: opencodeSvg, label: 'OpenCode' },
|
|
51
52
|
discord: { color: discordSvg, mono: discordSvg, label: 'Discord' },
|
|
52
53
|
telegram: { color: telegramSvg, mono: telegramSvg, label: 'Telegram' },
|
|
54
|
+
slack: { color: slackSvg, mono: slackSvg, label: 'Slack' },
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
const PROVIDER_LABEL_ALIASES: Record<string, string> = {
|
|
@@ -79,6 +81,7 @@ function resolveProviderSlug(slug: string): ProviderSlug | null {
|
|
|
79
81
|
if (normalized === 'openai' || normalized.startsWith('gpt') || normalized.startsWith('o1') || normalized.startsWith('o3') || normalized.startsWith('o4')) return 'openai';
|
|
80
82
|
if (normalized === 'discord') return 'discord';
|
|
81
83
|
if (normalized === 'telegram') return 'telegram';
|
|
84
|
+
if (normalized === 'slack') return 'slack';
|
|
82
85
|
return null;
|
|
83
86
|
}
|
|
84
87
|
|
package/public/js/ws.ts
CHANGED
|
@@ -1088,7 +1088,7 @@ function handleServerEvent(msg: WsMessage): void {
|
|
|
1088
1088
|
markRunFinalized(null); // killed run — drop its replayed stream too
|
|
1089
1089
|
finalizeAgent('');
|
|
1090
1090
|
setStatus('steering');
|
|
1091
|
-
} else if (msg.type === 'new_message' && (msg.source === 'telegram' || msg.source === 'discord' || msg.source === 'bgtask' || msg.source === 'cli' || msg.source === 'goal' || msg.external === true || msg.fromQueue === true)) {
|
|
1091
|
+
} else if (msg.type === 'new_message' && (msg.source === 'telegram' || msg.source === 'discord' || msg.source === 'slack' || msg.source === 'bgtask' || msg.source === 'cli' || msg.source === 'goal' || msg.external === true || msg.fromQueue === true)) {
|
|
1092
1092
|
const newMessageRole = msg.role === 'assistant' ? 'agent' : (msg.role || 'user');
|
|
1093
1093
|
const newMessageContent = msg.content || '';
|
|
1094
1094
|
const newMessageCli = msg.cli;
|
package/public/locales/en.json
CHANGED
|
@@ -504,6 +504,27 @@
|
|
|
504
504
|
"help.discord.avoid.1": "You expect direct-message delivery",
|
|
505
505
|
"help.discord.related.1": "jaw doctor",
|
|
506
506
|
"help.discord.related.2": "Discord degraded mode",
|
|
507
|
+
"help.slack.title": "Slack",
|
|
508
|
+
"help.slack.aria": "Slack help",
|
|
509
|
+
"help.slack.intro": "Attach a bot to your Slack workspace and drive agents from channels, DMs, and threads.",
|
|
510
|
+
"help.slack.effect": "Mentions, DMs, and slash commands in allowed conversations reach the agent, and replies come back in-thread.",
|
|
511
|
+
"help.slack.use.1": "Calling the agent by mention in a team channel",
|
|
512
|
+
"help.slack.use.2": "Running private work over DM",
|
|
513
|
+
"help.slack.avoid.1": "When you only have one token — a bot token alone cannot receive events",
|
|
514
|
+
"help.slack.related.1": "Telegram settings",
|
|
515
|
+
"help.slack.related.2": "Discord settings",
|
|
516
|
+
"help.slack.link.portal": "Open the Slack app dashboard",
|
|
517
|
+
"help.slack.link.docs": "Socket Mode documentation",
|
|
518
|
+
"help.slack.example.1": "@cli-jaw run the tests for this repo",
|
|
519
|
+
"help.slack.howTo.1": "Create an app at api.slack.com/apps (a manifest is easiest).",
|
|
520
|
+
"help.slack.howTo.2": "Enable Socket Mode and generate an app-level token (xapp-) with connections:write.",
|
|
521
|
+
"help.slack.howTo.3": "Install to the workspace to obtain the bot token (xoxb-).",
|
|
522
|
+
"help.slack.howTo.4": "Paste each token into its own field below — do not swap them.",
|
|
523
|
+
"help.slack.howTo.5": "List the conversation IDs the bot may act in. Empty means all are allowed.",
|
|
524
|
+
"help.slack.howTo.6": "Invite the bot in each channel with /invite @yourbot.",
|
|
525
|
+
"help.slack.howTo.7": "With Mention Only on, the bot answers channels only when mentioned. DMs always work.",
|
|
526
|
+
"help.slack.howTo.8": "With Reply in Thread on, answers land in the originating message thread.",
|
|
527
|
+
"help.slack.howTo.9": "Run cli-jaw doctor to verify the tokens and connection state.",
|
|
507
528
|
"help.fallbackOrder.title": "Fallback",
|
|
508
529
|
"help.fallbackOrder.aria": "Fallback help",
|
|
509
530
|
"help.fallbackOrder.intro": "Sets which CLI is tried when the current one fails or is unavailable.",
|
package/public/locales/ja.json
CHANGED
|
@@ -493,6 +493,27 @@
|
|
|
493
493
|
"help.discord.avoid.1": "DM での配信を期待している場合",
|
|
494
494
|
"help.discord.related.1": "jaw doctor コマンド",
|
|
495
495
|
"help.discord.related.2": "Discord degraded mode の説明",
|
|
496
|
+
"help.slack.title": "Slack",
|
|
497
|
+
"help.slack.aria": "Slack ヘルプ",
|
|
498
|
+
"help.slack.intro": "Attach a bot to your Slack workspace and drive agents from channels, DMs, and threads.",
|
|
499
|
+
"help.slack.effect": "Mentions, DMs, and slash commands in allowed conversations reach the agent, and replies come back in-thread.",
|
|
500
|
+
"help.slack.use.1": "Calling the agent by mention in a team channel",
|
|
501
|
+
"help.slack.use.2": "Running private work over DM",
|
|
502
|
+
"help.slack.avoid.1": "When you only have one token — a bot token alone cannot receive events",
|
|
503
|
+
"help.slack.related.1": "Telegram settings",
|
|
504
|
+
"help.slack.related.2": "Discord settings",
|
|
505
|
+
"help.slack.link.portal": "Open the Slack app dashboard",
|
|
506
|
+
"help.slack.link.docs": "Socket Mode documentation",
|
|
507
|
+
"help.slack.example.1": "@cli-jaw run the tests for this repo",
|
|
508
|
+
"help.slack.howTo.1": "Create an app at api.slack.com/apps (a manifest is easiest).",
|
|
509
|
+
"help.slack.howTo.2": "Enable Socket Mode and generate an app-level token (xapp-) with connections:write.",
|
|
510
|
+
"help.slack.howTo.3": "Install to the workspace to obtain the bot token (xoxb-).",
|
|
511
|
+
"help.slack.howTo.4": "Paste each token into its own field below — do not swap them.",
|
|
512
|
+
"help.slack.howTo.5": "List the conversation IDs the bot may act in. Empty means all are allowed.",
|
|
513
|
+
"help.slack.howTo.6": "Invite the bot in each channel with /invite @yourbot.",
|
|
514
|
+
"help.slack.howTo.7": "With Mention Only on, the bot answers channels only when mentioned. DMs always work.",
|
|
515
|
+
"help.slack.howTo.8": "With Reply in Thread on, answers land in the originating message thread.",
|
|
516
|
+
"help.slack.howTo.9": "Run cli-jaw doctor to verify the tokens and connection state.",
|
|
496
517
|
"help.fallbackOrder.title": "Fallback 設定",
|
|
497
518
|
"help.fallbackOrder.aria": "Fallback ヘルプ",
|
|
498
519
|
"help.fallbackOrder.intro": "現在の CLI が失敗または利用不可になったとき、どの CLI を試すかの順序を設定します。",
|
package/public/locales/ko.json
CHANGED
|
@@ -504,6 +504,27 @@
|
|
|
504
504
|
"help.discord.avoid.1": "DM 전달을 기대하는 경우",
|
|
505
505
|
"help.discord.related.1": "jaw doctor",
|
|
506
506
|
"help.discord.related.2": "Discord degraded mode",
|
|
507
|
+
"help.slack.title": "Slack",
|
|
508
|
+
"help.slack.aria": "Slack 도움말",
|
|
509
|
+
"help.slack.intro": "Slack 워크스페이스에 봇을 붙여 채널·DM·스레드에서 에이전트를 실행합니다.",
|
|
510
|
+
"help.slack.effect": "허용된 대화에서 멘션·DM·슬래시 커맨드가 에이전트로 전달되고, 답변은 스레드로 돌아옵니다.",
|
|
511
|
+
"help.slack.use.1": "팀 채널에서 멘션으로 에이전트를 부를 때",
|
|
512
|
+
"help.slack.use.2": "DM으로 개인 작업을 돌릴 때",
|
|
513
|
+
"help.slack.avoid.1": "토큰이 하나만 있을 때 — 봇 토큰만으로는 수신이 안 됩니다",
|
|
514
|
+
"help.slack.related.1": "Telegram 설정",
|
|
515
|
+
"help.slack.related.2": "Discord 설정",
|
|
516
|
+
"help.slack.link.portal": "Slack 앱 관리 페이지 열기",
|
|
517
|
+
"help.slack.link.docs": "Socket Mode 문서",
|
|
518
|
+
"help.slack.example.1": "@cli-jaw 이 저장소 테스트 돌려줘",
|
|
519
|
+
"help.slack.howTo.1": "api.slack.com/apps 에서 앱을 만듭니다 (매니페스트 사용 권장).",
|
|
520
|
+
"help.slack.howTo.2": "Socket Mode를 켜고 connections:write 범위의 app-level 토큰(xapp-)을 발급합니다.",
|
|
521
|
+
"help.slack.howTo.3": "워크스페이스에 설치해 봇 토큰(xoxb-)을 받습니다.",
|
|
522
|
+
"help.slack.howTo.4": "두 토큰을 아래 입력란에 각각 붙여넣습니다 — 서로 바꿔 넣지 않도록 주의하세요.",
|
|
523
|
+
"help.slack.howTo.5": "Channel IDs에 봇이 활동할 대화 ID를 넣습니다. 비우면 전체 허용입니다.",
|
|
524
|
+
"help.slack.howTo.6": "각 채널에서 /invite @봇이름 으로 봇을 초대합니다.",
|
|
525
|
+
"help.slack.howTo.7": "Mention Only가 켜져 있으면 채널에서는 멘션할 때만 응답합니다. DM은 항상 응답합니다.",
|
|
526
|
+
"help.slack.howTo.8": "Reply in Thread가 켜져 있으면 답변이 원 메시지 스레드로 들어갑니다.",
|
|
527
|
+
"help.slack.howTo.9": "cli-jaw doctor 로 토큰과 연결 상태를 확인합니다.",
|
|
507
528
|
"help.fallbackOrder.title": "Fallback",
|
|
508
529
|
"help.fallbackOrder.aria": "Fallback 도움말",
|
|
509
530
|
"help.fallbackOrder.intro": "현재 CLI가 실패하거나 사용 불가일 때 재시도할 CLI 순서를 정합니다.",
|
package/public/locales/zh.json
CHANGED
|
@@ -493,6 +493,27 @@
|
|
|
493
493
|
"help.discord.avoid.1": "你期待通过私信投递时",
|
|
494
494
|
"help.discord.related.1": "jaw doctor 命令",
|
|
495
495
|
"help.discord.related.2": "Discord 降级模式",
|
|
496
|
+
"help.slack.title": "Slack",
|
|
497
|
+
"help.slack.aria": "Slack 帮助",
|
|
498
|
+
"help.slack.intro": "Attach a bot to your Slack workspace and drive agents from channels, DMs, and threads.",
|
|
499
|
+
"help.slack.effect": "Mentions, DMs, and slash commands in allowed conversations reach the agent, and replies come back in-thread.",
|
|
500
|
+
"help.slack.use.1": "Calling the agent by mention in a team channel",
|
|
501
|
+
"help.slack.use.2": "Running private work over DM",
|
|
502
|
+
"help.slack.avoid.1": "When you only have one token — a bot token alone cannot receive events",
|
|
503
|
+
"help.slack.related.1": "Telegram settings",
|
|
504
|
+
"help.slack.related.2": "Discord settings",
|
|
505
|
+
"help.slack.link.portal": "Open the Slack app dashboard",
|
|
506
|
+
"help.slack.link.docs": "Socket Mode documentation",
|
|
507
|
+
"help.slack.example.1": "@cli-jaw run the tests for this repo",
|
|
508
|
+
"help.slack.howTo.1": "Create an app at api.slack.com/apps (a manifest is easiest).",
|
|
509
|
+
"help.slack.howTo.2": "Enable Socket Mode and generate an app-level token (xapp-) with connections:write.",
|
|
510
|
+
"help.slack.howTo.3": "Install to the workspace to obtain the bot token (xoxb-).",
|
|
511
|
+
"help.slack.howTo.4": "Paste each token into its own field below — do not swap them.",
|
|
512
|
+
"help.slack.howTo.5": "List the conversation IDs the bot may act in. Empty means all are allowed.",
|
|
513
|
+
"help.slack.howTo.6": "Invite the bot in each channel with /invite @yourbot.",
|
|
514
|
+
"help.slack.howTo.7": "With Mention Only on, the bot answers channels only when mentioned. DMs always work.",
|
|
515
|
+
"help.slack.howTo.8": "With Reply in Thread on, answers land in the originating message thread.",
|
|
516
|
+
"help.slack.howTo.9": "Run cli-jaw doctor to verify the tokens and connection state.",
|
|
496
517
|
"help.fallbackOrder.title": "Fallback 设置",
|
|
497
518
|
"help.fallbackOrder.aria": "Fallback 帮助",
|
|
498
519
|
"help.fallbackOrder.intro": "设置当前 CLI 失败或不可用时尝试其他 CLI 的顺序。",
|
|
@@ -33,6 +33,7 @@ const PAGE_REGISTRY: Record<
|
|
|
33
33
|
model: lazy(() => import('./pages/ModelProvider')),
|
|
34
34
|
'channels-telegram': lazy(() => import('./pages/ChannelsTelegram')),
|
|
35
35
|
'channels-discord': lazy(() => import('./pages/ChannelsDiscord')),
|
|
36
|
+
'channels-slack': lazy(() => import('./pages/ChannelsSlack')),
|
|
36
37
|
speech: lazy(() => import('./pages/SpeechKeys')),
|
|
37
38
|
heartbeat: lazy(() => import('./pages/Heartbeat')),
|
|
38
39
|
memory: lazy(() => import('./pages/Memory')),
|
|
@@ -19,6 +19,7 @@ const CATEGORIES: SidebarEntry[] = [
|
|
|
19
19
|
{ id: 'display', label: 'Display', group: 'identity' },
|
|
20
20
|
{ id: 'channels-telegram', label: 'Channels — Telegram', group: 'channels' },
|
|
21
21
|
{ id: 'channels-discord', label: 'Channels — Discord', group: 'channels' },
|
|
22
|
+
{ id: 'channels-slack', label: 'Channels — Slack', group: 'channels' },
|
|
22
23
|
{ id: 'telegram-hub', label: 'Telegram Hub', group: 'channels' },
|
|
23
24
|
{ id: 'heartbeat', label: 'Heartbeat & schedules', group: 'automation' },
|
|
24
25
|
{ id: 'memory', label: 'Memory', group: 'automation' },
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// Channels (Slack) page.
|
|
2
|
+
//
|
|
3
|
+
// Settings keys:
|
|
4
|
+
// channel (shared across telegram + discord + slack pages)
|
|
5
|
+
// slack.enabled
|
|
6
|
+
// slack.botToken (SecretField, masked, never logged)
|
|
7
|
+
// slack.appToken (SecretField, masked, never logged)
|
|
8
|
+
// slack.teamId
|
|
9
|
+
// slack.channelIds (string[])
|
|
10
|
+
// slack.forwardAll
|
|
11
|
+
// slack.allowBots
|
|
12
|
+
// slack.mentionOnly
|
|
13
|
+
// slack.replyInThread
|
|
14
|
+
|
|
15
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
16
|
+
import type { SettingsPageProps, DirtyEntry } from '../types';
|
|
17
|
+
import { ToggleField, SecretField, ChipListField, TextField } from '../fields';
|
|
18
|
+
import {
|
|
19
|
+
SettingsSection,
|
|
20
|
+
PageError,
|
|
21
|
+
PageLoading,
|
|
22
|
+
PageOffline,
|
|
23
|
+
usePageSnapshot,
|
|
24
|
+
} from './page-shell';
|
|
25
|
+
import { expandPatch } from './path-utils';
|
|
26
|
+
import { ActiveChannelToggle } from './components/ActiveChannelToggle';
|
|
27
|
+
import type { ActiveChannel } from './components/ActiveChannelToggle';
|
|
28
|
+
import { TransportStatusChips } from './components/TransportStatusChips';
|
|
29
|
+
|
|
30
|
+
type SlackBlock = {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
botToken?: string;
|
|
33
|
+
appToken?: string;
|
|
34
|
+
teamId?: string;
|
|
35
|
+
channelIds?: string[];
|
|
36
|
+
forwardAll?: boolean;
|
|
37
|
+
allowBots?: boolean;
|
|
38
|
+
mentionOnly?: boolean;
|
|
39
|
+
replyInThread?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type SlackSnapshot = {
|
|
43
|
+
channel?: ActiveChannel;
|
|
44
|
+
slack?: SlackBlock;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const SLACK_KEYS = [
|
|
49
|
+
'channel',
|
|
50
|
+
'slack.enabled',
|
|
51
|
+
'slack.botToken',
|
|
52
|
+
'slack.appToken',
|
|
53
|
+
'slack.teamId',
|
|
54
|
+
'slack.channelIds',
|
|
55
|
+
'slack.forwardAll',
|
|
56
|
+
'slack.allowBots',
|
|
57
|
+
'slack.mentionOnly',
|
|
58
|
+
'slack.replyInThread',
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Slack conversation ids are prefix-typed, not numeric snowflakes:
|
|
63
|
+
* C (channel), G (group/MPIM), D (IM) followed by uppercase alphanumerics.
|
|
64
|
+
* This is the Slack analogue of ChannelsDiscord's isValidSnowflake.
|
|
65
|
+
*/
|
|
66
|
+
export function isValidSlackConversationId(chip: string): boolean {
|
|
67
|
+
if (!chip) return false;
|
|
68
|
+
return /^[CGD][A-Z0-9]{6,20}$/.test(chip.trim().toUpperCase());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default function ChannelsSlack({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
72
|
+
const { state, refresh, setData } = usePageSnapshot<SlackSnapshot>(client, '/api/settings');
|
|
73
|
+
|
|
74
|
+
const [enabled, setEnabled] = useState(false);
|
|
75
|
+
const [botToken, setBotToken] = useState('');
|
|
76
|
+
const [appToken, setAppToken] = useState('');
|
|
77
|
+
const [teamId, setTeamId] = useState('');
|
|
78
|
+
const [channelIds, setChannelIds] = useState<string[]>([]);
|
|
79
|
+
const [forwardAll, setForwardAll] = useState(true);
|
|
80
|
+
const [allowBots, setAllowBots] = useState(false);
|
|
81
|
+
const [mentionOnly, setMentionOnly] = useState(true);
|
|
82
|
+
const [replyInThread, setReplyInThread] = useState(true);
|
|
83
|
+
|
|
84
|
+
const applySnapshot = useCallback((sc: SlackBlock) => {
|
|
85
|
+
setEnabled(Boolean(sc.enabled));
|
|
86
|
+
setBotToken('');
|
|
87
|
+
setAppToken('');
|
|
88
|
+
setTeamId(sc.teamId ?? '');
|
|
89
|
+
setChannelIds(Array.isArray(sc.channelIds) ? [...sc.channelIds] : []);
|
|
90
|
+
setForwardAll(sc.forwardAll !== false);
|
|
91
|
+
setAllowBots(Boolean(sc.allowBots));
|
|
92
|
+
// These two default TRUE for Slack, so a Boolean() read would show them
|
|
93
|
+
// off on a fresh install while the backend behaves as on.
|
|
94
|
+
setMentionOnly(sc.mentionOnly !== false);
|
|
95
|
+
setReplyInThread(sc.replyInThread !== false);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (state.kind !== 'ready') return;
|
|
100
|
+
applySnapshot(state.data.slack || {});
|
|
101
|
+
}, [state, applySnapshot]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
return () => {
|
|
105
|
+
for (const key of SLACK_KEYS) dirty.remove(key);
|
|
106
|
+
};
|
|
107
|
+
}, [dirty]);
|
|
108
|
+
|
|
109
|
+
const setEntry = useCallback(
|
|
110
|
+
(key: string, entry: DirtyEntry) => dirty.set(key, entry),
|
|
111
|
+
[dirty],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const original = useMemo<SlackBlock>(() => {
|
|
115
|
+
if (state.kind !== 'ready') return {};
|
|
116
|
+
return state.data.slack || {};
|
|
117
|
+
}, [state]);
|
|
118
|
+
|
|
119
|
+
const originalChannel = state.kind === 'ready' ? state.data.channel : undefined;
|
|
120
|
+
|
|
121
|
+
const onSave = useCallback(async () => {
|
|
122
|
+
const bundle = dirty.saveBundle();
|
|
123
|
+
if (Object.keys(bundle).length === 0) return;
|
|
124
|
+
const patch = expandPatch(bundle);
|
|
125
|
+
const updated = await client.put<SlackSnapshot>('/api/settings', patch);
|
|
126
|
+
const fresh = (updated && typeof updated === 'object' && 'data' in updated
|
|
127
|
+
? (updated as { data: SlackSnapshot }).data
|
|
128
|
+
: updated) as SlackSnapshot;
|
|
129
|
+
dirty.clear();
|
|
130
|
+
setData(fresh);
|
|
131
|
+
applySnapshot(fresh.slack || {});
|
|
132
|
+
await refresh();
|
|
133
|
+
}, [client, dirty, refresh, setData, applySnapshot]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!registerSave) return;
|
|
137
|
+
registerSave(onSave);
|
|
138
|
+
return () => registerSave(null);
|
|
139
|
+
}, [registerSave, onSave]);
|
|
140
|
+
|
|
141
|
+
if (state.kind === 'loading') return <PageLoading />;
|
|
142
|
+
if (state.kind === 'offline') return <PageOffline port={port} />;
|
|
143
|
+
if (state.kind === 'error') return <PageError message={state.message} />;
|
|
144
|
+
|
|
145
|
+
const maskOf = (value: string | undefined) =>
|
|
146
|
+
value ? `••••••••${value.slice(-4)}` : '(empty)';
|
|
147
|
+
const invalidChannelIds = channelIds.filter((c) => !isValidSlackConversationId(c));
|
|
148
|
+
const channelIdsError = invalidChannelIds.length > 0
|
|
149
|
+
? `Slack conversation IDs only (C…/G…/D…) — invalid: ${invalidChannelIds.join(', ')}`
|
|
150
|
+
: null;
|
|
151
|
+
const botTokenError = botToken && !botToken.startsWith('xoxb-')
|
|
152
|
+
? 'Bot tokens start with xoxb-.'
|
|
153
|
+
: null;
|
|
154
|
+
const appTokenError = appToken && !appToken.startsWith('xapp-')
|
|
155
|
+
? 'App-level tokens start with xapp-.'
|
|
156
|
+
: null;
|
|
157
|
+
// Surface the outbound-only state on THIS page rather than changing the
|
|
158
|
+
// shared status chips, which are frozen for cross-channel changes.
|
|
159
|
+
const outboundOnly = Boolean(original.enabled) && Boolean(original.botToken) && !original.appToken;
|
|
160
|
+
const slackHint = outboundOnly
|
|
161
|
+
? 'Currently OUTBOUND-ONLY: the app-level token (xapp-) is missing, so no inbound Slack events can arrive.'
|
|
162
|
+
: 'Slack needs TWO tokens: a bot token (xoxb-) for the Web API and an app-level token (xapp-) for Socket Mode.';
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<form
|
|
166
|
+
className="settings-page-form"
|
|
167
|
+
onSubmit={(event) => {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
void onSave();
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<SettingsSection
|
|
173
|
+
title="Channels"
|
|
174
|
+
hint="Choose which channel receives inbound chat. Outbound send can still work on the other channels when configured."
|
|
175
|
+
>
|
|
176
|
+
<ActiveChannelToggle
|
|
177
|
+
original={originalChannel}
|
|
178
|
+
dirty={dirty}
|
|
179
|
+
idPrefix="sl-channel"
|
|
180
|
+
/>
|
|
181
|
+
<TransportStatusChips client={client} channel="slack" />
|
|
182
|
+
</SettingsSection>
|
|
183
|
+
|
|
184
|
+
<SettingsSection
|
|
185
|
+
title="Slack"
|
|
186
|
+
hint={slackHint}
|
|
187
|
+
>
|
|
188
|
+
<ToggleField
|
|
189
|
+
id="sl-enabled"
|
|
190
|
+
label="Slack enabled"
|
|
191
|
+
value={enabled}
|
|
192
|
+
onChange={(next) => {
|
|
193
|
+
setEnabled(next);
|
|
194
|
+
setEntry('slack.enabled', {
|
|
195
|
+
value: next,
|
|
196
|
+
original: Boolean(original.enabled),
|
|
197
|
+
valid: true,
|
|
198
|
+
});
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
<SecretField
|
|
202
|
+
id="sl-botToken"
|
|
203
|
+
label="Bot token"
|
|
204
|
+
value={botToken}
|
|
205
|
+
placeholder={maskOf(original.botToken)}
|
|
206
|
+
error={botTokenError}
|
|
207
|
+
onChange={(next) => {
|
|
208
|
+
setBotToken(next);
|
|
209
|
+
if (next.length === 0) {
|
|
210
|
+
dirty.remove('slack.botToken');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
setEntry('slack.botToken', {
|
|
214
|
+
value: next,
|
|
215
|
+
original: original.botToken ?? '',
|
|
216
|
+
valid: next.startsWith('xoxb-'),
|
|
217
|
+
});
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
<SecretField
|
|
221
|
+
id="sl-appToken"
|
|
222
|
+
label="App-level token"
|
|
223
|
+
value={appToken}
|
|
224
|
+
placeholder={maskOf(original.appToken)}
|
|
225
|
+
error={appTokenError}
|
|
226
|
+
onChange={(next) => {
|
|
227
|
+
setAppToken(next);
|
|
228
|
+
if (next.length === 0) {
|
|
229
|
+
dirty.remove('slack.appToken');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
setEntry('slack.appToken', {
|
|
233
|
+
value: next,
|
|
234
|
+
original: original.appToken ?? '',
|
|
235
|
+
valid: next.startsWith('xapp-'),
|
|
236
|
+
});
|
|
237
|
+
}}
|
|
238
|
+
/>
|
|
239
|
+
<TextField
|
|
240
|
+
id="sl-teamId"
|
|
241
|
+
label="Team ID"
|
|
242
|
+
value={teamId}
|
|
243
|
+
placeholder="T01234567 (optional)"
|
|
244
|
+
onChange={(next) => {
|
|
245
|
+
setTeamId(next);
|
|
246
|
+
setEntry('slack.teamId', {
|
|
247
|
+
value: next,
|
|
248
|
+
original: original.teamId ?? '',
|
|
249
|
+
valid: true,
|
|
250
|
+
});
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
<ChipListField
|
|
254
|
+
id="sl-channelIds"
|
|
255
|
+
label="Channel IDs"
|
|
256
|
+
value={channelIds}
|
|
257
|
+
placeholder="C01234567"
|
|
258
|
+
error={channelIdsError}
|
|
259
|
+
onChange={(next) => {
|
|
260
|
+
setChannelIds(next);
|
|
261
|
+
setEntry('slack.channelIds', {
|
|
262
|
+
value: next,
|
|
263
|
+
original: original.channelIds ?? [],
|
|
264
|
+
valid: next.every(isValidSlackConversationId),
|
|
265
|
+
});
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
<ToggleField
|
|
269
|
+
id="sl-mentionOnly"
|
|
270
|
+
label="Mention only"
|
|
271
|
+
value={mentionOnly}
|
|
272
|
+
onChange={(next) => {
|
|
273
|
+
setMentionOnly(next);
|
|
274
|
+
setEntry('slack.mentionOnly', {
|
|
275
|
+
value: next,
|
|
276
|
+
original: original.mentionOnly !== false,
|
|
277
|
+
valid: true,
|
|
278
|
+
});
|
|
279
|
+
}}
|
|
280
|
+
/>
|
|
281
|
+
<ToggleField
|
|
282
|
+
id="sl-replyInThread"
|
|
283
|
+
label="Reply in thread"
|
|
284
|
+
value={replyInThread}
|
|
285
|
+
onChange={(next) => {
|
|
286
|
+
setReplyInThread(next);
|
|
287
|
+
setEntry('slack.replyInThread', {
|
|
288
|
+
value: next,
|
|
289
|
+
original: original.replyInThread !== false,
|
|
290
|
+
valid: true,
|
|
291
|
+
});
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
<ToggleField
|
|
295
|
+
id="sl-forwardAll"
|
|
296
|
+
label="Forward all"
|
|
297
|
+
value={forwardAll}
|
|
298
|
+
onChange={(next) => {
|
|
299
|
+
setForwardAll(next);
|
|
300
|
+
setEntry('slack.forwardAll', {
|
|
301
|
+
value: next,
|
|
302
|
+
original: original.forwardAll !== false,
|
|
303
|
+
valid: true,
|
|
304
|
+
});
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
<ToggleField
|
|
308
|
+
id="sl-allowBots"
|
|
309
|
+
label="Allow bots"
|
|
310
|
+
value={allowBots}
|
|
311
|
+
onChange={(next) => {
|
|
312
|
+
setAllowBots(next);
|
|
313
|
+
setEntry('slack.allowBots', {
|
|
314
|
+
value: next,
|
|
315
|
+
original: Boolean(original.allowBots),
|
|
316
|
+
valid: true,
|
|
317
|
+
});
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
</SettingsSection>
|
|
321
|
+
</form>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
import { useCallback, useState } from 'react';
|
|
12
12
|
import type { DirtyStore } from '../../types';
|
|
13
13
|
|
|
14
|
-
export type ActiveChannel = 'telegram' | 'discord';
|
|
14
|
+
export type ActiveChannel = 'telegram' | 'discord' | 'slack';
|
|
15
15
|
|
|
16
16
|
const CHANNELS: ReadonlyArray<{ value: ActiveChannel; label: string }> = [
|
|
17
17
|
{ value: 'telegram', label: 'Telegram' },
|
|
18
18
|
{ value: 'discord', label: 'Discord' },
|
|
19
|
+
{ value: 'slack', label: 'Slack' },
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
type Props = {
|