cli-jaw 2.2.8 → 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/agent-driven.js +33 -0
- package/dist/bin/agent-driven.js.map +1 -0
- 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/bin/interactive-confirm.js +110 -0
- package/dist/bin/interactive-confirm.js.map +1 -0
- package/dist/bin/star-prompt.js +46 -15
- package/dist/bin/star-prompt.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
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// ─── Slack Bot ───────────────────────────────────────
|
|
2
|
+
// Slack transport implementation for the cli-jaw messaging runtime.
|
|
3
|
+
// Mirrors src/discord/bot.ts structurally: init/shutdown lifecycle, an inbound
|
|
4
|
+
// handler that gates then dispatches into submitMessage/orchestrateAndCollect,
|
|
5
|
+
// and a forwarder for non-Slack-origin agent output.
|
|
6
|
+
import { settings } from '../core/config.js';
|
|
7
|
+
import { log } from '../core/logger.js';
|
|
8
|
+
import { t, normalizeLocale } from '../core/i18n.js';
|
|
9
|
+
import { addBroadcastListener, removeBroadcastListener } from '../core/bus.js';
|
|
10
|
+
import { submitMessage } from '../orchestrator/gateway.js';
|
|
11
|
+
import { orchestrateAndCollect } from '../orchestrator/collect.js';
|
|
12
|
+
import { isResetIntent } from '../orchestrator/pipeline.js';
|
|
13
|
+
import { setLastActiveTarget, setLatestSeenTarget, getLastActiveTarget } from '../messaging/runtime.js';
|
|
14
|
+
import { slackTargetFromId, resolveSlackThreadTs } from '../messaging/slack-target.js';
|
|
15
|
+
import { slackApi } from './api.js';
|
|
16
|
+
import { SlackSocketClient } from './socket.js';
|
|
17
|
+
import { resolveEventText, shouldProcessSlackEvent } from './events.js';
|
|
18
|
+
import { sendSlackText, getSlackSendClient } from './send-only-client.js';
|
|
19
|
+
import { createSlackForwarder, relaySlackImages } from './forwarder.js';
|
|
20
|
+
import { handleSlackSlashCommand } from './commands.js';
|
|
21
|
+
import { logErrorText, redactOutboundText } from '../messaging/redact.js';
|
|
22
|
+
let socketClient = null;
|
|
23
|
+
let forwarderHandler = null;
|
|
24
|
+
let selfUserId = null;
|
|
25
|
+
let slackInitLock = false;
|
|
26
|
+
/**
|
|
27
|
+
* Listeners waiting on a queued agent result. Tracked so shutdown can drop
|
|
28
|
+
* them immediately instead of leaving them armed for their 5-minute timeout,
|
|
29
|
+
* which would let a post-shutdown result fire against a dead transport.
|
|
30
|
+
*/
|
|
31
|
+
const pendingQueueWaiters = new Set();
|
|
32
|
+
/**
|
|
33
|
+
* Bumped by every init and shutdown. An `initSlack` suspended on an await
|
|
34
|
+
* checks it afterwards, so a shutdown that races the auth round-trip cannot be
|
|
35
|
+
* undone by the stale initialization resuming and resurrecting the transport.
|
|
36
|
+
*/
|
|
37
|
+
let lifecycleGeneration = 0;
|
|
38
|
+
/**
|
|
39
|
+
* Set when an init arrives while another is already running. The in-flight
|
|
40
|
+
* init drains it on the way out, so a rapid disable/re-enable cannot leave
|
|
41
|
+
* Slack permanently off just because its start request landed mid-teardown.
|
|
42
|
+
*/
|
|
43
|
+
let initRequestPending = false;
|
|
44
|
+
export function getSlackSelfUserId() { return selfUserId; }
|
|
45
|
+
export function getSlackConnectionState() {
|
|
46
|
+
return socketClient?.getState() ?? 'disconnected';
|
|
47
|
+
}
|
|
48
|
+
function currentLocale() { return normalizeLocale(settings["locale"], 'ko'); }
|
|
49
|
+
function gateConfig() {
|
|
50
|
+
const sc = settings["slack"] || {};
|
|
51
|
+
return {
|
|
52
|
+
selfUserId,
|
|
53
|
+
allowBots: Boolean(sc.allowBots),
|
|
54
|
+
mentionOnly: sc.mentionOnly !== false,
|
|
55
|
+
channelIds: Array.isArray(sc.channelIds) ? sc.channelIds : [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildSlackTarget(event) {
|
|
59
|
+
const replyInThread = settings["slack"]?.replyInThread !== false;
|
|
60
|
+
const threadTs = resolveSlackThreadTs(event, replyInThread);
|
|
61
|
+
const teamId = settings["slack"]?.teamId;
|
|
62
|
+
return slackTargetFromId(event.channel, {
|
|
63
|
+
...(threadTs ? { threadTs } : {}),
|
|
64
|
+
...(teamId ? { teamId: String(teamId) } : {}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// ─── Dispatch (full reply path) ─────────────────────
|
|
68
|
+
async function slackOrchestrate(target, prompt, displayMsg) {
|
|
69
|
+
const client = getSlackSendClient();
|
|
70
|
+
if (!client.token)
|
|
71
|
+
return;
|
|
72
|
+
const token = client.token;
|
|
73
|
+
const chatId = target.targetId;
|
|
74
|
+
const result = submitMessage(prompt, {
|
|
75
|
+
origin: 'slack', displayText: displayMsg, skipOrchestrate: true, target, chatId,
|
|
76
|
+
});
|
|
77
|
+
if (result.action === 'queued') {
|
|
78
|
+
log.info(`[slack:queue] agent busy, queued (${result.pending} pending)`);
|
|
79
|
+
const requestId = result.requestId;
|
|
80
|
+
let queueTimeout;
|
|
81
|
+
let disposed = false;
|
|
82
|
+
const dispose = () => {
|
|
83
|
+
if (disposed)
|
|
84
|
+
return;
|
|
85
|
+
disposed = true;
|
|
86
|
+
clearTimeout(queueTimeout);
|
|
87
|
+
removeBroadcastListener(queueHandler);
|
|
88
|
+
pendingQueueWaiters.delete(dispose);
|
|
89
|
+
};
|
|
90
|
+
const queueHandler = async (type, data) => {
|
|
91
|
+
if (type === 'orchestrate_done' && data["text"] && data["origin"] === 'slack'
|
|
92
|
+
&& data["requestId"] === requestId) {
|
|
93
|
+
// Dispose FIRST so a duplicate broadcast cannot double-post.
|
|
94
|
+
if (disposed)
|
|
95
|
+
return;
|
|
96
|
+
dispose();
|
|
97
|
+
const text = String(data["text"]);
|
|
98
|
+
await sendSlackText(token, target, text);
|
|
99
|
+
await relaySlackImages(token, target, text);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// Register BEFORE any await: a fast queued job can finish while the
|
|
103
|
+
// "queued" notice is still in flight, and the completion broadcast
|
|
104
|
+
// would be missed entirely.
|
|
105
|
+
addBroadcastListener(queueHandler);
|
|
106
|
+
pendingQueueWaiters.add(dispose);
|
|
107
|
+
queueTimeout = setTimeout(dispose, 300000);
|
|
108
|
+
await sendSlackText(token, target, t('tg.queued', { count: result.pending }, currentLocale()));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (result.action === 'rejected') {
|
|
112
|
+
await sendSlackText(token, target, `❌ ${result.reason}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const text = String(await orchestrateAndCollect(prompt, {
|
|
117
|
+
origin: 'slack', target, chatId, requestId: result.requestId, _skipInsert: true,
|
|
118
|
+
}));
|
|
119
|
+
await sendSlackText(token, target, text);
|
|
120
|
+
await relaySlackImages(token, target, text);
|
|
121
|
+
log.info(`[slack:out] ${target.targetId}: ${redactOutboundText(text).slice(0, 80)}`);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
log.error('[slack:error]', logErrorText(err));
|
|
125
|
+
await sendSlackText(token, target, `❌ Error: ${err.message}`).catch(() => { });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ─── Envelope routing ───────────────────────────────
|
|
129
|
+
export async function handleSlackEnvelope(envelope) {
|
|
130
|
+
if (envelope.type === 'slash_commands') {
|
|
131
|
+
await handleSlackSlashCommand(envelope.payload || {});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (envelope.type === 'interactive') {
|
|
135
|
+
// v1 scope: acked by the socket layer and logged. Block Kit callback
|
|
136
|
+
// routing is a recorded follow-up (050 section 5.8).
|
|
137
|
+
log.info('[slack:interactive] received (not routed in v1)');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const payload = envelope.payload;
|
|
141
|
+
const event = payload?.event;
|
|
142
|
+
if (!event)
|
|
143
|
+
return;
|
|
144
|
+
const decision = shouldProcessSlackEvent(event, gateConfig(), envelope.type);
|
|
145
|
+
if (!decision.process) {
|
|
146
|
+
log.info(`[slack:in] skipped (${decision.reason})`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const target = buildSlackTarget(event);
|
|
150
|
+
setLastActiveTarget('slack', target);
|
|
151
|
+
setLatestSeenTarget('slack', target);
|
|
152
|
+
const text = resolveEventText(event, selfUserId);
|
|
153
|
+
if (!text)
|
|
154
|
+
return;
|
|
155
|
+
log.info(`[slack:in] ${event.channel}: ${redactOutboundText(text).slice(0, 80)}`);
|
|
156
|
+
if (isResetIntent(text)) {
|
|
157
|
+
const client = getSlackSendClient();
|
|
158
|
+
const result = submitMessage(text, { origin: 'slack', target });
|
|
159
|
+
if (client.token) {
|
|
160
|
+
await sendSlackText(client.token, target, result.action === 'rejected'
|
|
161
|
+
? t('ws.agentBusy', {}, currentLocale())
|
|
162
|
+
: t('tg.resetDone', {}, currentLocale()));
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
slackOrchestrate(target, text, text).catch(e => log.error('[slack:orchestrate]', logErrorText(e)));
|
|
167
|
+
}
|
|
168
|
+
// ─── Init / Shutdown ────────────────────────────────
|
|
169
|
+
export async function initSlack() {
|
|
170
|
+
if (slackInitLock) {
|
|
171
|
+
// Do not discard the request: the running init may be about to abort
|
|
172
|
+
// because THIS caller's shutdown superseded it.
|
|
173
|
+
log.info('[slack] initSlack already in progress — queuing a follow-up');
|
|
174
|
+
initRequestPending = true;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
slackInitLock = true;
|
|
178
|
+
try {
|
|
179
|
+
// Claim the generation FIRST so an external shutdown that lands while
|
|
180
|
+
// we are tearing down or authenticating is not lost, then tear down
|
|
181
|
+
// WITHOUT bumping it — an internal teardown must not invalidate the
|
|
182
|
+
// init it belongs to.
|
|
183
|
+
const generation = ++lifecycleGeneration;
|
|
184
|
+
await disposeSlackRuntime();
|
|
185
|
+
const sc = settings["slack"];
|
|
186
|
+
if (!sc?.enabled || !sc?.botToken) {
|
|
187
|
+
log.info('[slack] ⏭️ Slack pending (disabled or no bot token)');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!sc.appToken) {
|
|
191
|
+
// Outbound still works via the send transport; only inbound needs
|
|
192
|
+
// the app-level token. Say so precisely instead of "failed".
|
|
193
|
+
log.warn('[slack] app-level token missing — outbound only, no inbound events');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const auth = await slackApi(sc.botToken, 'auth.test');
|
|
197
|
+
// A shutdown may have landed while auth.test was in flight; resuming
|
|
198
|
+
// here would resurrect a transport the caller asked us to stop.
|
|
199
|
+
if (generation !== lifecycleGeneration) {
|
|
200
|
+
log.info('[slack] init superseded during auth — aborting');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!auth.ok) {
|
|
204
|
+
log.error('[slack] auth.test failed:', auth.error);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
selfUserId = auth.data?.user_id || null;
|
|
208
|
+
if (auth.data?.team_id && !sc.teamId)
|
|
209
|
+
sc.teamId = auth.data.team_id;
|
|
210
|
+
const client = new SlackSocketClient({
|
|
211
|
+
appToken: sc.appToken,
|
|
212
|
+
onEnvelope: handleSlackEnvelope,
|
|
213
|
+
});
|
|
214
|
+
socketClient = client;
|
|
215
|
+
await client.start();
|
|
216
|
+
if (generation !== lifecycleGeneration) {
|
|
217
|
+
log.info('[slack] init superseded during connect — disposing socket');
|
|
218
|
+
client.stop();
|
|
219
|
+
if (socketClient === client)
|
|
220
|
+
socketClient = null;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
forwarderHandler = createSlackForwarder({
|
|
224
|
+
getToken: () => getSlackSendClient().token,
|
|
225
|
+
getLastTarget: () => getLastActiveTarget('slack'),
|
|
226
|
+
shouldSkip: (data) => data["origin"] === 'slack',
|
|
227
|
+
});
|
|
228
|
+
addBroadcastListener(forwarderHandler);
|
|
229
|
+
log.info(`[slack] ✅ connected as ${selfUserId || 'unknown'}`);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
slackInitLock = false;
|
|
233
|
+
if (initRequestPending) {
|
|
234
|
+
initRequestPending = false;
|
|
235
|
+
await initSlack();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export async function shutdownSlack() {
|
|
240
|
+
lifecycleGeneration++;
|
|
241
|
+
await disposeSlackRuntime();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Release every runtime resource WITHOUT touching the lifecycle generation.
|
|
245
|
+
* `initSlack` reuses this for its own teardown; only an external
|
|
246
|
+
* `shutdownSlack` invalidates in-flight initializations.
|
|
247
|
+
*/
|
|
248
|
+
async function disposeSlackRuntime() {
|
|
249
|
+
if (forwarderHandler) {
|
|
250
|
+
removeBroadcastListener(forwarderHandler);
|
|
251
|
+
forwarderHandler = null;
|
|
252
|
+
}
|
|
253
|
+
// Drop any queued-result waiters rather than leaving them armed for their
|
|
254
|
+
// 5-minute timeout against a transport that no longer exists.
|
|
255
|
+
for (const dispose of [...pendingQueueWaiters])
|
|
256
|
+
dispose();
|
|
257
|
+
pendingQueueWaiters.clear();
|
|
258
|
+
socketClient?.stop();
|
|
259
|
+
socketClient = null;
|
|
260
|
+
selfUserId = null;
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=bot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/slack/bot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// ─── Slack Slash Commands ────────────────────────────
|
|
2
|
+
// Payload shape: { command: '/jaw', text: '...', channel_id, channel_name,
|
|
3
|
+
// user_id, team_id, ... }
|
|
4
|
+
// Routed through the SHARED command pipeline (parseCommand/executeCommand),
|
|
5
|
+
// exactly like src/discord/commands.ts. The command catalog advertises
|
|
6
|
+
// commands on Slack, so forwarding raw text here instead would make that
|
|
7
|
+
// catalog a lie.
|
|
8
|
+
//
|
|
9
|
+
// Unlike Discord there is no deferReply/editReply: the socket layer owns the
|
|
10
|
+
// 3s ack, and every reply is posted asynchronously via chat.postMessage.
|
|
11
|
+
import { settings } from '../core/config.js';
|
|
12
|
+
import { log } from '../core/logger.js';
|
|
13
|
+
import { parseCommand, executeCommand } from '../cli/commands.js';
|
|
14
|
+
import { makeCommandCtx } from '../cli/command-context.js';
|
|
15
|
+
import { normalizeLocale } from '../core/i18n.js';
|
|
16
|
+
import { resetFallbackState } from '../agent/spawn.js';
|
|
17
|
+
import { applyRuntimeSettingsPatch } from '../core/runtime-settings.js';
|
|
18
|
+
import { bumpSessionOwnershipGeneration } from '../agent/session-persistence.js';
|
|
19
|
+
import { clearMainSessionState, resetSessionPreservingHistory } from '../core/main-session.js';
|
|
20
|
+
import { resetEmployeeSessions, seedDefaultEmployees } from '../core/employees.js';
|
|
21
|
+
import { slackTargetFromId } from '../messaging/slack-target.js';
|
|
22
|
+
import { setLastActiveTarget } from '../messaging/runtime.js';
|
|
23
|
+
import { getSlackSendClient, sendSlackText } from './send-only-client.js';
|
|
24
|
+
import { isConversationAllowed } from './events.js';
|
|
25
|
+
import { logErrorText } from '../messaging/redact.js';
|
|
26
|
+
function makeSlackCommandCtx() {
|
|
27
|
+
const locale = normalizeLocale(settings["locale"], 'ko');
|
|
28
|
+
return makeCommandCtx('slack', locale, {
|
|
29
|
+
applySettings: async (patch) => {
|
|
30
|
+
bumpSessionOwnershipGeneration();
|
|
31
|
+
return applyRuntimeSettingsPatch(patch, { resetFallbackState });
|
|
32
|
+
},
|
|
33
|
+
clearSession: () => {
|
|
34
|
+
bumpSessionOwnershipGeneration();
|
|
35
|
+
clearMainSessionState();
|
|
36
|
+
},
|
|
37
|
+
resetSession: () => {
|
|
38
|
+
bumpSessionOwnershipGeneration();
|
|
39
|
+
resetSessionPreservingHistory();
|
|
40
|
+
},
|
|
41
|
+
resetEmployees: () => seedDefaultEmployees({ reset: true, notify: true }),
|
|
42
|
+
resetEmployeeSessions: () => resetEmployeeSessions(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function handleSlackSlashCommand(payload) {
|
|
46
|
+
const channelId = typeof payload['channel_id'] === 'string' ? payload['channel_id'] : '';
|
|
47
|
+
const command = typeof payload['command'] === 'string' ? payload['command'] : '';
|
|
48
|
+
const args = typeof payload['text'] === 'string' ? payload['text'].trim() : '';
|
|
49
|
+
if (!channelId || !command)
|
|
50
|
+
return;
|
|
51
|
+
// Same allowlist gate as the message path. Without this, a slash command
|
|
52
|
+
// from a non-allowlisted channel reaches orchestration while an ordinary
|
|
53
|
+
// message from the same channel is blocked.
|
|
54
|
+
const configured = settings["slack"]?.channelIds;
|
|
55
|
+
const channelIds = Array.isArray(configured) ? configured : [];
|
|
56
|
+
const isDm = channelId.toUpperCase().startsWith('D')
|
|
57
|
+
|| payload['channel_name'] === 'directmessage';
|
|
58
|
+
if (!isConversationAllowed(channelId, channelIds, isDm)) {
|
|
59
|
+
log.info(`[slack:slash] blocked (channel_not_allowed) ${channelId}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const client = getSlackSendClient();
|
|
63
|
+
if (!client.token)
|
|
64
|
+
return;
|
|
65
|
+
const token = client.token;
|
|
66
|
+
const target = slackTargetFromId(channelId);
|
|
67
|
+
setLastActiveTarget('slack', target);
|
|
68
|
+
// Slack delivers the command name and its arguments separately; the shared
|
|
69
|
+
// parser expects one "/name args" string. parseCommand returns null only
|
|
70
|
+
// for non-command text; an unrecognized NAME yields { type: 'unknown' },
|
|
71
|
+
// which executeCommand handles with its suggestion/recovery path. Short-
|
|
72
|
+
// circuiting here would bypass the UX Telegram and Discord already get.
|
|
73
|
+
const parsed = parseCommand(`${command} ${args}`.trim());
|
|
74
|
+
if (!parsed) {
|
|
75
|
+
await sendSlackText(token, target, `Unrecognized input: ${command}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = await executeCommand(parsed, makeSlackCommandCtx());
|
|
80
|
+
// /steer returns a prompt to run rather than text to print.
|
|
81
|
+
if (result?.steerPrompt) {
|
|
82
|
+
if (result.text)
|
|
83
|
+
await sendSlackText(token, target, result.text);
|
|
84
|
+
const { orchestrateAndCollect } = await import('../orchestrator/collect.js');
|
|
85
|
+
const reply = String(await orchestrateAndCollect(result.steerPrompt, {
|
|
86
|
+
origin: 'slack', target, chatId: channelId, _skipInsert: true,
|
|
87
|
+
}));
|
|
88
|
+
await sendSlackText(token, target, reply);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await sendSlackText(token, target, result?.text || '(no output)');
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
log.error('[slack:slash]', logErrorText(error));
|
|
95
|
+
await sendSlackText(token, target, `❌ ${error.message}`).catch(() => { });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/slack/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ─── Slack Event Normalization ───────────────────────
|
|
2
|
+
// Pure decision + extraction helpers. No IO, so every gating rule below is
|
|
3
|
+
// directly unit-testable without a socket or a workspace.
|
|
4
|
+
/** Message subtypes that are edits/joins/system noise, never user input. */
|
|
5
|
+
const IGNORED_SUBTYPES = new Set([
|
|
6
|
+
'message_changed',
|
|
7
|
+
'message_deleted',
|
|
8
|
+
'channel_join',
|
|
9
|
+
'channel_leave',
|
|
10
|
+
'channel_topic',
|
|
11
|
+
'channel_purpose',
|
|
12
|
+
'bot_message',
|
|
13
|
+
'thread_broadcast_deleted',
|
|
14
|
+
]);
|
|
15
|
+
export function isDirectMessage(event) {
|
|
16
|
+
return event.channel_type === 'im' || (event.channel || '').toUpperCase().startsWith('D');
|
|
17
|
+
}
|
|
18
|
+
export function mentionsUser(text, userId) {
|
|
19
|
+
return new RegExp(`<@${userId}(?:\\|[^>]*)?>`).test(text);
|
|
20
|
+
}
|
|
21
|
+
export function stripMention(text, userId) {
|
|
22
|
+
return text.replace(new RegExp(`<@${userId}(?:\\|[^>]*)?>`, 'g'), '').trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Allowlist/DM policy, shared by BOTH the message path and the slash-command
|
|
26
|
+
* path. A slash command that skipped this check would be an allowlist bypass:
|
|
27
|
+
* any user in any channel could invoke orchestration.
|
|
28
|
+
*/
|
|
29
|
+
export function isConversationAllowed(conversationId, channelIds, isDm) {
|
|
30
|
+
if (isDm)
|
|
31
|
+
return true;
|
|
32
|
+
if (!channelIds.length)
|
|
33
|
+
return true;
|
|
34
|
+
return channelIds.includes(conversationId);
|
|
35
|
+
}
|
|
36
|
+
export function shouldProcessSlackEvent(event, config, envelopeType) {
|
|
37
|
+
if (event.subtype && IGNORED_SUBTYPES.has(event.subtype)) {
|
|
38
|
+
return { process: false, reason: `subtype_${event.subtype}` };
|
|
39
|
+
}
|
|
40
|
+
// Self-echo: our own posts arrive back as message events. Without this the
|
|
41
|
+
// bot answers itself forever.
|
|
42
|
+
if (config.selfUserId && event.user === config.selfUserId) {
|
|
43
|
+
return { process: false, reason: 'self_message' };
|
|
44
|
+
}
|
|
45
|
+
if (event.bot_id && !config.allowBots) {
|
|
46
|
+
return { process: false, reason: 'bot_message' };
|
|
47
|
+
}
|
|
48
|
+
if (!event.channel) {
|
|
49
|
+
return { process: false, reason: 'missing_channel' };
|
|
50
|
+
}
|
|
51
|
+
const dm = isDirectMessage(event);
|
|
52
|
+
if (!isConversationAllowed(event.channel, config.channelIds, dm)) {
|
|
53
|
+
return { process: false, reason: 'channel_not_allowed' };
|
|
54
|
+
}
|
|
55
|
+
// app_mention envelopes are mentions by definition; message events in a
|
|
56
|
+
// channel need the gate. DMs always bypass it.
|
|
57
|
+
if (config.mentionOnly && !dm && event.type !== 'app_mention') {
|
|
58
|
+
if (!config.selfUserId || !mentionsUser(event.text || '', config.selfUserId)) {
|
|
59
|
+
return { process: false, reason: 'mention_required' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (envelopeType === 'events_api' && !event.text && !event.blocks?.length && !event.files?.length) {
|
|
63
|
+
return { process: false, reason: 'empty_event' };
|
|
64
|
+
}
|
|
65
|
+
return { process: true };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract readable text from Block Kit blocks.
|
|
69
|
+
* Slack messages forwarded from apps often have an empty `text` with all the
|
|
70
|
+
* content in `blocks`; without this the agent receives an empty prompt.
|
|
71
|
+
*
|
|
72
|
+
* Traversal is ITERATIVE: a deeply nested payload would blow the call stack
|
|
73
|
+
* in a recursive walk, and inbound block structures are attacker-influenced.
|
|
74
|
+
*/
|
|
75
|
+
export function extractTextFromBlocks(blocks, maxChars = 6000) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const stack = [...blocks];
|
|
79
|
+
let budget = maxChars;
|
|
80
|
+
while (stack.length > 0 && budget > 0) {
|
|
81
|
+
const node = stack.pop();
|
|
82
|
+
if (!node || typeof node !== 'object')
|
|
83
|
+
continue;
|
|
84
|
+
if (seen.has(node))
|
|
85
|
+
continue; // guard against cyclic payloads
|
|
86
|
+
seen.add(node);
|
|
87
|
+
const obj = node;
|
|
88
|
+
const text = obj['text'];
|
|
89
|
+
if (typeof text === 'string') {
|
|
90
|
+
out.push(text);
|
|
91
|
+
budget -= text.length;
|
|
92
|
+
}
|
|
93
|
+
else if (text) {
|
|
94
|
+
stack.push(text);
|
|
95
|
+
}
|
|
96
|
+
for (const key of ['elements', 'fields', 'blocks']) {
|
|
97
|
+
const value = obj[key];
|
|
98
|
+
// Push in reverse so popping preserves document order.
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
for (let i = value.length - 1; i >= 0; i--)
|
|
101
|
+
stack.push(value[i]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out.join('\n').slice(0, maxChars).trim();
|
|
106
|
+
}
|
|
107
|
+
/** The prompt text an agent should receive for this event. */
|
|
108
|
+
export function resolveEventText(event, selfUserId) {
|
|
109
|
+
let text = (event.text || '').trim();
|
|
110
|
+
if (!text && event.blocks?.length)
|
|
111
|
+
text = extractTextFromBlocks(event.blocks);
|
|
112
|
+
if (selfUserId)
|
|
113
|
+
text = stripMention(text, selfUserId);
|
|
114
|
+
return text.trim();
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/slack/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// ─── Slack Message Formatting ────────────────────────
|
|
2
|
+
// CommonMark (what agents emit) -> mrkdwn (what Slack renders).
|
|
3
|
+
// Conversion rules verified against docs.slack.dev/messaging/formatting-message-text
|
|
4
|
+
// The order of operations matters: bold must be handled before italic, because
|
|
5
|
+
// '**x**' contains '*x*' as a substring.
|
|
6
|
+
import { chunkFenceAware } from '../messaging/chunk.js';
|
|
7
|
+
import { redactOutboundText } from '../messaging/redact.js';
|
|
8
|
+
const CODE_FENCE = /```[\s\S]*?```/g;
|
|
9
|
+
const INLINE_CODE = /`[^`\n]+`/g;
|
|
10
|
+
// Sentinel for stashed code spans. It must not occur in the input, so any
|
|
11
|
+
// literal occurrence is stripped first — otherwise text like "a\u00000\u0000b"
|
|
12
|
+
// would be mistaken for a stash reference and silently eat content.
|
|
13
|
+
const STASH_MARK = '\u0000';
|
|
14
|
+
/** Protect code spans from formatting conversion, convert, then restore. */
|
|
15
|
+
function withCodeProtected(text, convert) {
|
|
16
|
+
const stash = [];
|
|
17
|
+
const stashed = text
|
|
18
|
+
.replaceAll(STASH_MARK, '')
|
|
19
|
+
.replace(CODE_FENCE, (m) => { stash.push(m); return `${STASH_MARK}${stash.length - 1}${STASH_MARK}`; })
|
|
20
|
+
.replace(INLINE_CODE, (m) => { stash.push(m); return `${STASH_MARK}${stash.length - 1}${STASH_MARK}`; });
|
|
21
|
+
const converted = convert(stashed);
|
|
22
|
+
return converted.replace(/\u0000(\d+)\u0000/g, (_, i) => stash[Number(i)] ?? '');
|
|
23
|
+
}
|
|
24
|
+
export function toMrkdwn(text) {
|
|
25
|
+
if (!text)
|
|
26
|
+
return '';
|
|
27
|
+
return withCodeProtected(text, (s) => s
|
|
28
|
+
// Links: [label](url) -> <url|label>. The label may contain balanced
|
|
29
|
+
// brackets (common in agent output: "[see [1]](url)").
|
|
30
|
+
.replace(/\[((?:[^\][]|\[[^\][]*\])+)\]\((https?:\/\/[^)\s]+)\)/g, '<$2|$1>')
|
|
31
|
+
// Bold+italic first: ***x*** -> *_x_* (Slack has no combined marker).
|
|
32
|
+
.replace(/\*\*\*([^*\n]+)\*\*\*/g, '*_$1_*')
|
|
33
|
+
// Bold: **x** or __x__ -> *x* (before italic)
|
|
34
|
+
.replace(/\*\*([^*\n]+)\*\*/g, '*$1*')
|
|
35
|
+
.replace(/__([^_\n]+)__/g, '*$1*')
|
|
36
|
+
// Strikethrough: ~~x~~ -> ~x~
|
|
37
|
+
.replace(/~~([^~\n]+)~~/g, '~$1~')
|
|
38
|
+
// Headings: '## Title' -> '*Title*' (mrkdwn has no heading syntax)
|
|
39
|
+
.replace(/^#{1,6}\s+(.+)$/gm, '*$1*')).replace(/```[a-zA-Z0-9_+-]+\n/g, '```\n'); // fence language tags are unsupported
|
|
40
|
+
}
|
|
41
|
+
/** Reserved for the ``` we may append/prepend when a split lands inside a fence. */
|
|
42
|
+
/**
|
|
43
|
+
* Split for chat.postMessage.
|
|
44
|
+
* Slack recommends staying under 4,000 chars and truncates above 40,000.
|
|
45
|
+
* 3,900 leaves headroom for any prefix the caller adds.
|
|
46
|
+
*
|
|
47
|
+
* Delegates to the shared splitter. Slack never sees a fence language tag:
|
|
48
|
+
* `toMrkdwn` strips it above, because mrkdwn does not render one. The shared
|
|
49
|
+
* splitter preserves tags it finds but never invents one, so that contract
|
|
50
|
+
* holds.
|
|
51
|
+
*
|
|
52
|
+
* Redaction happens here, as it does for Discord: every outbound Slack text
|
|
53
|
+
* passes through this function, so there is one place to audit.
|
|
54
|
+
*/
|
|
55
|
+
export function chunkSlackMessage(text, limit = 3900) {
|
|
56
|
+
return chunkFenceAware(redactOutboundText(text), limit);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/slack/format.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,gEAAgE;AAChE,qFAAqF;AACrF,+EAA+E;AAC/E,yCAAyC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,UAAU,GAAG,iBAAiB,CAAC;AACrC,MAAM,WAAW,GAAG,YAAY,CAAC;AACjC,0EAA0E;AAC1E,+EAA+E;AAC/E,oEAAoE;AACpE,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,IAAY,EAAE,OAA8B;IACnE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI;SACf,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;SACtG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7G,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,SAAS,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnC,qEAAqE;QACrE,uDAAuD;SACtD,OAAO,CAAC,wDAAwD,EAAE,SAAS,CAAC;QAC7E,sEAAsE;SACrE,OAAO,CAAC,wBAAwB,EAAE,QAAQ,CAAC;QAC5C,gDAAgD;SAC/C,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC;SACrC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC;QAClC,8BAA8B;SAC7B,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC;QAClC,mEAAmE;SAClE,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CACxC,CAAC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC,CAAC,sCAAsC;AACvF,CAAC;AAED,oFAAoF;AACpF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAK,GAAG,IAAI;IACxD,OAAO,eAAe,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// ─── Slack Forwarder ─────────────────────────────────
|
|
2
|
+
// Forwards agent_done results to Slack conversations.
|
|
3
|
+
// Mirrors src/discord/forwarder.ts; no client object is needed because the
|
|
4
|
+
// Slack outbound path is stateless HTTP.
|
|
5
|
+
import { settings } from '../core/config.js';
|
|
6
|
+
import { log } from '../core/logger.js';
|
|
7
|
+
import { extractLocalImagePaths } from '../messaging/extract-images.js';
|
|
8
|
+
import { assertSendFilePath } from '../security/path-guards.js';
|
|
9
|
+
import { sendSlackFile } from './slack-file.js';
|
|
10
|
+
import { sendSlackText } from './send-only-client.js';
|
|
11
|
+
import { logErrorText } from '../messaging/redact.js';
|
|
12
|
+
export async function relaySlackImages(token, target, text) {
|
|
13
|
+
for (const candidate of extractLocalImagePaths(text)) {
|
|
14
|
+
try {
|
|
15
|
+
const filePath = assertSendFilePath(candidate, settings["workingDir"] || undefined, settings["projectDirs"] || null);
|
|
16
|
+
const result = await sendSlackFile(token, target, filePath);
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
log.warn('[slack:image-relay] send failed', {
|
|
19
|
+
path: candidate,
|
|
20
|
+
error: result.error || 'unknown error',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
log.warn('[slack:image-relay] skipped', {
|
|
26
|
+
path: candidate,
|
|
27
|
+
error: error instanceof Error ? error.message : String(error),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function createSlackForwarder(opts) {
|
|
33
|
+
return async (type, data) => {
|
|
34
|
+
const text = data?.["text"];
|
|
35
|
+
if (type !== 'agent_done' || typeof text !== 'string' || !text || data["error"])
|
|
36
|
+
return;
|
|
37
|
+
if (opts.shouldSkip?.(data))
|
|
38
|
+
return;
|
|
39
|
+
const target = opts.getLastTarget();
|
|
40
|
+
const token = opts.getToken();
|
|
41
|
+
if (!target?.targetId || !token)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
const result = await sendSlackText(token, target, `${opts.prefix || ''}${text}`);
|
|
45
|
+
if (!result.ok) {
|
|
46
|
+
log.error('[slack:forward]', logErrorText(result.error || 'send failed'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await relaySlackImages(token, target, text);
|
|
50
|
+
opts.log?.({ channelId: target.targetId, preview: text.slice(0, 60) });
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
log.error('[slack:forward]', logErrorText(e));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=forwarder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarder.js","sourceRoot":"","sources":["../../../src/slack/forwarder.ts"],"names":[],"mappings":"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"}
|