Skip to content

feat: enrich human gates with Markdown rendering and file links#114

Closed
PolyphonyRequiem wants to merge 69 commits intomicrosoft:mainfrom
PolyphonyRequiem:feat/human-gate-enrichment
Closed

feat: enrich human gates with Markdown rendering and file links#114
PolyphonyRequiem wants to merge 69 commits intomicrosoft:mainfrom
PolyphonyRequiem:feat/human-gate-enrichment

Conversation

@PolyphonyRequiem
Copy link
Copy Markdown
Member

Summary

Enriches human gate prompts with proper Markdown rendering and interactive file links in both the terminal CLI and web dashboard.

Terminal (CLI)

  • Gate prompts now render as Rich Markdown -- headings, bold, lists, code blocks, and links display properly instead of raw markup text

Web Dashboard

  • New GET /api/files/{path} endpoint serves local files relative to the workflow root
    • Security: path traversal protection (resolve + relative_to), extension allowlist, 1MB size limit, absolute/UNC/scheme path rejection
  • New FileViewer modal component renders linked files (Markdown for .md, monospace for others)
  • Relative links in gate prompts (e.g. [plan](./plan.md)) open the FileViewer instead of navigating away
  • External URLs continue to open in new browser tabs

Files Changed

  • src/conductor/gates/human.py -- Wrap prompt in RichMarkdown
  • src/conductor/web/server.py -- Add workflow_root param + file API endpoint
  • src/conductor/cli/run.py -- Pass workflow_root to WebDashboard
  • src/conductor/web/frontend/.../FileViewer.tsx -- NEW modal file viewer
  • src/conductor/web/frontend/.../GateDetail.tsx -- Link interception + isRelativeFileLink helper
  • tests/test_web/test_server.py -- 13 new file API security tests
  • tests/test_gates/test_human.py -- 3 new/updated Markdown rendering tests

Testing

All 79 tests pass across both test suites. No regressions.

Daniel Green and others added 30 commits April 16, 2026 12:30
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>
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>
Daniel Green and others added 25 commits April 24, 2026 11:39
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>
@PolyphonyRequiem PolyphonyRequiem force-pushed the feat/human-gate-enrichment branch from b8c59a3 to 612e8ff Compare May 1, 2026 02:23
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants