Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -554,25 +554,22 @@ impl DynamicSlackTriggeredRunDeliveryHook {
#[async_trait::async_trait]
impl PostSubmitDeliveryHook for DynamicSlackTriggeredRunDeliveryHook {
async fn on_trigger_submitted(&self, fire: TriggerFire, run_id: TurnRunId, scope: TurnScope) {
let hook = self.clone();
tokio::spawn(async move {
match hook.current_driver().await {
Ok(Some(driver)) => driver.on_trigger_submitted(fire, run_id, scope).await,
Ok(None) => {
tracing::debug!(
%run_id,
"Slack dynamic triggered-run delivery skipped: Slack setup is not configured"
);
}
Err(error) => {
tracing::warn!(
%run_id,
%error,
"Slack dynamic triggered-run delivery skipped: delivery hook unavailable"
);
}
match self.current_driver().await {
Ok(Some(driver)) => driver.on_trigger_submitted(fire, run_id, scope).await,
Ok(None) => {
tracing::debug!(
%run_id,
"Slack dynamic triggered-run delivery skipped: Slack setup is not configured"
);
}
});
Err(error) => {
tracing::warn!(
%run_id,
%error,
"Slack dynamic triggered-run delivery skipped: delivery hook unavailable"
);
}
}
}
}

Expand Down
20 changes: 8 additions & 12 deletions crates/ironclaw_webui_v2_static/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,11 @@ mod tests {
}

#[test]
fn chat_connect_action_assets_render_slack_pairing_and_extensions_channel_picker() {
fn chat_omits_connect_action_while_extensions_render_slack_setup_ui() {
let chat = asset_text("js/pages/chat/chat.js");
assert!(chat.contains("ChannelConnectCard"));
assert!(chat.contains("channelConnectAction"));
assert!(chat.contains("dismissChannelConnectAction"));

let card = asset_text("js/pages/chat/components/channel-connect-card.js");
assert!(card.contains("SlackPairingSection"));
assert!(card.contains("isSlackStrategy(connectAction, \"inbound_proof_code\")"));
assert!(card.contains("action=${connectAction.action}"));
assert!(!chat.contains("ChannelConnectCard"));
assert!(!chat.contains("channelConnectAction"));
assert!(!chat.contains("dismissChannelConnectAction"));

let picker = asset_text("js/components/slack-channel-picker.js");
assert!(picker.contains("listSlackAllowedChannels"));
Expand All @@ -288,9 +283,10 @@ mod tests {
assert!(channels_tab.contains("action=${action.action}"));

let regression = source_text("js/pages/chat/lib/useChat-send.test.mjs");
assert!(regression.contains("channel connect requests return an action"));
assert!(regression.contains("without submitting a prompt"));
assert!(regression.contains("unmatched channel connect requests submit the prompt"));
assert!(regression.contains(
"slash connect text submits to the model without fetching connectable channels"
));
assert!(regression.contains("ordinary Slack chat prompts submit to the model"));
}

#[test]
Expand Down
76 changes: 0 additions & 76 deletions crates/ironclaw_webui_v2_static/static/js/lib/channel-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,3 @@ import { apiFetch } from "./api.js";
export function listConnectableChannels() {
return apiFetch("/api/webchat/v2/channels/connectable");
}

export function resolveChannelConnectCommand(input, channels) {
if (!looksLikeChannelConnectCommand(input)) return null;
const text = normalizedWords(input);
const requireExplicitCommandAlias = explicitlyTargetsSlackChannelManagement(text);
let best = null;
for (const channel of channels || []) {
if (!isChatCommandResolvable(channel)) continue;
const matchLength = bestMatchingAliasLength(text, channel, {
commandAliasesOnly: requireExplicitCommandAlias,
});
if (matchLength > (best?.matchLength || 0)) {
best = { channel, matchLength };
}
}
return best?.channel || null;
}

export function looksLikeChannelConnectCommand(input) {
const text = normalizedWords(input);
if (!text) return false;
const intent = /(^|\s)(connect|link|pair|setup|set up)(\s|$)/.test(text);
const target = /(^|\s)(account|channel|app|integration|slack|telegram|whatsapp)(\s|$)/.test(text);
return intent && target;
}

function channelAliases(channel) {
return [
channel?.channel,
channel?.display_name,
...(Array.isArray(channel?.command_aliases) ? channel.command_aliases : []),
].filter(Boolean);
}

function commandAliases(channel, options = {}) {
const aliases = Array.isArray(channel?.command_aliases)
? channel.command_aliases.filter(Boolean)
: [];
if (!options.channelManagementOnly) return aliases;
return aliases.filter((alias) => explicitlyTargetsChannelManagement(normalizedWords(alias)));
}

function isChatCommandResolvable(channel) {
return channel?.strategy !== "admin_managed_channels";
}

function explicitlyTargetsSlackChannelManagement(text) {
return includesWordPhrase(text, "slack") && explicitlyTargetsChannelManagement(text);
}

function explicitlyTargetsChannelManagement(text) {
return /(^|\s)(channel|channels|allowlist)(\s|$)/.test(text);
}

function normalizedWords(value) {
return String(value || "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, " ")
.trim()
.replace(/\s+/g, " ");
}

function bestMatchingAliasLength(text, channel, options = {}) {
const aliases = options.commandAliasesOnly
? commandAliases(channel, { channelManagementOnly: true })
: channelAliases(channel);
return aliases.reduce((best, alias) => {
const phrase = normalizedWords(alias);
return includesWordPhrase(text, phrase) ? Math.max(best, phrase.length) : best;
}, 0);
}

function includesWordPhrase(text, phrase) {
if (!phrase) return false;
return ` ${text} `.includes(` ${phrase} `);
}

This file was deleted.

13 changes: 1 addition & 12 deletions crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ApprovalCard } from "./components/approval-card.js";
import { AuthGenericCard } from "./components/auth-generic-card.js";
import { AuthOauthCard } from "./components/auth-oauth-card.js";
import { AuthTokenCard } from "./components/auth-token-card.js";
import { ChannelConnectCard } from "./components/channel-connect-card.js";
import { ChatInput } from "./components/chat-input.js";
import { ConnectionStatus } from "./components/connection-status.js";
import { EmptyState } from "./components/empty-state.js";
Expand Down Expand Up @@ -53,7 +52,6 @@ export function Chat({
isProcessing,
pendingGate,
busyGateNotice,
channelConnectAction,
suggestions,
sseStatus,
historyLoading,
Expand All @@ -70,7 +68,6 @@ export function Chat({
loadMore,
setSuggestions,
submitAuthToken,
dismissChannelConnectAction,
} = useChat(activeThreadId);

const activeThread = React.useMemo(
Expand All @@ -86,8 +83,7 @@ export function Chat({
const hasMessages =
messages.length > 0 ||
activeThreadIsProcessing ||
activeThreadHasGate ||
Boolean(channelConnectAction);
activeThreadHasGate;
// Don't show the landing composer when history failed to load — show the
// error banner instead so the user is not misled into thinking the thread
// is empty.
Expand Down Expand Up @@ -293,13 +289,6 @@ export function Chat({
/>
`}
${activeThreadIsProcessing && !activeThreadHasGate && html`<${TypingIndicator} />`}
${channelConnectAction &&
html`
<${ChannelConnectCard}
connectAction=${channelConnectAction}
onDismiss=${dismissChannelConnectAction}
/>
`}
${pendingGate &&
(pendingGate.kind === "auth_required"
? (pendingGate.challengeKind === "oauth_url"
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading