mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-06 18:52:03 +08:00
feat(approvals): colored buttons on approval cards (Slack primary/danger)
Approval cards render every button with the same neutral style, so Approve
and Reject read identically at a glance. The chat SDK's Button already
accepts style ('primary' | 'danger' | 'default') and @chat-adapter/slack
maps primary→green and danger→red Block Kit styles (Telegram ignores it),
but the ask_question pipeline dropped the field.
- ask-question.ts: add OptionStyle and an optional style field to
OptionInput/NormalizedOption; normalizeOption whitelists the value.
Bare-string options stay unstyled.
- chat-sdk-bridge.ts: forward opt.style into Button() on ask_question
cards. Persisted options_json tolerates the extra key
(getAskQuestionRender only reads label/selectedLabel/value).
- Producers: module approvals (Approve/Reject), sender approvals
(Allow/Deny), channel approvals (Connect/Reject), and OneCLI credential
cards (Approve/Reject) annotate primary/danger. Multi-choice picker
options stay unstyled — a list of equals.
Purely additive: options without style render exactly as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,15 @@
|
||||
* and rendering.
|
||||
*/
|
||||
|
||||
/** Chat SDK button styles — Slack maps primary→green, danger→red; platforms
|
||||
* without button colors (Telegram) ignore it. */
|
||||
export type OptionStyle = 'primary' | 'danger' | 'default';
|
||||
|
||||
export interface OptionInput {
|
||||
label: string;
|
||||
selectedLabel?: string;
|
||||
value?: string;
|
||||
style?: OptionStyle;
|
||||
}
|
||||
|
||||
export type RawOption = string | OptionInput;
|
||||
@@ -19,6 +24,7 @@ export interface NormalizedOption {
|
||||
label: string;
|
||||
selectedLabel: string;
|
||||
value: string;
|
||||
style?: OptionStyle;
|
||||
}
|
||||
|
||||
export function normalizeOption(raw: RawOption): NormalizedOption {
|
||||
@@ -30,6 +36,7 @@ export function normalizeOption(raw: RawOption): NormalizedOption {
|
||||
label,
|
||||
selectedLabel: raw.selectedLabel ?? label,
|
||||
value: raw.value ?? label,
|
||||
style: raw.style === 'primary' || raw.style === 'danger' || raw.style === 'default' ? raw.style : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
// well past that. The onAction handlers resolve the index back
|
||||
// to the real value via getAskQuestionRender(questionId).
|
||||
options.map((opt, idx) =>
|
||||
Button({ id: `ncq:${questionId}:${idx}`, label: opt.label, value: String(idx) }),
|
||||
Button({ id: `ncq:${questionId}:${idx}`, label: opt.label, value: String(idx), style: opt.style }),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -147,8 +147,8 @@ async function handleRequest(request: ApprovalRequest): Promise<Decision> {
|
||||
|
||||
const onecliTitle = 'Credentials Request';
|
||||
const onecliOptions = [
|
||||
{ label: 'Approve', selectedLabel: '✅ Approved', value: 'approve' },
|
||||
{ label: 'Reject', selectedLabel: '❌ Rejected', value: 'reject' },
|
||||
{ label: 'Approve', selectedLabel: '✅ Approved', value: 'approve', style: 'primary' as const },
|
||||
{ label: 'Reject', selectedLabel: '❌ Rejected', value: 'reject', style: 'danger' as const },
|
||||
];
|
||||
let platformMessageId: string | undefined;
|
||||
try {
|
||||
|
||||
@@ -46,8 +46,8 @@ export const REJECT_WITH_REASON_VALUE = 'reject_with_reason';
|
||||
* keep their own two-button set in onecli-approvals.ts.
|
||||
*/
|
||||
const APPROVAL_OPTIONS: RawOption[] = [
|
||||
{ label: 'Approve', selectedLabel: '✅ Approved', value: 'approve' },
|
||||
{ label: 'Reject', selectedLabel: '❌ Rejected', value: 'reject' },
|
||||
{ label: 'Approve', selectedLabel: '✅ Approved', value: 'approve', style: 'primary' },
|
||||
{ label: 'Reject', selectedLabel: '❌ Rejected', value: 'reject', style: 'danger' },
|
||||
{ label: 'Reject with reason…', selectedLabel: '📝 Rejected (awaiting reason)', value: REJECT_WITH_REASON_VALUE },
|
||||
];
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ function buildApprovalOptions(agentGroups: AgentGroup[], approverUserId?: string
|
||||
label: `Connect to ${visibleAgentGroups[0].name}`,
|
||||
selectedLabel: `✅ Connected to ${visibleAgentGroups[0].name}`,
|
||||
value: `${CONNECT_PREFIX}${visibleAgentGroups[0].id}`,
|
||||
style: 'primary',
|
||||
});
|
||||
} else if (visibleAgentGroups.length > 1) {
|
||||
options.push({
|
||||
@@ -110,6 +111,7 @@ function buildApprovalOptions(agentGroups: AgentGroup[], approverUserId?: string
|
||||
label: 'Reject',
|
||||
selectedLabel: '🙅 Rejected',
|
||||
value: REJECT_VALUE,
|
||||
style: 'danger',
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ import { pickApprovalDelivery, pickApprover } from '../approvals/primitive.js';
|
||||
import { createPendingSenderApproval, hasInFlightSenderApproval } from './db/pending-sender-approvals.js';
|
||||
|
||||
const APPROVAL_OPTIONS: RawOption[] = [
|
||||
{ label: 'Allow', selectedLabel: '✅ Allowed', value: 'approve' },
|
||||
{ label: 'Deny', selectedLabel: '❌ Denied', value: 'reject' },
|
||||
{ label: 'Allow', selectedLabel: '✅ Allowed', value: 'approve', style: 'primary' },
|
||||
{ label: 'Deny', selectedLabel: '❌ Denied', value: 'reject', style: 'danger' },
|
||||
];
|
||||
|
||||
function generateId(): string {
|
||||
|
||||
Reference in New Issue
Block a user