feat(03-01): add SkillsSection layout, SkillList, SkillFileTree, and SkillImportModal

- Create SkillsSection with two-column grid layout (19rem + flex) matching Paperclip pattern
- Create SkillList with source icons, expandable file trees, CSS grid animation
- Create SkillFileTree with recursive rendering, dir/file icons, indent levels
- Create SkillImportModal with 4 tabs: GitHub, Local Path, HTTP URL, skills.sh
- Wire listSkills IPC for skill loading and importSkill IPC for import flow
- Add skill IPC channels, Zod schemas, and DesktopApi methods to contracts.ts
- Add skill IPC bridge methods to preload/index.ts
- All Phosphor icons, all ui.tsx components, no external dependencies

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-26 01:51:06 -04:00
1 parent e8d642941f
commit 49b75257fb
6 files changed
+671 -6

No files matched your search

+51 -1
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import type { AgentMessageRecord, AgentMetrics, BrowserActionRecord, CommentRecord, CompanyMetrics, DesktopEvent, InboxItem, OnboardingBootstrapResult, ProfileSnapshot, RunLogChunk, StandupReport } from "./types";
import type { AgentMessageRecord, AgentMetrics, BrowserActionRecord, CommentRecord, CompanyMetrics, DesktopEvent, InboxItem, OnboardingBootstrapResult, ProfileSnapshot, RunLogChunk, SkillImportResult, SkillRecord, SkillUpdateStatus, StandupReport } from "./types";
export const IPC_CHANNELS = {
loadSnapshot: "agent-company:load-snapshot",
@@ -73,6 +73,13 @@ export const IPC_CHANNELS = {
runWorkflow: "agent-company:run-workflow",
autoAssignTask: "agent-company:auto-assign-task",
searchMessages: "agent-company:search-messages",
importSkill: "agent-company:import-skill",
listSkills: "agent-company:list-skills",
getSkillDetail: "agent-company:get-skill-detail",
deleteSkill: "agent-company:delete-skill",
assignSkillToAgent: "agent-company:assign-skill-to-agent",
removeSkillFromAgent: "agent-company:remove-skill-from-agent",
checkSkillUpdate: "agent-company:check-skill-update",
} as const;
const optionalId = z.preprocess(
@@ -514,6 +521,42 @@ export const autoAssignSchema = z.object({
companyId: z.string().uuid(),
});
export const importSkillSchema = z.object({
companyId: z.string().uuid(),
source: z.string().min(1),
});
export const listSkillsSchema = z.object({
companyId: z.string().uuid(),
});
export const getSkillDetailSchema = z.object({
companyId: z.string().uuid(),
skillId: z.string().uuid(),
});
export const deleteSkillSchema = z.object({
companyId: z.string().uuid(),
skillId: z.string().uuid(),
});
export const assignSkillToAgentSchema = z.object({
companyId: z.string().uuid(),
agentId: z.string().uuid(),
skillId: z.string().uuid(),
});
export const removeSkillFromAgentSchema = z.object({
companyId: z.string().uuid(),
agentId: z.string().uuid(),
skillId: z.string().uuid(),
});
export const checkSkillUpdateSchema = z.object({
companyId: z.string().uuid(),
skillId: z.string().uuid(),
});
export type DesktopResult<T> =
| { ok: true; data: T }
| { ok: false; error: { code: string; message: string } };
@@ -592,5 +635,12 @@ export type DesktopApi = {
recoveryRetry: (taskId: string, agentId: string, companyId: string) => Promise<DesktopResult<null>>;
recoveryDismiss: (taskId: string) => Promise<DesktopResult<null>>;
recoveryFail: (taskId: string) => Promise<DesktopResult<null>>;
importSkill: (companyId: string, source: string) => Promise<DesktopResult<SkillImportResult[]>>;
listSkills: (companyId: string) => Promise<DesktopResult<SkillRecord[]>>;
getSkillDetail: (companyId: string, skillId: string) => Promise<DesktopResult<SkillRecord>>;
deleteSkill: (companyId: string, skillId: string) => Promise<DesktopResult<void>>;
assignSkillToAgent: (companyId: string, agentId: string, skillId: string) => Promise<DesktopResult<void>>;
removeSkillFromAgent: (companyId: string, agentId: string, skillId: string) => Promise<DesktopResult<void>>;
checkSkillUpdate: (companyId: string, skillId: string) => Promise<DesktopResult<SkillUpdateStatus>>;
subscribe: (callback: (event: DesktopEvent) => void) => () => void;
};