feat: enrich human gates with Markdown rendering and file links#114
Closed
PolyphonyRequiem wants to merge 69 commits intomicrosoft:mainfrom
Closed
feat: enrich human gates with Markdown rendering and file links#114PolyphonyRequiem wants to merge 69 commits intomicrosoft:mainfrom
PolyphonyRequiem wants to merge 69 commits intomicrosoft:mainfrom
Conversation
When multiple workflow runs start in the same second (common when
orchestrating parallel runs), time.strftime('%Y%m%d-%H%M%S') produces
identical timestamps, causing all runs to write to the same file.
This corrupts event logs, checkpoint files, and CLI log files by
interleaving events from different runs.
Append a random 8-character hex suffix (via secrets.token_hex(4))
to filenames across all three affected locations:
- EventLogSubscriber (event_log.py)
- CheckpointManager.save_checkpoint (checkpoint.py)
- generate_log_path (cli/run.py)
Filenames change from:
conductor-workflow-20260416-014816.events.jsonl
to:
conductor-workflow-20260416-014816-a3b7c9f1.events.jsonl
Backward compatible: existing tools that glob *.events.jsonl,
*.json, or *.log continue to work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a human gate is presented and a web dashboard has been started (e.g. via --web-bg), _handle_gate_with_web previously checked `self._web_dashboard.has_connections()` and fell back to the CLI-only path if no WebSocket client was currently connected. In practice users almost always open the per-run dashboard *after* seeing the gate-waiting notification, so `has_connections()` is typically False at the moment the gate is presented. Under --web-bg there is no attached stdin, so the CLI task blocks forever on `input()`, and when the user later connects and clicks approve, the `gate_response` WebSocket message is enqueued to `_gate_response_queue` with no coroutine awaiting it. The workflow hangs indefinitely. Fix: only bail to CLI-only when there is no web dashboard at all. Always start both the CLI task and the web-wait task in parallel when a dashboard exists; `wait_for_gate_response` happily awaits an empty queue until the user eventually connects and clicks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a human gate is presented and a web dashboard has been started (e.g. via --web-bg), _handle_gate_with_web previously checked `self._web_dashboard.has_connections()` and fell back to the CLI-only path if no WebSocket client was currently connected. In practice users almost always open the per-run dashboard *after* seeing the gate-waiting notification, so `has_connections()` is typically False at the moment the gate is presented. Under --web-bg there is no attached stdin, so the CLI task blocks forever on `input()`, and when the user later connects and clicks approve, the `gate_response` WebSocket message is enqueued to `_gate_response_queue` with no coroutine awaiting it. The workflow hangs indefinitely. Fix: only bail to CLI-only when there is no web dashboard at all. Always start both the CLI task and the web-wait task in parallel when a dashboard exists; `wait_for_gate_response` happily awaits an empty queue until the user eventually connects and clicks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`wait_for_gate_response` previously re-queued any message whose `agent_name` did not match the expected agent, then slept 10ms and retried. Because `asyncio.Queue` has no deduplication, the same stale message would immediately be dequeued again on the next iteration — producing a tight loop that re-enqueues and re-inspects the same dict forever (cpu-bound, 100 iter/sec per stale message). In practice this never triggered during normal flow because conductor presents one gate at a time and the `has_connections()` short-circuit used to hide the issue. With that short-circuit now gone (PR to always race both tasks), and with any client ever producing a duplicate click, the spin becomes reachable. Fix: since conductor only ever awaits one gate at a time, any non-matching `gate_response` is definitionally stale (a duplicate click from a dashboard that missed the first resolution, or a message for a gate that already completed). Re-queueing can never deliver it — the matching `wait_for_gate_response` call is already gone. Discard stale messages with a warning log so the queue drains cleanly and the next `await .get()` blocks properly on an empty queue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`wait_for_gate_response` previously re-queued any message whose `agent_name` did not match the expected agent, then slept 10ms and retried. Because `asyncio.Queue` has no deduplication, the same stale message would immediately be dequeued again on the next iteration — producing a tight loop that re-enqueues and re-inspects the same dict forever (cpu-bound, 100 iter/sec per stale message). In practice this never triggered during normal flow because conductor presents one gate at a time and the `has_connections()` short-circuit used to hide the issue. With that short-circuit now gone (PR to always race both tasks), and with any client ever producing a duplicate click, the spin becomes reachable. Fix: since conductor only ever awaits one gate at a time, any non-matching `gate_response` is definitionally stale (a duplicate click from a dashboard that missed the first resolution, or a message for a gate that already completed). Re-queueing can never deliver it — the matching `wait_for_gate_response` call is already gone. Discard stale messages with a warning log so the queue drains cleanly and the next `await .get()` blocks properly on an empty queue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds TestWaitForGateResponse with two cases: - Returns the matching message. - Discards stale (non-matching) messages without re-queueing, regression-covering the busy-loop bug fixed in this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds TestWaitForGateResponse with two cases: - Returns the matching message. - Discards stale (non-matching) messages without re-queueing, regression-covering the busy-loop bug fixed in this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rosoft#101) Add input_mapping field to AgentDef for type='workflow' agents. When present, each value is a Jinja2 expression rendered against the parent context to build sub-workflow inputs. When absent, existing behavior (forwarding parent's workflow.input.*) is preserved. - Schema: Add input_mapping to AgentDef with validation for workflow-only - Engine: Render input_mapping templates in _execute_subworkflow() - Tests: Schema validation for all agent types - Experimental workflows: test-input-mapping parent/child pair Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) Remove validator restriction blocking type='workflow' in for_each groups. Wire execute_single_item() to call _execute_subworkflow_with_inputs() for workflow agents, rendering input_mapping with loop variables in scope. - Validator: Remove workflow rejection in for_each validation - Engine: Add workflow branch in execute_single_item(), new helper _execute_subworkflow_with_inputs() for pre-built inputs - Tests: Update test_workflow_in_for_each to validate (not reject) - Experimental workflows: test-for-each-workflow parent/child pair Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…acking (microsoft#103) Remove the circular reference path check that blocked workflows from referencing themselves. The existing MAX_SUBWORKFLOW_DEPTH=10 already prevents infinite recursion. Add optional per-agent max_depth field for tighter author-controlled bounds. - Engine: Remove self-reference path equality check in both _execute_subworkflow() and _execute_subworkflow_with_inputs() - Engine: Add per-agent max_depth enforcement alongside global limit - Schema: Add max_depth field to AgentDef with validation - Tests: Replace circular reference test with depth-limit tests - Experimental: test-recursive.yaml self-referential countdown Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a metadata field to WorkflowDef that allows workflow authors to
attach arbitrary key-value pairs for external tooling. The metadata
is included verbatim in the workflow_started event, enabling
downstream consumers (dashboards, trackers, enrichers) to adapt
behavior without parsing the YAML source.
Example usage in workflow YAML:
workflow:
name: twig-sdlc
metadata:
tracker: ado
project_url: https://dev.azure.com/org/Project
work_item_id_agent: intake
work_item_id_field: epic_id
The field defaults to an empty dict, so existing workflows are
unaffected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add --metadata / -m flag to 'conductor run' that accepts key=value
pairs, merged on top of YAML-declared metadata. This enables callers
to inject dynamic values at invocation time:
conductor run twig-sdlc.yaml --metadata work_item_id=1814
CLI metadata is:
- Parsed separately from --input (different binding path)
- Merged on top of YAML metadata (CLI wins on conflicts)
- Forwarded through --web-bg background process spawning
- Included in the workflow_started event alongside YAML metadata
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7 new tests verifying: - Schema: metadata defaults to empty dict, accepts arbitrary keys, independent from input/context fields - Loader: metadata round-trips through YAML, omission gives empty dict, nested values preserved, metadata and input are separate namespaces All 140 config tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Propagate the event log's random hex suffix as a run_id across all systems: - EventLogSubscriber: expose run_id property (was already generated) - WorkflowEngine: accept run_id + log_file params, include in workflow_started event - PID files: include run_id + log_file fields - Web dashboard: add /api/info endpoint returning run_id, log_file, workflow_name, started_at, metadata This enables the central dashboard to match per-run dashboards to event logs by exact run_id instead of fragile name/timestamp heuristics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a parent workflow calls a sub-workflow via type: workflow, the child engine now inherits the parent's agent outputs in its context. This allows sub-workflow agents to access parent agent state (e.g., task_manager.output, pr_group_manager.output) by declaring them in their input: list, even when input_mapping doesn't cover all fields. Previously, sub-workflow agents could only access workflow.input.* (populated via input_mapping) and their own sibling agents' outputs. Parent agent outputs were lost at the sub-workflow boundary, causing nested sub-workflows to see default values (0, '') instead of the actual parent state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds full subworkflow awareness to the per-run web dashboard: State pollution fix: - Added wf_depth counter to workflow store — only depth-0 workflow_started initializes root context. Inner workflow events are routed to isolated SubworkflowContext objects. - Each subworkflow invocation gets its own nodes/routes/agents maps, keyed by (parentAgent, iteration). Repeated runs of the same subworkflow no longer share state. Subworkflow event handling: - Added TypeScript types for subworkflow_started, subworkflow_completed, subworkflow_failed events (mirrors engine emit). - Event handlers create/update child contexts and track the active context path for routing subsequent events. Breadcrumb navigation: - New BreadcrumbBar component shows the context stack above the graph (e.g., Root > twig-sdlc-planning > plan-issue). - Click any breadcrumb to navigate to that context level. - Double-click a workflow agent node in the graph to dive into its subworkflow context. - Graph rebuilds automatically when context changes. Context stack architecture: - SubworkflowContext[] tree structure mirrors workflow nesting. - activeContextPath tracks where live events are routed. - viewContextPath tracks what the user is viewing (independent). - getViewedContext() returns the correct nodes/routes for rendering. - All event handlers use activeTarget() helper to route to the correct context's nodes/groupProgress. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 4-5 of breadcrumb navigation feature: WorkflowNode component: - New node type for type:'workflow' agents with dashed border and Layers icon (visually distinct from regular agent nodes). - Shows child workflow name, elapsed time, and a chevron indicator when a SubworkflowContext exists. - Double-click to navigate into the subworkflow graph. SubworkflowDetail panel: - New detail component shown when a workflow agent is selected. - Lists all subworkflow runs for that agent with status, agent count, and cost summary. - Click any run to navigate into its context. Context-aware rendering: - All graph node components (AgentNode, ScriptNode, GateNode, GroupNode, AnimatedEdge) now read from getViewedContext().nodes instead of root state.nodes — ensures correct status display when viewing child contexts. - DetailPanel reads from viewed context for node lookup. - GroupDetail reads groupProgress from viewed context. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to install-combined
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
getViewedContext() creates a new object on every call, causing infinite re-render loops (React error #185) when used inside Zustand selectors. New hooks in use-viewed-context.ts use useMemo with stable state references: - useViewedNodes() — nodes map for current context - useViewedGroupProgress() — group progress for current context - useViewedHighlightedEdges() — edge highlights - useViewedSubworkflowContexts() — child contexts - useViewedGraphData() — full graph data for WorkflowGraph All graph components and detail panels updated to use these hooks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # src/conductor/web/static/index.html
When a parent workflow calls a sub-workflow via type: workflow, the child engine now inherits the parent's agent outputs in its context. This allows sub-workflow agents to access parent agent state (e.g., task_manager.output, pr_group_manager.output) by declaring them in their input: list, even when input_mapping doesn't cover all fields. Previously, sub-workflow agents could only access workflow.input.* (populated via input_mapping) and their own sibling agents' outputs. Parent agent outputs were lost at the sub-workflow boundary, causing nested sub-workflows to see default values (0, '') instead of the actual parent state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Auto-inject runtime diagnostics (PID, platform, Python version, cwd, conductor version, started_at, run_id, log_file, bg_mode) into the workflow_started event. Dashboard port/URL included when --web is active; parent_pid included in --web-bg mode. System metadata flows through: - JSONL event log (via EventLogSubscriber) - Web dashboard /api/info endpoint - Checkpoint files (for resume context) PID files are intentionally left unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a web-based drag-and-drop visual designer for creating and editing conductor workflow YAML files. Launch with 'conductor designer' CLI command. Backend: - FastAPI server with REST endpoints for workflow CRUD, validation, export/import - WorkflowConfig <-> JSON bridge reusing existing Pydantic validation - YAML export via ruamel.yaml with clean formatting - CLI integration: conductor designer [workflow.yaml] [--port] [--no-open] Frontend (React + @xyflow/react + Zustand + TailwindCSS): - WorkflowConfig as source of truth (ReactFlow nodes/edges derived) - 8 node types: Agent, Gate, Script, Workflow, Parallel, ForEach, Start, End - Property panel with type-specific editors + workflow settings - Jinja2-aware prompt editor with syntax highlighting - YAML preview, validation panel, undo/redo (Ctrl+Z/Ctrl+Shift+Z) - Import/export YAML, save to disk, keyboard shortcuts (Ctrl+S) - Dagre auto-layout for DAG visualization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agents with multiple routes to the same target (e.g., researcher has two routes to synthesizer with different conditions) would generate duplicate edge IDs, causing React key collisions. Fixed by appending route index to edge IDs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s NameError Includes both fixes: - workflow.input always available for script/sub-workflow templates in explicit mode - _execute_subworkflow_with_inputs context parameter fix (for-each + sub-workflow) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… variables
Adds three new template variables available in all agent contexts:
- {{ workflow.dir }} - absolute path to the workflow YAML's directory
- {{ workflow.file }} - absolute path to the workflow YAML file
- {{ workflow.name }} - workflow name from the YAML config
These enable script agents to resolve co-located scripts relative to
the workflow file rather than CWD, which is critical for registry-based
workflows where scripts live alongside the YAML in the registry directory.
Example:
args:
- -File
- {{ workflow.dir }}/scripts/detect-state.ps1
Available in all context modes (accumulate, last_only, explicit).
Empty strings are omitted from context to avoid polluting templates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a script agent's stdout is valid JSON, the parsed fields are now
merged into the output dict alongside stdout/stderr/exit_code. This
makes parsed fields accessible in route conditions and downstream
templates as output.field_name — matching LLM structured output behavior.
Example: a script outputting JSON like {"route": "planning"} now allows
route conditions like 'when: route == "planning"' instead of requiring
opaque exit codes.
Backward compatible: stdout/stderr/exit_code still present. Non-JSON
stdout is unchanged. Only JSON object stdout gets merged fields.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ults
Optional workflow inputs without an explicit `default:` previously
defaulted to Python None, which renders as "None" in templates and
isn't caught by Jinja's `| default()` filter without the boolean flag.
Now uses type-appropriate zero values: "" for string, 0 for number,
false for boolean, [] for array, {} for object. This ensures templates
render cleanly without requiring `| default()` guards or `if X else Y`
workarounds.
Explicit `default:` values in the schema are still honored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enhanced `conductor validate` to scan Jinja2 templates in agent prompts,
script args, input_mapping, and workflow output for reference errors:
Level 1 — Template reference resolution:
- {{ X.output.Y }} where X is not a valid agent name → error
- {{ workflow.input.X }} where X is not a declared input → error
- In explicit mode, LLM agents referencing agent outputs not in their
input: list → warning
Also wires semantic validation (validate_workflow_config) into the CLI
`conductor validate` command, which previously only ran Pydantic schema
validation.
Catches errors like: stale agent name references after renames, missing
workflow input declarations, and undeclared dependencies in explicit mode
— all before runtime, at zero cost.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 2-part form '- workflow.input' in an agent's input list now injects all declared workflow inputs, matching the documented behavior. Previously it silently injected nothing, requiring the verbose 3-part form '- workflow.input.<param>' for each parameter. - Add elif branch in _add_explicit_input() for len(parts)==2 - Update INPUT_REF_PATTERN regex to accept workflow.input without param - Update validator error message to mention both forms - Add tests for 2-part form (all inputs, merge with individual, optional) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… nodes
Parse ?agent={name} and ?subworkflow={name} query params on initial load
to auto-select and center the matching node in the workflow graph. This
enables the meta-dashboard (conductor-dashboard) to generate clickable
breadcrumb links that open the conductor UI focused on a specific node.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update useDeepLink hook to: - Parse slash-separated subworkflow paths (e.g., ?subworkflow=planning/design) for navigating multiple levels deep into nested subworkflows - Support combined ?subworkflow=X&agent=Y to select an agent within a subworkflow context - Remove dependency on subworkflowContexts selector (array mutation doesn't trigger re-renders); rely on late-joiner replay instead Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrote useDeepLink to fix timing issues that prevented navigation: - Use zustand.subscribe() instead of useEffect + selector reactivity. The old approach relied on subworkflowContexts selector changes, but the store mutates the array in-place during processEvent, so the selector never detected changes. - Resolve the full subworkflow path in one shot via index walking instead of calling navigateIntoSubworkflow() in a loop. - Set viewContextPath directly via setState instead of relying on action functions that might see stale state. - Add error banner when deep-link target is invalid: shows the error message and a link back to the root dashboard. - Validate agent exists in the target context's agent list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement three related improvements to per-agent MCP tool scoping:
1. Sub-workflow MCP server merging (Issue 1):
- Extract build_mcp_servers to shared mcp/utils.py
- Add ProviderRegistry.merge_mcp_servers() for additive merging
- Child sub-workflows can now declare their own mcp_servers
- Parent definitions win on name collision
- Child registries use child config and are properly cleaned up
2. Copilot provider tool filtering (Issue 2):
- Add filter_mcp_server_configs() to filter SDK server dicts
- Apply agent tools filter in _execute_sdk_call() before create_session()
- tools=None -> all servers, tools=[] -> no servers, tools=[list] -> matched
3. Tool name matching convention (Issue 3):
- Create shared mcp/tool_filter.py with three match modes:
a. Prefixed exact: server__tool matches specific server+tool
b. Server name: includes all tools from that server
c. Unprefixed: matches original tool name across servers
- Claude provider now uses shared filter (supports unprefixed names)
- Both providers use consistent matching logic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When workflows use mcp_servers without a top-level tools: whitelist, both resolve_agent_tools() and validate_workflow_config() incorrectly rejected agent tool declarations against an empty set. Changes: - resolve_agent_tools: return None (unconstrained) when workflow_tools is empty and agent_tools is None, preserving MCP 'all tools' semantic. Pass through agent tool lists when no workflow-level restriction exists. - validator: skip _validate_tool_references when config.tools is empty and MCP servers are configured (tools are runtime-discovered). - Updated tests to match new semantics and added MCP validation tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Designer and team agent features remain on their respective feature branches. This commit removes: - src/conductor/designer/ (visual workflow editor) - designer CLI command from app.py - team-specific validation checks from validator.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GateDetail's PromptMarkdown component was missing table/th/td handlers, causing markdown tables to render as raw pipe-separated text. Added the same table component overrides already used in FileViewer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Linkify and the web dashboard file viewer now use Path.cwd() instead of the workflow YAML's parent directory. File references in gate prompts are typically relative to where the user launched conductor, not where the YAML lives. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the FileViewer modal with vscode://file/ URI links for relative file paths in gate prompts. This opens files directly in the user's editor rather than a read-only modal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two additional PromptMarkdown call sites still passed the removed onFileClick/setViewingFile prop, causing a ReferenceError that blanked the page when navigating to a human gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ReactMarkdown requires the remark-gfm plugin to parse GitHub Flavored Markdown extensions like tables. Without it, pipe-delimited tables render as plain text inside paragraph elements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace vscode://file/ links with a server-side POST /api/open-file endpoint that opens files using the OS default application (os.startfile on Windows, open on macOS, xdg-open on Linux). Also emit workflow_root (cwd) in the workflow_started event so the frontend has access to the project root path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b8c59a3 to
612e8ff
Compare
jrob5756
pushed a commit
that referenced
this pull request
May 4, 2026
* feat(dialog): agent dialog mode with web dashboard support Add dialog mode for agents enabling multi-turn conversational interactions within workflows. Includes dialog gate implementation, condition evaluation, provider support (Copilot + Claude), web dashboard UI components, and comprehensive tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(dialog): address PR #130 review feedback Real bugs: - linkify: use Path.is_relative_to instead of str startswith for path containment (true path-aware boundary, not string-prefix) - dialog (CLI + web): pop user turn from provider history on exception so the next iteration doesn't append a second consecutive user turn - dialog (web): drop duplicate history.append in READY-decline branch (loop top already appends the approval as the next user turn) - dialog: treat [READY_TO_CONTINUE] as a terminal token only (rstrip().endswith) so user-typed marker mid-message can't trick the agent into ending the dialog; strip the marker before storing in transcript - dialog_evaluator: only strip last code-fence line if it's actually a closing fence (unterminated fences no longer drop the JSON line) - web/server: filter wait_for_dialog_message by dialog_id as well as agent_name so messages can't cross dialog boundaries - workflow-store: reset activeDialog/dialogEngaged in setReplayPosition too (was only in replayState/setReplayMode) Suggestions adopted: - evaluator: append \n…[truncated] marker to truncated outputs so the evaluator (and future inspection) knows context was cut - workflow-store sendDialogMessage: drop optimistic dialog_awaiting_response set; the dialog_message event handler now flips it on user/agent role echo (single source of truth) - DialogDetail isRelativeFileLink: defense in depth — reject .. segments and Windows drive letters before the eventual /api/open-file lands Tests: - web dialog flow tests (engagement decline, happy path, exception recovery, READY-decline-no-duplicate-history, READY-approval) - READY marker terminal-only tests - CLI exception history-pop test - evaluator truncation marker tests - evaluator unterminated/terminated code-fence tests - Claude provider execute_dialog_turn parity tests (empty/multi-turn history, model override, error wrapping) - Copilot provider execute_dialog_turn parity tests (prompt construction via mocked session, model override, error wrapping) - end-to-end integration test: dialog triggers → user converses → agent re-executes with transcript in guidance section Note: TS source changes only — frontend static assets need a CI rebuild. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(dialog): drop dead /api/open-file call from dialog markdown The DialogDetail component intercepted relative-file links in agent markdown and POSTed to /api/open-file, but that endpoint was meant to land in #114 (closed). Clicks silently 404 and leave users staring at links that look interactive but do nothing. Drop the link rewriting (and the now-unused isRelativeFileLink guard) and render every link as a plain anchor with target=_blank + rel=noopener. External URLs still open in a new tab; relative paths become honest broken links instead of fake interactive ones. If/when the file-open endpoint comes back in a future PR, the rewriting can return alongside the matching server-side validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Daniel Green <dangreen@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enriches human gate prompts with proper Markdown rendering and interactive file links in both the terminal CLI and web dashboard.
Terminal (CLI)
Web Dashboard
GET /api/files/{path}endpoint serves local files relative to the workflow rootFileViewermodal component renders linked files (Markdown for .md, monospace for others)[plan](./plan.md)) open the FileViewer instead of navigating awayFiles Changed
src/conductor/gates/human.py-- Wrap prompt in RichMarkdownsrc/conductor/web/server.py-- Add workflow_root param + file API endpointsrc/conductor/cli/run.py-- Pass workflow_root to WebDashboardsrc/conductor/web/frontend/.../FileViewer.tsx-- NEW modal file viewersrc/conductor/web/frontend/.../GateDetail.tsx-- Link interception + isRelativeFileLink helpertests/test_web/test_server.py-- 13 new file API security teststests/test_gates/test_human.py-- 3 new/updated Markdown rendering testsTesting
All 79 tests pass across both test suites. No regressions.