feat(ui): édition templates — nom d'agent par défaut, permissions système et MCP outils — #242

TemplateEditor/TemplatesPanel étendus aux nouveaux champs, projection
useTemplates/adapters/ports, panneau Agents aligné, templatePresentation.ts
extrait. Tests frontend étendus (templates, agents, routage chat WS).
This commit is contained in:
2026-08-23 11:00:04 +02:00
parent d6c80be56d
commit f952807882
13 changed files with 551 additions and 174 deletions

View File

@ -60,6 +60,7 @@ import type {
CreateMemoryInput,
CreateSkillInput,
CreateTemplateInput,
UpdateTemplateInput,
EmbedderGateway,
GitGateway,
InputGateway,
@ -286,11 +287,28 @@ export class HttpTemplateGateway implements TemplateGateway {
}
createTemplate(input: CreateTemplateInput): Promise<Template> {
return this.http.invoke<Template>("create_template", {
request: { name: input.name, content: input.content, defaultProfileId: input.defaultProfileId },
request: {
name: input.name,
content: input.content,
defaultProfileId: input.defaultProfileId,
defaultAgentName: input.defaultAgentName ?? null,
systemPermissions: input.systemPermissions ?? null,
mcpToolPermissions: input.mcpToolPermissions ?? null,
},
});
}
updateTemplate(templateId: string, content: string): Promise<Template> {
return this.http.invoke<Template>("update_template", { request: { templateId, content } });
updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
return this.http.invoke<Template>("update_template", {
request: {
templateId,
name: input.name,
content: input.content,
defaultProfileId: input.defaultProfileId,
defaultAgentName: input.defaultAgentName ?? null,
systemPermissions: input.systemPermissions ?? null,
mcpToolPermissions: input.mcpToolPermissions ?? null,
},
});
}
async deleteTemplate(templateId: string): Promise<void> {
await this.http.invoke("delete_template", { templateId });

View File

@ -137,9 +137,9 @@ describe("WsLiveClient structured chat routing", () => {
expect(client.getConnectionState()).toBe("reconnecting");
expect(client.needsReconnect()).toBe(true);
const fire = timerFn;
const fire = timerFn as unknown as () => void;
timerFn = null;
fire?.();
fire();
await vi.waitFor(() => expect(sockets.length).toBe(2));
await vi.waitFor(() => expect(sockets[1].sent.length).toBeGreaterThan(0));

View File

@ -118,6 +118,7 @@ import type {
DeviceGateway,
EmbedderGateway,
CreateTemplateInput,
UpdateTemplateInput,
Gateways,
InputGateway,
LiveAgent,
@ -2230,12 +2231,18 @@ export class MockTemplateGateway implements TemplateGateway {
contentMd: input.content,
version: 1,
defaultProfileId: input.defaultProfileId,
defaultAgentName: input.defaultAgentName ?? null,
systemPermissions: input.systemPermissions ?? null,
mcpToolPermissions: input.mcpToolPermissions ?? null,
};
this.templates.push(template);
return structuredClone(template);
}
async updateTemplate(templateId: string, content: string): Promise<Template> {
async updateTemplate(
templateId: string,
input: UpdateTemplateInput | string,
): Promise<Template> {
const idx = this.templates.findIndex((t) => t.id === templateId);
if (idx === -1) {
const err: GatewayError = {
@ -2244,10 +2251,27 @@ export class MockTemplateGateway implements TemplateGateway {
};
throw err;
}
const current = this.templates[idx];
const update =
typeof input === "string"
? {
name: current.name,
content: input,
defaultProfileId: current.defaultProfileId,
defaultAgentName: current.defaultAgentName ?? null,
systemPermissions: current.systemPermissions ?? null,
mcpToolPermissions: current.mcpToolPermissions ?? null,
}
: input;
this.templates[idx] = {
...this.templates[idx],
contentMd: content,
version: this.templates[idx].version + 1,
...current,
name: update.name,
contentMd: update.content,
defaultProfileId: update.defaultProfileId,
defaultAgentName: update.defaultAgentName ?? null,
systemPermissions: update.systemPermissions ?? null,
mcpToolPermissions: update.mcpToolPermissions ?? null,
version: current.version + 1,
};
return structuredClone(this.templates[idx]);
}
@ -2279,7 +2303,7 @@ export class MockTemplateGateway implements TemplateGateway {
}
const synchronized = opts?.synchronized ?? true;
const name = opts?.name ?? template.name;
const name = opts?.name ?? template.defaultAgentName ?? template.name;
const slug = slugify(name) || "agent";
// Derive unique contextPath (same logic as MockAgentGateway)

View File

@ -13,7 +13,7 @@
import { invoke } from "@tauri-apps/api/core";
import type { Agent, AgentDrift, Template } from "@/domain";
import type { CreateTemplateInput, TemplateGateway } from "@/ports";
import type { CreateTemplateInput, TemplateGateway, UpdateTemplateInput } from "@/ports";
export class TauriTemplateGateway implements TemplateGateway {
listTemplates(): Promise<Template[]> {
@ -26,13 +26,24 @@ export class TauriTemplateGateway implements TemplateGateway {
name: input.name,
content: input.content,
defaultProfileId: input.defaultProfileId,
defaultAgentName: input.defaultAgentName ?? null,
systemPermissions: input.systemPermissions ?? null,
mcpToolPermissions: input.mcpToolPermissions ?? null,
},
});
}
updateTemplate(templateId: string, content: string): Promise<Template> {
updateTemplate(templateId: string, input: UpdateTemplateInput): Promise<Template> {
return invoke<Template>("update_template", {
request: { templateId, content },
request: {
templateId,
name: input.name,
content: input.content,
defaultProfileId: input.defaultProfileId,
defaultAgentName: input.defaultAgentName ?? null,
systemPermissions: input.systemPermissions ?? null,
mcpToolPermissions: input.mcpToolPermissions ?? null,
},
});
}