mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-27 06:58:44 +02:00
feat: add workspace agents for reusable agent definitions across patterns
Introduce a workspace-level agent library that allows users to define agents once and reference them from multiple orchestration patterns. Domain model: - Add WorkspaceAgentDefinition type with name, model, instructions, etc. - Extend PatternAgentDefinition with optional workspaceAgentId and overrides - Add resolution helpers that merge workspace agent base with per-pattern overrides - Extend WorkspaceSettings with agents array and normalize on load IPC & main process: - Add saveWorkspaceAgent/deleteWorkspaceAgent IPC channels and handlers - Resolve workspace agent references in buildEffectivePattern before sending to sidecar (no C# changes needed) Settings UI: - Add 'Agents' tab under Orchestration in the Settings panel - Create WorkspaceAgentEditor component using ToolingEditorShell - Show usage count (which patterns reference each agent) Pattern editor integration: - Add agent picker dropdown: 'New inline agent' or 'From library' - Show linked badge (chain icon) on referenced agent graph nodes - Show linked workspace agent banner in the inspector - Add 'Save to Agent Library' action to promote inline agents - Add 'Unlink' action to convert referenced agents back to inline Tests: - Add unit tests for resolution helpers (resolvePatternAgent, resolvePatternAgents, findWorkspaceAgentUsages, normalize) - Update existing tooling test for new agents field Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
import { Handle, Position, type NodeProps } from '@xyflow/react';
|
||||
import { CircleUser, Shuffle, Layers, Radio, Bot } from 'lucide-react';
|
||||
import { CircleUser, Link2, Shuffle, Layers, Radio, Bot } from 'lucide-react';
|
||||
|
||||
import type { GraphNodeData } from '@renderer/lib/patternGraph';
|
||||
import type { PatternGraphNodeKind } from '@shared/domain/pattern';
|
||||
@@ -56,6 +56,11 @@ function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: b
|
||||
SYS
|
||||
</span>
|
||||
)}
|
||||
{data.isLinked && (
|
||||
<span className="ml-1 flex size-4 items-center justify-center rounded bg-[var(--color-accent)]/15" title="Linked workspace agent">
|
||||
<Link2 className="size-2.5 text-[var(--color-accent)]" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bot, ChevronDown, ChevronUp, CircleUser, Layers, Radio, Shuffle, Trash2 } from 'lucide-react';
|
||||
import { Bot, ChevronDown, ChevronUp, CircleUser, Layers, Library, Link2, Radio, Shuffle, Trash2, Unlink } from 'lucide-react';
|
||||
|
||||
import {
|
||||
findModel,
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
PatternGraph,
|
||||
PatternGraphNodeKind,
|
||||
} from '@shared/domain/pattern';
|
||||
import type { WorkspaceAgentDefinition } from '@shared/domain/workspaceAgent';
|
||||
import {
|
||||
canMoveSequential,
|
||||
findAgentForNode,
|
||||
@@ -25,8 +26,11 @@ interface PatternGraphInspectorProps {
|
||||
graph: PatternGraph;
|
||||
mode: OrchestrationMode;
|
||||
selectedNodeId: string | null;
|
||||
workspaceAgents: WorkspaceAgentDefinition[];
|
||||
onAgentChange: (agentId: string, patch: Partial<PatternAgentDefinition>) => void;
|
||||
onAgentRemove: (agentId: string) => void;
|
||||
onAgentPromote: (agentId: string) => void;
|
||||
onAgentUnlink: (agentId: string) => void;
|
||||
onGraphChange: (graph: PatternGraph) => void;
|
||||
}
|
||||
|
||||
@@ -118,8 +122,11 @@ function AgentNodeInspector({
|
||||
mode,
|
||||
graph,
|
||||
nodeId,
|
||||
workspaceAgents,
|
||||
onAgentChange,
|
||||
onAgentRemove,
|
||||
onAgentPromote,
|
||||
onAgentUnlink,
|
||||
onGraphChange,
|
||||
}: {
|
||||
agent: PatternAgentDefinition;
|
||||
@@ -127,17 +134,45 @@ function AgentNodeInspector({
|
||||
mode: OrchestrationMode;
|
||||
graph: PatternGraph;
|
||||
nodeId: string;
|
||||
workspaceAgents: WorkspaceAgentDefinition[];
|
||||
onAgentChange: (agentId: string, patch: Partial<PatternAgentDefinition>) => void;
|
||||
onAgentRemove: (agentId: string) => void;
|
||||
onAgentPromote: (agentId: string) => void;
|
||||
onAgentUnlink: (agentId: string) => void;
|
||||
onGraphChange: (graph: PatternGraph) => void;
|
||||
}) {
|
||||
const model = findModel(agent.model, availableModels);
|
||||
const showReorder = mode === 'sequential' || mode === 'single' || mode === 'magentic';
|
||||
const canUp = showReorder && canMoveSequential(graph, nodeId, 'up');
|
||||
const canDown = showReorder && canMoveSequential(graph, nodeId, 'down');
|
||||
const isLinked = Boolean(agent.workspaceAgentId);
|
||||
const linkedWorkspaceAgent = isLinked
|
||||
? workspaceAgents.find((wa) => wa.id === agent.workspaceAgentId)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Linked workspace agent banner */}
|
||||
{isLinked && (
|
||||
<div className="flex items-center justify-between rounded-lg border border-[var(--color-accent)]/20 bg-[var(--color-accent)]/5 px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link2 className="size-3.5 text-[var(--color-accent)]" />
|
||||
<span className="text-[11px] text-[var(--color-text-secondary)]">
|
||||
Linked to <strong className="text-[var(--color-text-primary)]">{linkedWorkspaceAgent?.name ?? 'unknown agent'}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center gap-1 rounded px-2 py-1 text-[11px] text-[var(--color-text-muted)] transition hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-secondary)]"
|
||||
onClick={() => onAgentUnlink(agent.id)}
|
||||
title="Unlink from workspace agent (convert to inline)"
|
||||
type="button"
|
||||
>
|
||||
<Unlink className="size-3" />
|
||||
Unlink
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex size-8 items-center justify-center rounded-lg bg-[var(--color-surface-2)]">
|
||||
@@ -218,6 +253,19 @@ function AgentNodeInspector({
|
||||
placeholder="System prompt for this agent..."
|
||||
value={agent.instructions}
|
||||
/>
|
||||
|
||||
{/* Promote / Unlink action */}
|
||||
{!isLinked && (
|
||||
<button
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-[var(--color-border)] px-3 py-2 text-[12px] text-[var(--color-text-muted)] transition hover:border-[var(--color-accent)]/30 hover:bg-[var(--color-accent)]/5 hover:text-[var(--color-accent)]"
|
||||
onClick={() => onAgentPromote(agent.id)}
|
||||
title="Save this agent to the workspace library for reuse across patterns"
|
||||
type="button"
|
||||
>
|
||||
<Library className="size-3.5" />
|
||||
Save to Agent Library
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -228,8 +276,11 @@ export function PatternGraphInspector({
|
||||
graph,
|
||||
mode,
|
||||
selectedNodeId,
|
||||
workspaceAgents,
|
||||
onAgentChange,
|
||||
onAgentRemove,
|
||||
onAgentPromote,
|
||||
onAgentUnlink,
|
||||
onGraphChange,
|
||||
}: PatternGraphInspectorProps) {
|
||||
if (!selectedNodeId) {
|
||||
@@ -268,8 +319,11 @@ export function PatternGraphInspector({
|
||||
mode={mode}
|
||||
graph={graph}
|
||||
nodeId={selectedNodeId}
|
||||
workspaceAgents={workspaceAgents}
|
||||
onAgentChange={onAgentChange}
|
||||
onAgentRemove={onAgentRemove}
|
||||
onAgentPromote={onAgentPromote}
|
||||
onAgentUnlink={onAgentUnlink}
|
||||
onGraphChange={onGraphChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user