mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-30 08:28:48 +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:
@@ -36,6 +36,11 @@ import {
|
||||
type ReasoningEffort,
|
||||
validatePatternDefinition,
|
||||
} from '@shared/domain/pattern';
|
||||
import {
|
||||
normalizeWorkspaceAgentDefinition,
|
||||
resolvePatternAgents,
|
||||
type WorkspaceAgentDefinition,
|
||||
} from '@shared/domain/workspaceAgent';
|
||||
import {
|
||||
applyDiscoveredMcpServerStatus,
|
||||
listAcceptedDiscoveredMcpServers,
|
||||
@@ -813,6 +818,39 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async saveWorkspaceAgent(agent: WorkspaceAgentDefinition): Promise<WorkspaceState> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
const agents = workspace.settings.agents ?? [];
|
||||
const existingIndex = agents.findIndex((current) => current.id === agent.id);
|
||||
const timestamp = nowIso();
|
||||
const candidate = normalizeWorkspaceAgentDefinition({
|
||||
...agent,
|
||||
createdAt: existingIndex >= 0 ? agents[existingIndex].createdAt : timestamp,
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
|
||||
if (!candidate.name) {
|
||||
throw new Error('Workspace agent name is required.');
|
||||
}
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
agents[existingIndex] = candidate;
|
||||
} else {
|
||||
agents.push(candidate);
|
||||
}
|
||||
|
||||
workspace.settings.agents = agents;
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async deleteWorkspaceAgent(agentId: string): Promise<WorkspaceState> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
workspace.settings.agents = (workspace.settings.agents ?? []).filter(
|
||||
(agent) => agent.id !== agentId,
|
||||
);
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async createSession(projectId: string, patternId: string): Promise<WorkspaceState> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
const project = this.requireProject(workspace, projectId);
|
||||
@@ -955,7 +993,7 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
const project = this.requireProject(workspace, session.projectId);
|
||||
const pattern = this.requirePattern(workspace, session.patternId);
|
||||
const effectivePattern = this.applyProjectCustomizationToPattern(
|
||||
await this.buildEffectivePattern(pattern, session),
|
||||
await this.buildEffectivePattern(pattern, session, workspace.settings.agents ?? []),
|
||||
project,
|
||||
);
|
||||
const projectInstructions = resolveProjectInstructionsContent(project.customization);
|
||||
@@ -1014,7 +1052,7 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
const project = this.requireProject(workspace, session.projectId);
|
||||
const pattern = this.requirePattern(workspace, session.patternId);
|
||||
const effectivePattern = this.applyProjectCustomizationToPattern(
|
||||
await this.buildEffectivePattern(pattern, session),
|
||||
await this.buildEffectivePattern(pattern, session, workspace.settings.agents ?? []),
|
||||
project,
|
||||
);
|
||||
const projectInstructions = resolveProjectInstructionsContent(project.customization);
|
||||
@@ -1068,7 +1106,7 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
const project = this.requireProject(workspace, session.projectId);
|
||||
const pattern = this.requirePattern(workspace, session.patternId);
|
||||
const effectivePattern = this.applyProjectCustomizationToPattern(
|
||||
await this.buildEffectivePattern(pattern, session),
|
||||
await this.buildEffectivePattern(pattern, session, workspace.settings.agents ?? []),
|
||||
project,
|
||||
);
|
||||
const projectInstructions = resolveProjectInstructionsContent(project.customization);
|
||||
@@ -2919,10 +2957,12 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
private async buildEffectivePattern(
|
||||
pattern: PatternDefinition,
|
||||
session: SessionRecord,
|
||||
workspaceAgents: ReadonlyArray<WorkspaceAgentDefinition>,
|
||||
): Promise<PatternDefinition> {
|
||||
const resolvedPattern = resolvePatternAgents(pattern, workspaceAgents);
|
||||
const patternWithSessionConfig = session.sessionModelConfig
|
||||
? applySessionModelConfig(pattern, session)
|
||||
: pattern;
|
||||
? applySessionModelConfig(resolvedPattern, session)
|
||||
: resolvedPattern;
|
||||
const patternWithApprovalSettings = applySessionApprovalSettings(patternWithSessionConfig, session);
|
||||
|
||||
const modelCatalog = await this.loadAvailableModelCatalog();
|
||||
|
||||
@@ -34,6 +34,7 @@ import type {
|
||||
SaveLspProfileInput,
|
||||
SaveMcpServerInput,
|
||||
SavePatternInput,
|
||||
SaveWorkspaceAgentInput,
|
||||
SendSessionMessageInput,
|
||||
SetPatternFavoriteInput,
|
||||
SetProjectAgentProfileEnabledInput,
|
||||
@@ -148,6 +149,12 @@ export function registerIpcHandlers(
|
||||
ipcMain.handle(ipcChannels.deleteLspProfile, (_event, profileId: string) =>
|
||||
service.deleteLspProfile(profileId),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.saveWorkspaceAgent, (_event, input: SaveWorkspaceAgentInput) =>
|
||||
service.saveWorkspaceAgent(input.agent),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.deleteWorkspaceAgent, (_event, agentId: string) =>
|
||||
service.deleteWorkspaceAgent(agentId),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.describeTerminal, () => service.describeTerminal());
|
||||
ipcMain.handle(ipcChannels.createTerminal, () => service.createTerminal());
|
||||
ipcMain.handle(ipcChannels.restartTerminal, () => service.restartTerminal());
|
||||
|
||||
Reference in New Issue
Block a user