mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
The desktop app could read plugin-produced skills and agents, but it had no plugin control plane of its own. This adds a dedicated Settings tab backed by server-side plugin APIs so installed plugins can be inspected, enabled, disabled, updated, reloaded, and uninstalled from the WebUI. The implementation also teaches browser-based desktop dev sessions to honor a custom backend URL, which made it possible to run isolated worktree ports for real UI automation. During verification, the long-lived desktop server kept a stale installed-plugin snapshot after external CLI mutations, so cache clearing now resets that session-level plugin installation state as well. Constraint: Desktop WebUI needed an isolated backend URL instead of the hard-coded 127.0.0.1:3456 fallback Constraint: Reuse existing plugin operations and loaders instead of rebuilding plugin lifecycle logic in the desktop layer Rejected: Fold plugin management into Skills or Adapters | mixed unrelated lifecycles and hid plugin-specific health/actions Rejected: Expose only read-only plugin status in desktop | did not satisfy enable-disable-reload-uninstall verification needs Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep desktop plugin actions routed through the shared plugin operation layer and clear installed-plugin session caches when plugin state changes externally Tested: cd desktop && bun run lint Tested: cd desktop && bun run test -- src/__tests__/pluginsSettings.test.tsx Tested: bun test src/server/__tests__/plugins.test.ts src/server/__tests__/skills.test.ts Tested: Browser automation against isolated ports 15120/38456 covering discord plugin list/detail/disable/apply/enable/update/uninstall flows Not-tested: Full desktop session runtime parity with CLI /reload-plugins AppState refresh beyond the new desktop API path
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import { api } from './client'
|
|
import type {
|
|
PluginDetail,
|
|
PluginListResponse,
|
|
PluginReloadSummary,
|
|
PluginScope,
|
|
} from '../types/plugin'
|
|
|
|
type PluginActionPayload = {
|
|
id: string
|
|
scope?: PluginScope
|
|
keepData?: boolean
|
|
}
|
|
|
|
export const pluginsApi = {
|
|
list: (cwd?: string) => {
|
|
const query = cwd ? `?cwd=${encodeURIComponent(cwd)}` : ''
|
|
return api.get<PluginListResponse>(`/api/plugins${query}`)
|
|
},
|
|
|
|
detail: (id: string, cwd?: string) => {
|
|
const query = new URLSearchParams({ id })
|
|
if (cwd) query.set('cwd', cwd)
|
|
return api.get<{ detail: PluginDetail }>(`/api/plugins/detail?${query.toString()}`)
|
|
},
|
|
|
|
enable: (payload: PluginActionPayload) =>
|
|
api.post<{ ok: true; message: string }>('/api/plugins/enable', payload),
|
|
|
|
disable: (payload: PluginActionPayload) =>
|
|
api.post<{ ok: true; message: string }>('/api/plugins/disable', payload),
|
|
|
|
update: (payload: PluginActionPayload) =>
|
|
api.post<{ ok: true; message: string }>('/api/plugins/update', payload),
|
|
|
|
uninstall: (payload: PluginActionPayload) =>
|
|
api.post<{ ok: true; message: string }>('/api/plugins/uninstall', payload),
|
|
|
|
reload: (cwd?: string) => {
|
|
const query = cwd ? `?cwd=${encodeURIComponent(cwd)}` : ''
|
|
return api.post<{ ok: true; summary: PluginReloadSummary }>(`/api/plugins/reload${query}`)
|
|
},
|
|
}
|