fix: fix some displays and features

This commit is contained in:
2026-06-06 17:06:45 +02:00
parent 2332b7f815
commit 3be55795a6
31 changed files with 3118 additions and 30 deletions

View File

@ -23,12 +23,15 @@ import type {
LayoutTree,
Project,
ProfileAvailability,
Skill,
SkillScope,
Template,
Unsubscribe,
} from "@/domain";
import type {
AgentGateway,
CreateAgentInput,
CreateSkillInput,
CreateTemplateInput,
Gateways,
GitGateway,
@ -38,6 +41,7 @@ import type {
ProjectGateway,
ReattachResult,
RemoteGateway,
SkillGateway,
SystemGateway,
TemplateGateway,
TerminalGateway,
@ -191,6 +195,7 @@ export class MockAgentGateway implements AgentGateway {
profileId: input.profileId,
origin: { type: "scratch" },
synchronized: false,
skills: [],
};
list.push(agent);
this.contexts.set(
@ -282,6 +287,18 @@ export class MockAgentGateway implements AgentGateway {
return this.getAgents(projectId);
}
/**
* Replaces an agent's assigned skills in-place.
* Used by `MockSkillGateway.assignSkill` / `unassignSkill` so both gateways
* share the same in-memory store.
*/
_setSkills(projectId: string, agentId: string, skills: Agent["skills"]): void {
const list = this.getAgents(projectId);
const idx = list.findIndex((a) => a.id === agentId);
if (idx === -1) return;
list[idx] = { ...list[idx], skills };
}
async launchAgent(
projectId: string,
agentId: string,
@ -931,6 +948,7 @@ export class MockTemplateGateway implements TemplateGateway {
syncedTemplateVersion: template.version,
},
synchronized,
skills: [],
};
this.agentGateway._insertAgent(projectId, agent, template.contentMd);
@ -990,6 +1008,130 @@ export class MockTemplateGateway implements TemplateGateway {
}
}
/**
* Stateful in-memory skill gateway (L12).
*
* Skills are keyed by `${scope}` (project scope is further partitioned by
* `projectId`, mirroring the disjoint on-disk roots). Assignment mutates the
* agent record held by the injected {@link MockAgentGateway}, so both gateways
* share one store — exactly like {@link MockTemplateGateway}.
*/
export class MockSkillGateway implements SkillGateway {
// Global skills are project-independent; project skills live under their id.
private global: Skill[] = [];
private byProject = new Map<string, Skill[]>();
private seq = 0;
constructor(private readonly agentGateway: MockAgentGateway) {}
private bucket(projectId: string, scope: SkillScope): Skill[] {
if (scope === "global") return this.global;
let list = this.byProject.get(projectId);
if (!list) {
list = [];
this.byProject.set(projectId, list);
}
return list;
}
async listSkills(projectId: string, scope: SkillScope): Promise<Skill[]> {
return structuredClone(this.bucket(projectId, scope));
}
async createSkill(input: CreateSkillInput): Promise<Skill> {
this.seq += 1;
const skill: Skill = {
id: `mock-skill-${this.seq}`,
name: input.name,
contentMd: input.content,
scope: input.scope,
};
this.bucket(input.projectId, input.scope).push(skill);
return structuredClone(skill);
}
async updateSkill(
projectId: string,
scope: SkillScope,
skillId: string,
content: string,
): Promise<Skill> {
const list = this.bucket(projectId, scope);
const idx = list.findIndex((s) => s.id === skillId);
if (idx === -1) {
const err: GatewayError = {
code: "NOT_FOUND",
message: `skill ${skillId} not found`,
};
throw err;
}
list[idx] = { ...list[idx], contentMd: content };
return structuredClone(list[idx]);
}
async deleteSkill(
projectId: string,
scope: SkillScope,
skillId: string,
): Promise<void> {
const list = this.bucket(projectId, scope);
const idx = list.findIndex((s) => s.id === skillId);
if (idx === -1) {
const err: GatewayError = {
code: "NOT_FOUND",
message: `skill ${skillId} not found`,
};
throw err;
}
list.splice(idx, 1);
}
async assignSkill(
projectId: string,
agentId: string,
skillId: string,
scope: SkillScope,
): Promise<void> {
const agent = this.agentGateway
._rawAgents(projectId)
.find((a) => a.id === agentId);
if (!agent) {
const err: GatewayError = {
code: "NOT_FOUND",
message: `agent ${agentId} not found in project ${projectId}`,
};
throw err;
}
if (agent.skills.some((s) => s.skillId === skillId)) return; // idempotent
this.agentGateway._setSkills(projectId, agentId, [
...agent.skills,
{ skillId, scope },
]);
}
async unassignSkill(
projectId: string,
agentId: string,
skillId: string,
): Promise<void> {
const agent = this.agentGateway
._rawAgents(projectId)
.find((a) => a.id === agentId);
if (!agent) {
const err: GatewayError = {
code: "NOT_FOUND",
message: `agent ${agentId} not found in project ${projectId}`,
};
throw err;
}
this.agentGateway._setSkills(
projectId,
agentId,
agent.skills.filter((s) => s.skillId !== skillId),
);
}
}
/** Builds the full set of mock gateways. */
export function createMockGateways(): Gateways {
const agentGateway = new MockAgentGateway();
@ -1003,6 +1145,7 @@ export function createMockGateways(): Gateways {
remote: new MockRemoteGateway(),
profile: new MockProfileGateway(),
template: new MockTemplateGateway(agentGateway),
skill: new MockSkillGateway(agentGateway),
};
}