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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.337"
VERSION = "0.261.338"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
33 changes: 32 additions & 1 deletion application/v2_ui/src/components/chat/MessageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BookmarkPlus,
BookOpen,
Brain,
CalendarClock,
ChevronLeft,
ChevronRight,
Clipboard,
Expand Down Expand Up @@ -70,20 +71,29 @@
children,
active = false,
disabled = false,
expanded,
controls,
buttonRef,
}: {
label: string;
onClick: () => void;
children: React.ReactNode;
active?: boolean;
disabled?: boolean;
expanded?: boolean;
controls?: string;
buttonRef?: React.Ref<HTMLButtonElement>;
}) {
return (
<button
ref={buttonRef}
type="button"
onClick={onClick}
disabled={disabled}
title={label}
aria-label={label}
aria-expanded={expanded}
aria-controls={controls}
className={clsx(
'rounded-md p-1.5 transition-colors disabled:cursor-not-allowed disabled:opacity-40',
active
Expand Down Expand Up @@ -407,12 +417,19 @@
inspector,
onInspect,
alignRight,
planReplay,
}: {
message: ChatMessage;
onEdit?: () => void;
/** Section currently open below the message, or null when the panel is closed. */
inspector?: InspectorSection | null;
onInspect?: (section: InspectorSection | null) => void;
planReplay?: {
expanded: boolean;
panelId: string;
buttonRef: React.Ref<HTMLButtonElement>;
onToggle: () => void;
};
/**
* Which side the message bubble sits on.
*
Expand Down Expand Up @@ -524,10 +541,12 @@

return (
<div
role="group"

Check warning on line 544 in application/v2_ui/src/components/chat/MessageActions.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
aria-label="Message actions"
className={clsx(
// Groups are separated by a wider gap than the buttons within them, so the
// row reads as related sets rather than one long undifferentiated strip.
'mt-1 flex items-center gap-3',
'mt-1 flex flex-wrap items-center gap-x-3 gap-y-1',
(alignRight ?? isUser) ? 'justify-end' : 'justify-start',
)}
>
Expand Down Expand Up @@ -600,6 +619,18 @@
)}

{!isUser && ttsEnabled && <SpeakButton message={message} />}
{!isUser && planReplay && (
<IconButton
label="Repeat on a schedule"
active={planReplay.expanded}
expanded={planReplay.expanded}
controls={planReplay.panelId}
buttonRef={planReplay.buttonRef}
onClick={planReplay.onToggle}
>
<CalendarClock size={15} aria-hidden="true" />

Check warning on line 631 in application/v2_ui/src/components/chat/MessageActions.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Moderate - Changed line contains obfuscation, dynamic loading, or hidden payload marker. Recommendation%3A Confirm the changed code is not hiding behavior, decoding payloads, or bypassing normal review.
</IconButton>
)}
</div>

{!isUser && feedbackEnabled && (
Expand Down
47 changes: 37 additions & 10 deletions application/v2_ui/src/components/chat/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { ReasoningAdjustmentNotice } from './ReasoningAdjustmentNotice';
// Renders the message thread, the in-flight streaming bubble and the reasoning panel.

import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { clsx } from 'clsx';
import {
BookOpen,
Expand Down Expand Up @@ -821,6 +821,9 @@
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(message.content);
const [inspector, setInspector] = useState<InspectorSection | null>(null);
const replayPanelId = useId();
const replayButtonRef = useRef<HTMLButtonElement>(null);
const [expandedReplayRun, setExpandedReplayRun] = useState<string | null>(null);
const bodyRef = useRef<HTMLDivElement>(null);
const editMessage = useChatStore((state) => state.editMessage);
const applyMask = useChatStore((state) => state.applyMask);
Expand Down Expand Up @@ -905,6 +908,18 @@
const markAnalysisUnavailable = useCallback(() => setUnavailableAnalysisKey(analysisKey), [analysisKey]);
const analysisUnavailable = hasSavedAnalysis && (!savedAnalysis || savedAnalysis.available === false ||
savedAnalysis.conversation_id !== activeConversationId || unavailableAnalysisKey === analysisKey);
const canRepeatPlan = message.role === 'assistant' && !analysisUnavailable

Check warning on line 911 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
&& planReplayAvailable && Boolean(orchestration.run_id) && orchestration.outcome === 'completed'
&& !masks.fullyMasked && masks.ranges.length === 0 && personalConversation

Check warning on line 913 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.

Check warning on line 913 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
&& message.conversation_id === activeConversationId

Check warning on line 914 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
&& !orchestrationProposedWorkflow(message.metadata?.orchestration)
&& !orchestrationStartedWorkflow(message.metadata?.orchestration)
&& !orchestrationHandedOffWorkflow(message.metadata?.orchestration);
const replayRunKey = `${message.conversation_id}:${orchestration.run_id}`;

Check warning on line 918 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
const replayExpanded = canRepeatPlan && expandedReplayRun === replayRunKey;
useEffect(() => {
setExpandedReplayRun(null);
}, [canRepeatPlan, replayRunKey]);
const artifactsReplaceText = !hasSavedAnalysis && suppressesAssistantText(artifacts);
useEffect(() => {
const store = useChatStore.getState();
Expand Down Expand Up @@ -1206,14 +1221,6 @@
<WorkflowHandoffCards conversationId={message.conversation_id} runId={orchestration.run_id}
liveRunStatus={liveRunStatus} />
) : null}
{planReplayAvailable && orchestration.run_id && orchestration.outcome === 'completed'
&& masks.ranges.length === 0 && personalConversation
&& message.conversation_id === activeConversationId
&& !orchestrationProposedWorkflow(message.metadata?.orchestration)
&& !orchestrationStartedWorkflow(message.metadata?.orchestration)
&& !orchestrationHandedOffWorkflow(message.metadata?.orchestration) ? (
<PlanReplaySaveCard conversationId={message.conversation_id} runId={orchestration.run_id} />
) : null}
{/* Inside the bubble, because a generated file belongs to the reply
that produced it rather than sitting loose in the thread. */}
{hasSavedAnalysis && (masks.ranges.length > 0 ? (
Expand Down Expand Up @@ -1265,7 +1272,7 @@
<div
className={clsx(
'transition-opacity group-hover/message:opacity-100 focus-within:opacity-100',
inspector ? 'opacity-100' : 'opacity-0',
inspector || replayExpanded ? 'opacity-100' : 'opacity-0',
)}
>
{!analysisUnavailable && <MessageActions
Expand All @@ -1274,9 +1281,29 @@
inspector={inspector}
onInspect={setInspector}
alignRight={alignRight}
planReplay={canRepeatPlan ? {
expanded: replayExpanded,
panelId: replayPanelId,
buttonRef: replayButtonRef,
onToggle: () => setExpandedReplayRun(replayExpanded ? null : replayRunKey),
} : undefined}
/>}
</div>

{canRepeatPlan && orchestration.run_id && (
<PlanReplaySaveCard
key={replayRunKey}
conversationId={message.conversation_id}

Check warning on line 1296 in application/v2_ui/src/components/chat/MessageList.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
runId={orchestration.run_id}
panelId={replayPanelId}
expanded={replayExpanded}
onCollapse={() => {
setExpandedReplayRun(null);
replayButtonRef.current?.focus();
}}
/>
)}

{inspector && (
<MessageInspector
message={message}
Expand Down
28 changes: 13 additions & 15 deletions application/v2_ui/src/components/chat/PlanReplaySaveCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// PlanReplaySaveCard.tsx
// Lets the requester save one completed chat orchestration plan as a read-only personal workflow.

import { useCallback, useId, useState, type ReactNode } from 'react';
import { useCallback, useEffect, useState, type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { CalendarClock, ChevronDown, RefreshCw } from 'lucide-react';
import { GlassButton } from '../ui/primitives';
Expand Down Expand Up @@ -65,12 +65,16 @@ function defaultName(request: string): string {
export function PlanReplaySaveCard({
runId,
conversationId,
panelId,
expanded,
onCollapse,
}: {
runId: string;
conversationId: string;
panelId: string;
expanded: boolean;
onCollapse: () => void;
}) {
const panelId = useId();
const [expanded, setExpanded] = useState(false);
const [loading, setLoading] = useState(false);
const [preview, setPreview] = useState<PlanReplayPreview | null>(null);
const [options, setOptions] = useState<WorkflowEditorOptions | null>(null);
Expand All @@ -85,7 +89,6 @@ export function PlanReplaySaveCard({
const [saved, setSaved] = useState<{ id: string; name: string; created: boolean; enabled: boolean } | null>(null);

const load = useCallback(async () => {
setExpanded(true);
setLoading(true);
setError('');
setErrorCode('');
Expand All @@ -108,6 +111,10 @@ export function PlanReplaySaveCard({
}
}, [conversationId, runId]);

useEffect(() => {
if (expanded) void load();
}, [expanded, load]);

const save = async () => {
if (!preview || saving) return;
setSaving(true);
Expand Down Expand Up @@ -139,16 +146,7 @@ export function PlanReplaySaveCard({
}
};

if (!expanded) {
return (
<div className="mt-3">
<GlassButton type="button" size="sm" variant="subtle" aria-expanded="false"
aria-controls={panelId} onClick={() => void load()}>
<CalendarClock size={14} aria-hidden="true" /> Repeat on a schedule
</GlassButton>
</div>
);
}
if (!expanded) return null;

return (
<section id={panelId} aria-label="Save chat plan as workflow"
Expand All @@ -163,7 +161,7 @@ export function PlanReplaySaveCard({
</p>
</div>
<GlassButton type="button" size="sm" variant="ghost" aria-expanded="true"
aria-controls={panelId} onClick={() => setExpanded(false)}>
aria-controls={panelId} onClick={onCollapse}>
<ChevronDown size={14} aria-hidden="true" /> Collapse
</GlassButton>
</div>
Expand Down
20 changes: 15 additions & 5 deletions docs/explanation/features/WORKFLOW_PLAN_REPLAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

Shared schedule policy implemented in version: **0.261.317**.

Calendar message action implemented in version: **0.261.338**.

Related issue: part of #1550, under #1543. Builds on
[Chat orchestration workflow proposals](CHAT_ORCHESTRATION_WORKFLOW_PROPOSALS.md),
[Chat orchestration workflow runs](CHAT_ORCHESTRATION_WORKFLOW_RUNS.md),
Expand Down Expand Up @@ -289,8 +291,15 @@

## Creation path

Creation path **B**: a **Repeat on a schedule** button under a finished
orchestrated answer opens a **Save this chat plan** card.
Creation path **B**: a **Repeat on a schedule** calendar icon in a finished
orchestrated answer's message action bar opens a **Save this chat plan** card.
The icon shares the existing Copy/Retry controls' hover and keyboard-focus
reveal behavior, with an accessible name, tooltip, and expanded-state

Check warning on line 297 in docs/explanation/features/WORKFLOW_PLAN_REPLAY.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
announcement. There is no standalone scheduling button inside the answer.
The card expands below the message, outside the fading action bar, and stays
visible until collapsed. Collapse restores focus to the calendar icon; the
icon can also close an open card. Moving the control does not change the
workflow defaults or create anything automatically.

Why B rather than asking the planner: the plan being saved is one the user
already saw run, so the card can show exactly that plan. The planner never
Expand Down Expand Up @@ -444,7 +453,7 @@

### When the setting is turned off or a capability is removed

Nothing is deleted or rewritten. The **Repeat on a schedule** button
Nothing is deleted or rewritten. The **Repeat on a schedule** calendar icon
disappears, the routes refuse with `replay_disabled`, and every existing replay
workflow fails its next run with the fixed `replay_disabled` reason before
running any step. With the default alert, the creator sees that reason in the
Expand Down Expand Up @@ -477,7 +486,7 @@
| `functions_orchestration_registry.py` | The setting's name. |
| `functions_saved_analysis.py` | Redaction withholds `plan_replay`. |
| `static/js/workspace/workspace_workflows.js` | The Classic page routes replay workflows to V2. |
| `v2_ui/src/components/chat/PlanReplaySaveCard.tsx`, `MessageList.tsx` | The card and when it shows. |
| `v2_ui/src/components/chat/PlanReplaySaveCard.tsx`, `MessageList.tsx`, `MessageActions.tsx` | The on-demand card, calendar message action, and shared availability conditions. |
| `v2_ui/src/lib/workflowPlanReplay.ts` | API client, parsing and disclosure text. |
| `v2_ui/src/components/workflows/WorkflowEditorDialog.tsx`, `WorkflowRunHistory.tsx`, `lib/workflowEditor.ts`, `lib/workflowEditorSections.ts` | Read-only editor section and the run inspector block. |

Expand All @@ -492,7 +501,8 @@
| `functional_tests/test_workflow_plan_replay_boundaries.py` | Import order and boundaries, scheduler exclusion. |
| `functional_tests/route_tests/test_route_blueprint_policy_inventory.py`, `test_route_unauthenticated_policy_contract.py` | Route policy for the two new routes. |
| `functional_tests/test_v2_workflow_plan_replay_client.mjs` | V2 client parsing, disclosure text, time-zone fallback, editor payloads, and the alert notice naming the server's rule. |
| `ui_tests/test_v2_workflow_plan_replay_card.py` | The card, refusals, the alert notice and save in a browser, with hostile step titles and workflow names. |
| `functional_tests/test_v2_message_actions.py` | Calendar action wiring, controlled panel, and lazy-preview contract alongside existing message actions. |
| `ui_tests/test_v2_workflow_plan_replay_card.py` | Calendar placement, hover/keyboard reveal, desktop/mobile layout, eligibility, collapse/reopen, run/masking changes, errors, refusals, the alert notice and save in a browser, with hostile step titles and workflow names. |

Check warning on line 505 in docs/explanation/features/WORKFLOW_PLAN_REPLAY.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
| `ui_tests/test_workflow_classic_advanced_guard.py` | The Classic page routes replay workflows to V2. |

## Limits and follow-ups
Expand Down
Loading
Loading