feat: integrate action mode into messaging components and enhance team provisioning banner

- Updated TeamDetailView to include action mode in message sending functionality.
- Enhanced TeamProvisioningBanner to display a message based on the online status of team members.
- Added ActionModeSelector to SendMessageDialog for selecting action modes during message composition.
- Updated tests to validate action mode handling and ensure proper messaging behavior.
This commit is contained in:
iliya 2026-03-10 12:43:03 +02:00
parent 2d0d390442
commit 5e4d10b950
4 changed files with 50 additions and 8 deletions

View file

@ -1877,12 +1877,18 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
sending={sendingMessage}
sendError={sendMessageError}
lastResult={lastSendMessageResult}
onSend={(member, text, summary, attachments) => {
onSend={(member, text, summary, attachments, actionMode) => {
void (async () => {
const sentAtMs = Date.now();
setPendingRepliesByMember((prev) => ({ ...prev, [member]: sentAtMs }));
try {
await sendTeamMessage(teamName, { member, text, summary, attachments });
await sendTeamMessage(teamName, {
member,
text,
summary,
attachments,
actionMode,
});
} catch {
setPendingRepliesByMember((prev) => {
if (prev[member] !== sentAtMs) return prev;

View file

@ -29,10 +29,11 @@ function findProgressForTeam(
export const TeamProvisioningBanner = ({
teamName,
}: TeamProvisioningBannerProps): React.JSX.Element | null => {
const { provisioningRuns, cancelProvisioning } = useStore(
const { provisioningRuns, cancelProvisioning, teamMembers } = useStore(
useShallow((s) => ({
provisioningRuns: s.provisioningRuns,
cancelProvisioning: s.cancelProvisioning,
teamMembers: s.selectedTeamData?.members,
}))
);
@ -105,14 +106,21 @@ export const TeamProvisioningBanner = ({
);
}
const allTeammatesOnline =
teamMembers != null &&
teamMembers.length > 0 &&
teamMembers.every((m) => m.status === 'active' || m.status === 'idle');
if (isReady) {
const readyMessage = allTeammatesOnline
? `Team launched — all ${teamMembers!.length} teammates online`
: 'Team launched — teammates may still be starting';
return (
<div className="mb-3">
<div className="flex items-center gap-2 rounded-md border border-[var(--step-done-border)] bg-[var(--step-done-bg)] px-3 py-2">
<CheckCircle2 size={14} className="shrink-0 text-[var(--step-done-text)]" />
<p className="flex-1 text-xs text-[var(--step-success-text)]">
Team launched teammates may still be starting
</p>
<p className="flex-1 text-xs text-[var(--step-success-text)]">{readyMessage}</p>
<Button
variant="outline"
size="sm"

View file

@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { AttachmentPreviewList } from '@renderer/components/team/attachments/AttachmentPreviewList';
import { DropZoneOverlay } from '@renderer/components/team/attachments/DropZoneOverlay';
import { ActionModeSelector } from '@renderer/components/team/messages/ActionModeSelector';
import {
Dialog,
DialogContent,
@ -28,6 +29,7 @@ import { AlertCircle, ImagePlus, Send, X } from 'lucide-react';
import { MemberBadge } from '../MemberBadge';
import type { ActionMode } from '@renderer/components/team/messages/ActionModeSelector';
import type { InlineChip } from '@renderer/types/inlineChip';
import type { MentionSuggestion } from '@renderer/types/mention';
import type { AttachmentPayload, ResolvedTeamMember, SendMessageResult } from '@shared/types';
@ -55,7 +57,8 @@ interface SendMessageDialogProps {
member: string,
text: string,
summary?: string,
attachments?: AttachmentPayload[]
attachments?: AttachmentPayload[],
actionMode?: ActionMode
) => void;
onClose: () => void;
}
@ -90,6 +93,7 @@ export const SendMessageDialog = ({
const fileInputRef = useRef<HTMLInputElement>(null);
const [imageRestrictionError, setImageRestrictionError] = useState<string | null>(null);
const imageRestrictionTimerRef = useRef(0);
const [actionMode, setActionMode] = useState<ActionMode>('do');
const {
attachments,
@ -107,6 +111,14 @@ export const SendMessageDialog = ({
const supportsAttachments = isLeadRecipient && !!isTeamAlive;
const canAttach = supportsAttachments && canAddMore;
useEffect(() => {
if (isLeadRecipient) {
setActionMode('delegate');
} else {
setActionMode((prev) => (prev === 'delegate' ? 'do' : prev));
}
}, [isLeadRecipient]);
const [pendingAutoClose, setPendingAutoClose] = useState(false);
// Reset form on open transition (avoid setState in render)
useEffect(() => {
@ -195,7 +207,13 @@ export const SendMessageDialog = ({
const handleSubmit = (): void => {
if (!canSend) return;
onSend(member.trim(), finalText, trimmedText, attachments.length > 0 ? attachments : undefined);
onSend(
member.trim(),
finalText,
trimmedText,
attachments.length > 0 ? attachments : undefined,
actionMode
);
textDraft.clearDraft();
chipDraft.clearChipDraft();
clearAttachments();
@ -428,6 +446,13 @@ export const SendMessageDialog = ({
maxRows={12}
maxLength={MAX_TEXT_LENGTH}
disabled={sending}
cornerActionLeft={
<ActionModeSelector
value={actionMode}
onChange={setActionMode}
showDelegate={isLeadRecipient}
/>
}
cornerAction={
<button
type="button"

View file

@ -276,6 +276,9 @@ describe('TeamProvisioningService post-compact lifecycle', () => {
// Should contain persistent context
expect(text).toContain('Constraints:');
expect(text).toContain('Do NOT call TeamDelete');
expect(text).toContain('TURN ACTION MODE PROTOCOL (HIGHEST PRIORITY FOR EACH USER TURN):');
expect(text).toContain('ASK: Strict read-only conversation mode.');
expect(text).toContain('DELEGATE: Strict orchestration mode for leads.');
expect(text).toContain('cross_team_send');
expect(text).toContain('cross_team_list_targets');
expect(text).toContain('cross_team_get_outbox');