agent-mockingbird 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/btca-cli/SKILL.md +64 -0
- package/.agents/skills/btca-cli/agents/openai.yaml +3 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/frontend-design/agents/openai.yaml +3 -0
- package/.env.example +36 -0
- package/.githooks/pre-commit +33 -0
- package/.github/workflows/ci.yml +309 -0
- package/.opencode/bun.lock +18 -0
- package/.opencode/package.json +5 -0
- package/.opencode/tools/agent_type_manager.ts +100 -0
- package/.opencode/tools/config_manager.ts +87 -0
- package/.opencode/tools/cron_manager.ts +145 -0
- package/.opencode/tools/memory_get.ts +43 -0
- package/.opencode/tools/memory_remember.ts +53 -0
- package/.opencode/tools/memory_search.ts +48 -0
- package/AGENTS.md +126 -0
- package/MEMORY.md +2 -0
- package/README.md +451 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/agent-mockingbird.config.example.json +135 -0
- package/apps/server/package.json +32 -0
- package/apps/server/src/backend/agents/bootstrapContext.ts +362 -0
- package/apps/server/src/backend/agents/openclawImport.test.ts +133 -0
- package/apps/server/src/backend/agents/openclawImport.ts +797 -0
- package/apps/server/src/backend/agents/opencodeConfig.ts +428 -0
- package/apps/server/src/backend/agents/service.ts +10 -0
- package/apps/server/src/backend/config/example-config.test.ts +20 -0
- package/apps/server/src/backend/config/orchestration.ts +243 -0
- package/apps/server/src/backend/config/policy.ts +158 -0
- package/apps/server/src/backend/config/schema.test.ts +15 -0
- package/apps/server/src/backend/config/schema.ts +391 -0
- package/apps/server/src/backend/config/semantic.test.ts +34 -0
- package/apps/server/src/backend/config/semantic.ts +149 -0
- package/apps/server/src/backend/config/service.test.ts +75 -0
- package/apps/server/src/backend/config/service.ts +207 -0
- package/apps/server/src/backend/config/smoke.ts +77 -0
- package/apps/server/src/backend/config/store.test.ts +123 -0
- package/apps/server/src/backend/config/store.ts +581 -0
- package/apps/server/src/backend/config/testFixtures.ts +5 -0
- package/apps/server/src/backend/config/types.ts +56 -0
- package/apps/server/src/backend/contracts/events.ts +320 -0
- package/apps/server/src/backend/contracts/runtime.ts +111 -0
- package/apps/server/src/backend/cron/executor.ts +435 -0
- package/apps/server/src/backend/cron/repository.ts +170 -0
- package/apps/server/src/backend/cron/service.ts +660 -0
- package/apps/server/src/backend/cron/storage.ts +92 -0
- package/apps/server/src/backend/cron/types.ts +138 -0
- package/apps/server/src/backend/cron/utils.ts +351 -0
- package/apps/server/src/backend/db/client.ts +20 -0
- package/apps/server/src/backend/db/migrate.ts +40 -0
- package/apps/server/src/backend/db/repository.ts +1762 -0
- package/apps/server/src/backend/db/schema.ts +113 -0
- package/apps/server/src/backend/db/usageDashboard.test.ts +102 -0
- package/apps/server/src/backend/db/wipe.ts +13 -0
- package/apps/server/src/backend/defaults.ts +32 -0
- package/apps/server/src/backend/env.ts +48 -0
- package/apps/server/src/backend/heartbeat/activeHours.ts +45 -0
- package/apps/server/src/backend/heartbeat/defaultJob.ts +88 -0
- package/apps/server/src/backend/heartbeat/heartbeat.test.ts +110 -0
- package/apps/server/src/backend/heartbeat/runtimeService.ts +190 -0
- package/apps/server/src/backend/heartbeat/service.ts +176 -0
- package/apps/server/src/backend/heartbeat/state.test.ts +63 -0
- package/apps/server/src/backend/heartbeat/state.ts +167 -0
- package/apps/server/src/backend/heartbeat/types.ts +54 -0
- package/apps/server/src/backend/http/boundedQueue.test.ts +49 -0
- package/apps/server/src/backend/http/boundedQueue.ts +92 -0
- package/apps/server/src/backend/http/parsers.ts +40 -0
- package/apps/server/src/backend/http/router.ts +61 -0
- package/apps/server/src/backend/http/routes/agentRoutes.ts +67 -0
- package/apps/server/src/backend/http/routes/backgroundRoutes.ts +203 -0
- package/apps/server/src/backend/http/routes/chatRoutes.ts +107 -0
- package/apps/server/src/backend/http/routes/configRoutes.ts +602 -0
- package/apps/server/src/backend/http/routes/cronRoutes.ts +221 -0
- package/apps/server/src/backend/http/routes/dashboardRoutes.ts +308 -0
- package/apps/server/src/backend/http/routes/eventRoutes.ts +7 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.test.ts +41 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.ts +28 -0
- package/apps/server/src/backend/http/routes/index.ts +101 -0
- package/apps/server/src/backend/http/routes/mcpRoutes.ts +213 -0
- package/apps/server/src/backend/http/routes/memoryRoutes.ts +154 -0
- package/apps/server/src/backend/http/routes/runRoutes.ts +310 -0
- package/apps/server/src/backend/http/routes/runtimeRoutes.ts +197 -0
- package/apps/server/src/backend/http/routes/skillRoutes.ts +112 -0
- package/apps/server/src/backend/http/routes/uiRoutes.test.ts +161 -0
- package/apps/server/src/backend/http/routes/uiRoutes.ts +177 -0
- package/apps/server/src/backend/http/routes/usageRoutes.test.ts +104 -0
- package/apps/server/src/backend/http/routes/usageRoutes.ts +767 -0
- package/apps/server/src/backend/http/schemas.ts +64 -0
- package/apps/server/src/backend/http/sse.ts +144 -0
- package/apps/server/src/backend/integration/backend-core.test.ts +2316 -0
- package/apps/server/src/backend/logging/logger.ts +64 -0
- package/apps/server/src/backend/mcp/service.ts +326 -0
- package/apps/server/src/backend/memory/cli.ts +170 -0
- package/apps/server/src/backend/memory/conceptExpansion.test.ts +28 -0
- package/apps/server/src/backend/memory/conceptExpansion.ts +80 -0
- package/apps/server/src/backend/memory/qmdPort.test.ts +54 -0
- package/apps/server/src/backend/memory/qmdPort.ts +61 -0
- package/apps/server/src/backend/memory/records.test.ts +66 -0
- package/apps/server/src/backend/memory/records.ts +229 -0
- package/apps/server/src/backend/memory/service.ts +2012 -0
- package/apps/server/src/backend/memory/sqliteVec.ts +58 -0
- package/apps/server/src/backend/memory/types.ts +104 -0
- package/apps/server/src/backend/opencode/agentMockingbirdPlugin.test.ts +396 -0
- package/apps/server/src/backend/opencode/client.ts +98 -0
- package/apps/server/src/backend/opencode/models.ts +41 -0
- package/apps/server/src/backend/opencode/systemPrompt.test.ts +146 -0
- package/apps/server/src/backend/opencode/systemPrompt.ts +284 -0
- package/apps/server/src/backend/paths.ts +57 -0
- package/apps/server/src/backend/prompts/service.ts +100 -0
- package/apps/server/src/backend/queue/queue.test.ts +189 -0
- package/apps/server/src/backend/queue/service.ts +177 -0
- package/apps/server/src/backend/queue/types.ts +39 -0
- package/apps/server/src/backend/run/service.ts +576 -0
- package/apps/server/src/backend/run/storage.ts +47 -0
- package/apps/server/src/backend/run/types.ts +44 -0
- package/apps/server/src/backend/runtime/errors.ts +61 -0
- package/apps/server/src/backend/runtime/index.ts +72 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.test.ts +153 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.ts +76 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/backgroundMethods.ts +765 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/coreMethods.ts +705 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/eventMethods.ts +503 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/memoryMethods.ts +462 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/promptMethods.ts +1167 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/shared.ts +254 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.test.ts +2899 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.ts +135 -0
- package/apps/server/src/backend/runtime/sessionScope.ts +45 -0
- package/apps/server/src/backend/skills/service.ts +442 -0
- package/apps/server/src/backend/workspace/resolve.ts +27 -0
- package/apps/server/src/cli/agent-mockingbird.mjs +2522 -0
- package/apps/server/src/cli/agent-mockingbird.test.ts +68 -0
- package/apps/server/src/cli/runtime-assets.mjs +269 -0
- package/apps/server/src/cli/runtime-assets.test.ts +52 -0
- package/apps/server/src/cli/runtime-layout.mjs +75 -0
- package/apps/server/src/cli/standaloneBuild.test.ts +19 -0
- package/apps/server/src/cli/standaloneBuild.ts +19 -0
- package/apps/server/src/cli/standaloneCronBinary.test.ts +187 -0
- package/apps/server/src/index.ts +178 -0
- package/apps/server/tsconfig.json +12 -0
- package/backlog.md +5 -0
- package/bin/agent-mockingbird +2522 -0
- package/bin/runtime-layout.mjs +75 -0
- package/build-bin.ts +34 -0
- package/build-cli.mjs +37 -0
- package/build.ts +40 -0
- package/bun-env.d.ts +11 -0
- package/bun.lock +888 -0
- package/bunfig.toml +2 -0
- package/components.json +21 -0
- package/config.json +130 -0
- package/deploy/RELEASE_INSTALL.md +112 -0
- package/deploy/docker-compose.yml +42 -0
- package/deploy/systemd/README.md +46 -0
- package/deploy/systemd/agent-mockingbird.service +28 -0
- package/deploy/systemd/opencode.service +25 -0
- package/docs/legacy-config-ui-reference.md +51 -0
- package/docs/memory-e2e-trace-2026-03-04.md +63 -0
- package/docs/memory-ops.md +96 -0
- package/docs/memory-runtime-contract.md +42 -0
- package/docs/memory-tuning-remote-2026-03-04.md +59 -0
- package/docs/opencode-rebase-workflow-plan.md +614 -0
- package/docs/opencode-startup-sync-plan.md +94 -0
- package/docs/vendor-opencode.md +41 -0
- package/drizzle/0000_famous_turbo.sql +49 -0
- package/drizzle/0001_cron_memory_aux.sql +160 -0
- package/drizzle/0002_runtime_session_bindings.sql +28 -0
- package/drizzle/0003_background_runs.sql +27 -0
- package/drizzle/0004_memory_open_write.sql +63 -0
- package/drizzle/0005_signal_channel.sql +47 -0
- package/drizzle/0006_usage_event_dimensions.sql +7 -0
- package/drizzle/meta/0000_snapshot.json +341 -0
- package/drizzle/meta/_journal.json +55 -0
- package/drizzle.config.ts +14 -0
- package/eslint.config.mjs +77 -0
- package/knip.json +18 -0
- package/memory/2026-03-04.md +4 -0
- package/opencode.lock.json +16 -0
- package/package.json +67 -0
- package/packages/agent-mockingbird-installer/README.md +31 -0
- package/packages/agent-mockingbird-installer/bin/agent-mockingbird-installer.mjs +44 -0
- package/packages/agent-mockingbird-installer/opencode.lock.json +16 -0
- package/packages/agent-mockingbird-installer/package.json +23 -0
- package/packages/contracts/package.json +19 -0
- package/packages/contracts/src/agentTypes.ts +122 -0
- package/packages/contracts/src/cron.ts +146 -0
- package/packages/contracts/src/dashboard.ts +378 -0
- package/packages/contracts/src/index.ts +3 -0
- package/packages/contracts/tsconfig.json +4 -0
- package/patches/opencode/0001-Wafflebot-OpenCode-baseline.patch +2341 -0
- package/patches/opencode/0002-Fix-OpenCode-web-entry-and-settings-icons.patch +104 -0
- package/patches/opencode/0003-fix-app-remove-duplicate-sidebar-mount.patch +32 -0
- package/patches/opencode/0004-Add-heartbeat-settings-and-usage-nav.patch +506 -0
- package/patches/opencode/0005-Use-chart-icon-for-usage-nav.patch +38 -0
- package/patches/opencode/0006-Modernize-cron-settings.patch +399 -0
- package/patches/opencode/0007-Rename-waffle-namespaces-to-mockingbird.patch +1110 -0
- package/patches/opencode/0008-Remove-cron-contract-section.patch +178 -0
- package/patches/opencode/0009-Rework-cron-tab-as-operations-console.patch +414 -0
- package/patches/opencode/0010-Refine-heartbeat-settings-controls.patch +208 -0
- package/runtime-assets/opencode-config/opencode.jsonc +25 -0
- package/runtime-assets/opencode-config/package.json +5 -0
- package/runtime-assets/opencode-config/plugins/agent-mockingbird.ts +715 -0
- package/runtime-assets/workspace/.agents/skills/config-auditor/SKILL.md +25 -0
- package/runtime-assets/workspace/.agents/skills/config-editor/SKILL.md +24 -0
- package/runtime-assets/workspace/.agents/skills/cron-manager/SKILL.md +57 -0
- package/runtime-assets/workspace/.agents/skills/memory-ops/SKILL.md +120 -0
- package/runtime-assets/workspace/.agents/skills/runtime-diagnose/SKILL.md +25 -0
- package/runtime-assets/workspace/AGENTS.md +56 -0
- package/runtime-assets/workspace/MEMORY.md +4 -0
- package/scripts/build-release-bundle.sh +66 -0
- package/scripts/check-ship.ts +383 -0
- package/scripts/dev-opencode.sh +17 -0
- package/scripts/dev-stack-opencode.sh +15 -0
- package/scripts/dev-stack.sh +61 -0
- package/scripts/install-systemd.sh +87 -0
- package/scripts/memory-e2e.sh +76 -0
- package/scripts/memory-trace-e2e.sh +141 -0
- package/scripts/migrate-opencode-env.ts +108 -0
- package/scripts/onboard/bootstrap.sh +32 -0
- package/scripts/opencode-swap.ts +78 -0
- package/scripts/opencode-sync.ts +715 -0
- package/scripts/runtime-assets-sync.mjs +83 -0
- package/scripts/setup-git-hooks.ts +39 -0
- package/tsconfig.json +45 -0
- package/tui.json +98 -0
- package/turbo.json +36 -0
- package/vendor/OPENCODE_VENDOR.md +13 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { normalizeAgentTypeDraft as normalizeSharedAgentTypeDraft, normalizeAgentTypeMode } from "@agent-mockingbird/contracts/agentTypes";
|
|
2
|
+
import type { Config, ConfigProvidersResponse } from "@opencode-ai/sdk/client";
|
|
3
|
+
import { applyEdits, format, modify, parse as parseJsonc } from "jsonc-parser";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import type { AgentTypeDefinition, AgentMockingbirdConfig } from "../config/schema";
|
|
9
|
+
import { agentTypeDefinitionSchema } from "../config/schema";
|
|
10
|
+
import { getConfigSnapshot } from "../config/service";
|
|
11
|
+
import { createOpencodeClientFromConnection, unwrapSdkData } from "../opencode/client";
|
|
12
|
+
import { resolveOpencodeConfigDir, resolveOpencodeWorkspaceDir } from "../workspace/resolve";
|
|
13
|
+
|
|
14
|
+
type OpenCodeAgentConfigRecord = Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
interface OpencodeAgentValidationIssue {
|
|
17
|
+
path: string;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface OpencodeAgentStorageInfo {
|
|
22
|
+
workspaceDirectory: string;
|
|
23
|
+
configDirectory: string;
|
|
24
|
+
configFilePath: string;
|
|
25
|
+
persistenceMode: "managed-config-dir";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const NON_DELETABLE_BUILTIN_AGENT_IDS = new Set(["explore", "general"]);
|
|
29
|
+
const OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
30
|
+
|
|
31
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
32
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function stableSerialize(value: unknown): string {
|
|
36
|
+
if (value === null) return "null";
|
|
37
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return `[${value.map(item => stableSerialize(item)).join(",")}]`;
|
|
42
|
+
}
|
|
43
|
+
if (!isPlainObject(value)) return JSON.stringify(value);
|
|
44
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
45
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableSerialize(entry)}`).join(",")}}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeRuntimeAgentConfigMap(value: unknown): Record<string, OpenCodeAgentConfigRecord> {
|
|
49
|
+
if (!isPlainObject(value)) return {};
|
|
50
|
+
const normalized: Record<string, OpenCodeAgentConfigRecord> = {};
|
|
51
|
+
for (const [rawName, rawConfig] of Object.entries(value)) {
|
|
52
|
+
const name = rawName.trim();
|
|
53
|
+
if (!name) continue;
|
|
54
|
+
normalized[name] = isPlainObject(rawConfig) ? { ...rawConfig } : {};
|
|
55
|
+
}
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeAgentTypeDraft(input: AgentTypeDefinition): AgentTypeDefinition {
|
|
60
|
+
const parsed = agentTypeDefinitionSchema.parse(input);
|
|
61
|
+
return normalizeSharedAgentTypeDraft(parsed) as AgentTypeDefinition;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toAgentTypeDefinition(id: string, config: OpenCodeAgentConfigRecord): AgentTypeDefinition {
|
|
65
|
+
const permission =
|
|
66
|
+
isPlainObject(config.permission) ? (config.permission as AgentTypeDefinition["permission"]) : undefined;
|
|
67
|
+
return normalizeAgentTypeDraft({
|
|
68
|
+
id,
|
|
69
|
+
name: typeof config.name === "string" ? config.name : undefined,
|
|
70
|
+
description: typeof config.description === "string" ? config.description : undefined,
|
|
71
|
+
prompt: typeof config.prompt === "string" ? config.prompt : undefined,
|
|
72
|
+
model: typeof config.model === "string" ? config.model : undefined,
|
|
73
|
+
variant: typeof config.variant === "string" ? config.variant : undefined,
|
|
74
|
+
mode: normalizeAgentTypeMode(config.mode),
|
|
75
|
+
hidden: config.hidden === true,
|
|
76
|
+
disable: config.disable === true,
|
|
77
|
+
temperature: typeof config.temperature === "number" ? config.temperature : undefined,
|
|
78
|
+
topP: typeof config.top_p === "number" ? config.top_p : undefined,
|
|
79
|
+
steps: typeof config.steps === "number" ? config.steps : undefined,
|
|
80
|
+
permission,
|
|
81
|
+
options: isPlainObject(config.options) ? (config.options as Record<string, unknown>) : {},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function toOpenCodeAgentConfig(
|
|
86
|
+
agentType: AgentTypeDefinition,
|
|
87
|
+
previous?: OpenCodeAgentConfigRecord,
|
|
88
|
+
): OpenCodeAgentConfigRecord {
|
|
89
|
+
return {
|
|
90
|
+
...(isPlainObject(previous) ? previous : {}),
|
|
91
|
+
name: agentType.name?.trim() || undefined,
|
|
92
|
+
description: agentType.description?.trim() || undefined,
|
|
93
|
+
prompt: agentType.prompt?.trim() || undefined,
|
|
94
|
+
model: agentType.model?.trim() || undefined,
|
|
95
|
+
variant: agentType.variant?.trim() || undefined,
|
|
96
|
+
mode: normalizeAgentTypeMode(agentType.mode),
|
|
97
|
+
hidden: agentType.hidden === true,
|
|
98
|
+
disable: agentType.disable === true,
|
|
99
|
+
temperature: agentType.temperature,
|
|
100
|
+
top_p: agentType.topP,
|
|
101
|
+
steps: agentType.steps,
|
|
102
|
+
permission: agentType.permission,
|
|
103
|
+
options: isPlainObject(agentType.options) ? { ...agentType.options } : {},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function canonicalOpencodeConfigPath(baseDir: string) {
|
|
108
|
+
return path.join(baseDir, "opencode.jsonc");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveOpencodeConfigFile(config: AgentMockingbirdConfig, createIfMissing = true) {
|
|
112
|
+
const configDirectory = resolveOpencodeConfigDir(config);
|
|
113
|
+
const fallback = canonicalOpencodeConfigPath(configDirectory);
|
|
114
|
+
if (!createIfMissing) {
|
|
115
|
+
return fallback;
|
|
116
|
+
}
|
|
117
|
+
mkdirSync(path.dirname(fallback), { recursive: true });
|
|
118
|
+
if (!existsSync(fallback)) {
|
|
119
|
+
writeFileSync(fallback, `{\n "$schema": "${OPENCODE_SCHEMA_URL}"\n}\n`, "utf8");
|
|
120
|
+
}
|
|
121
|
+
return fallback;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getOpencodeAgentStorageInfo(config: AgentMockingbirdConfig = getConfigSnapshot().config): OpencodeAgentStorageInfo {
|
|
125
|
+
const workspaceDirectory = resolveOpencodeWorkspaceDir(config);
|
|
126
|
+
const configDirectory = resolveOpencodeConfigDir(config);
|
|
127
|
+
return {
|
|
128
|
+
workspaceDirectory,
|
|
129
|
+
configDirectory,
|
|
130
|
+
configFilePath: resolveOpencodeConfigFile(config, false),
|
|
131
|
+
persistenceMode: "managed-config-dir",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function listAgentSearchRoots(config: AgentMockingbirdConfig): string[] {
|
|
136
|
+
return [resolveOpencodeConfigDir(config)];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function deleteAgentMarkdownFiles(config: AgentMockingbirdConfig, agentId: string): string[] {
|
|
140
|
+
const normalized = agentId.trim();
|
|
141
|
+
if (!normalized) return [];
|
|
142
|
+
const deleted: string[] = [];
|
|
143
|
+
|
|
144
|
+
for (const root of listAgentSearchRoots(config)) {
|
|
145
|
+
for (const folder of ["agent", "agents"]) {
|
|
146
|
+
const candidate = path.join(root, folder, `${normalized}.md`);
|
|
147
|
+
if (!existsSync(candidate)) continue;
|
|
148
|
+
unlinkSync(candidate);
|
|
149
|
+
deleted.push(candidate);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return deleted.sort((a, b) => a.localeCompare(b));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function patchJsoncField(input: string, value: unknown, jsonPath: string[]) {
|
|
157
|
+
const edits = modify(input, jsonPath, value, {
|
|
158
|
+
formattingOptions: {
|
|
159
|
+
insertSpaces: true,
|
|
160
|
+
tabSize: 2,
|
|
161
|
+
eol: "\n",
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
const patched = applyEdits(input, edits);
|
|
165
|
+
const formatEdits = format(patched, undefined, {
|
|
166
|
+
insertSpaces: true,
|
|
167
|
+
tabSize: 2,
|
|
168
|
+
eol: "\n",
|
|
169
|
+
});
|
|
170
|
+
return applyEdits(patched, formatEdits);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readFileAgentConfigMap(configPath: string): Record<string, OpenCodeAgentConfigRecord> {
|
|
174
|
+
const raw = readFileSync(configPath, "utf8");
|
|
175
|
+
const parsed = parseJsonc(raw);
|
|
176
|
+
if (!isPlainObject(parsed)) return {};
|
|
177
|
+
const agent = parsed.agent;
|
|
178
|
+
return normalizeRuntimeAgentConfigMap(agent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function writeFileAgentConfigMap(configPath: string, nextMap: Record<string, OpenCodeAgentConfigRecord>) {
|
|
182
|
+
const raw = readFileSync(configPath, "utf8");
|
|
183
|
+
const nextAgent = Object.fromEntries(Object.entries(nextMap).sort(([left], [right]) => left.localeCompare(right)));
|
|
184
|
+
const nextRaw = patchJsoncField(raw, nextAgent, ["agent"]);
|
|
185
|
+
writeFileSync(configPath, nextRaw, "utf8");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function hashAgentTypes(agentTypes: AgentTypeDefinition[]): string {
|
|
189
|
+
return createHash("sha256")
|
|
190
|
+
.update(stableSerialize(agentTypes))
|
|
191
|
+
.digest("hex");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createOpencodeConfigClient(config: AgentMockingbirdConfig) {
|
|
195
|
+
return createOpencodeClientFromConnection({
|
|
196
|
+
baseUrl: config.runtime.opencode.baseUrl,
|
|
197
|
+
directory: config.runtime.opencode.directory,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function disposeOpencodeInstance(config: AgentMockingbirdConfig) {
|
|
202
|
+
try {
|
|
203
|
+
await createOpencodeConfigClient(config).instance.dispose({
|
|
204
|
+
responseStyle: "data",
|
|
205
|
+
throwOnError: true,
|
|
206
|
+
signal: AbortSignal.timeout(config.runtime.opencode.timeoutMs),
|
|
207
|
+
});
|
|
208
|
+
} catch {
|
|
209
|
+
// Best-effort; if dispose is unavailable, rely on runtime file watchers.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function getOpencodeConfig(config: AgentMockingbirdConfig) {
|
|
214
|
+
const client = createOpencodeConfigClient(config);
|
|
215
|
+
return unwrapSdkData<Config>(
|
|
216
|
+
await client.config.get({
|
|
217
|
+
responseStyle: "data",
|
|
218
|
+
throwOnError: true,
|
|
219
|
+
signal: AbortSignal.timeout(config.runtime.opencode.timeoutMs),
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function loadProviderModelMap(config: AgentMockingbirdConfig) {
|
|
225
|
+
const client = createOpencodeConfigClient(config);
|
|
226
|
+
const payload = unwrapSdkData<ConfigProvidersResponse>(
|
|
227
|
+
await client.config.providers({
|
|
228
|
+
responseStyle: "data",
|
|
229
|
+
throwOnError: true,
|
|
230
|
+
signal: AbortSignal.timeout(config.runtime.opencode.timeoutMs),
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
const modelsByProvider = new Map<string, Set<string>>();
|
|
234
|
+
for (const provider of payload.providers) {
|
|
235
|
+
const modelSet = new Set<string>();
|
|
236
|
+
for (const [modelKey, model] of Object.entries(provider.models)) {
|
|
237
|
+
const id = (model.id ?? modelKey).trim();
|
|
238
|
+
if (id) modelSet.add(id);
|
|
239
|
+
}
|
|
240
|
+
modelsByProvider.set(provider.id.trim(), modelSet);
|
|
241
|
+
}
|
|
242
|
+
return modelsByProvider;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function parseModelRef(rawRef: string, defaultProviderId: string) {
|
|
246
|
+
const trimmed = rawRef.trim();
|
|
247
|
+
if (!trimmed) return null;
|
|
248
|
+
if (!trimmed.includes("/")) {
|
|
249
|
+
return { providerId: defaultProviderId.trim(), modelId: trimmed };
|
|
250
|
+
}
|
|
251
|
+
const [providerPart = "", ...rest] = trimmed.split("/");
|
|
252
|
+
const modelId = rest.join("/").trim();
|
|
253
|
+
const providerId = providerPart.trim();
|
|
254
|
+
if (!providerId || !modelId) return null;
|
|
255
|
+
return { providerId, modelId };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function listOpencodeAgentTypes() {
|
|
259
|
+
const config = getConfigSnapshot().config;
|
|
260
|
+
const storage = getOpencodeAgentStorageInfo(config);
|
|
261
|
+
const runtimeConfig = await getOpencodeConfig(config);
|
|
262
|
+
const currentMap = normalizeRuntimeAgentConfigMap((runtimeConfig as Record<string, unknown>).agent);
|
|
263
|
+
const agentTypes = Object.entries(currentMap)
|
|
264
|
+
.map(([id, agentConfig]) => toAgentTypeDefinition(id, agentConfig))
|
|
265
|
+
.filter(agentType => agentType.disable !== true)
|
|
266
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
267
|
+
return {
|
|
268
|
+
agentTypes,
|
|
269
|
+
hash: hashAgentTypes(agentTypes),
|
|
270
|
+
storage,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function validateOpencodeAgentPatch(input: {
|
|
275
|
+
upserts: unknown;
|
|
276
|
+
deletes: unknown;
|
|
277
|
+
}) {
|
|
278
|
+
const issues: OpencodeAgentValidationIssue[] = [];
|
|
279
|
+
const warnings: OpencodeAgentValidationIssue[] = [];
|
|
280
|
+
|
|
281
|
+
const upsertsRaw = Array.isArray(input.upserts) ? input.upserts : [];
|
|
282
|
+
const upserts: AgentTypeDefinition[] = [];
|
|
283
|
+
for (let index = 0; index < upsertsRaw.length; index += 1) {
|
|
284
|
+
const parsed = agentTypeDefinitionSchema.safeParse(upsertsRaw[index]);
|
|
285
|
+
if (!parsed.success) {
|
|
286
|
+
issues.push({
|
|
287
|
+
path: `upserts.${index}`,
|
|
288
|
+
message: "Invalid agent type definition",
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
upserts.push(normalizeAgentTypeDraft(parsed.data));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const deletes = Array.isArray(input.deletes)
|
|
296
|
+
? input.deletes
|
|
297
|
+
.map(value => (typeof value === "string" ? value.trim() : ""))
|
|
298
|
+
.filter(Boolean)
|
|
299
|
+
: [];
|
|
300
|
+
|
|
301
|
+
const seenUpserts = new Set<string>();
|
|
302
|
+
for (let index = 0; index < upserts.length; index += 1) {
|
|
303
|
+
const id = upserts[index]?.id;
|
|
304
|
+
if (!id) continue;
|
|
305
|
+
if (seenUpserts.has(id)) {
|
|
306
|
+
issues.push({
|
|
307
|
+
path: `upserts.${index}.id`,
|
|
308
|
+
message: `Duplicate upsert id: ${id}`,
|
|
309
|
+
});
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
seenUpserts.add(id);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const config = getConfigSnapshot().config;
|
|
316
|
+
try {
|
|
317
|
+
const providers = await loadProviderModelMap(config);
|
|
318
|
+
for (let index = 0; index < upserts.length; index += 1) {
|
|
319
|
+
const upsert = upserts[index];
|
|
320
|
+
if (!upsert?.model?.trim()) continue;
|
|
321
|
+
const parsed = parseModelRef(upsert.model, config.runtime.opencode.providerId);
|
|
322
|
+
if (!parsed) {
|
|
323
|
+
issues.push({
|
|
324
|
+
path: `upserts.${index}.model`,
|
|
325
|
+
message: `Invalid model reference: ${upsert.model}`,
|
|
326
|
+
});
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const models = providers.get(parsed.providerId);
|
|
330
|
+
if (!models) {
|
|
331
|
+
issues.push({
|
|
332
|
+
path: `upserts.${index}.model`,
|
|
333
|
+
message: `Unknown provider: ${parsed.providerId}`,
|
|
334
|
+
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (!models.has(parsed.modelId)) {
|
|
338
|
+
issues.push({
|
|
339
|
+
path: `upserts.${index}.model`,
|
|
340
|
+
message: `Unknown model: ${parsed.providerId}/${parsed.modelId}`,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
warnings.push({
|
|
346
|
+
path: "modelValidation",
|
|
347
|
+
message: error instanceof Error ? error.message : "Unable to verify models against runtime providers",
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
ok: issues.length === 0,
|
|
353
|
+
normalized: {
|
|
354
|
+
upserts,
|
|
355
|
+
deletes: [...new Set(deletes)].sort((a, b) => a.localeCompare(b)),
|
|
356
|
+
},
|
|
357
|
+
issues,
|
|
358
|
+
warnings,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function patchOpencodeAgentTypes(input: {
|
|
363
|
+
upserts: AgentTypeDefinition[];
|
|
364
|
+
deletes: string[];
|
|
365
|
+
expectedHash: string;
|
|
366
|
+
}) {
|
|
367
|
+
const config = getConfigSnapshot().config;
|
|
368
|
+
const storage = getOpencodeAgentStorageInfo(config);
|
|
369
|
+
const currentConfig = await getOpencodeConfig(config);
|
|
370
|
+
const currentMap = normalizeRuntimeAgentConfigMap((currentConfig as Record<string, unknown>).agent);
|
|
371
|
+
const currentAgentTypes = Object.entries(currentMap)
|
|
372
|
+
.map(([id, entry]) => toAgentTypeDefinition(id, entry))
|
|
373
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
374
|
+
const currentHash = hashAgentTypes(currentAgentTypes);
|
|
375
|
+
if (currentHash !== input.expectedHash.trim()) {
|
|
376
|
+
return {
|
|
377
|
+
ok: false as const,
|
|
378
|
+
status: 409,
|
|
379
|
+
error: "Agent definitions changed since last load; refresh and retry.",
|
|
380
|
+
currentHash,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const fileConfigPath = resolveOpencodeConfigFile(config);
|
|
385
|
+
const nextMap: Record<string, OpenCodeAgentConfigRecord> = { ...readFileAgentConfigMap(fileConfigPath) };
|
|
386
|
+
const deletedAgentFiles: string[] = [];
|
|
387
|
+
for (const id of input.deletes) {
|
|
388
|
+
const normalized = id.trim();
|
|
389
|
+
if (!normalized) continue;
|
|
390
|
+
if (NON_DELETABLE_BUILTIN_AGENT_IDS.has(normalized)) {
|
|
391
|
+
return {
|
|
392
|
+
ok: false as const,
|
|
393
|
+
status: 400,
|
|
394
|
+
error: `Cannot delete built-in agent type: ${normalized}`,
|
|
395
|
+
currentHash,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
delete nextMap[normalized];
|
|
399
|
+
deletedAgentFiles.push(...deleteAgentMarkdownFiles(config, normalized));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (const upsert of input.upserts) {
|
|
403
|
+
const normalized = normalizeAgentTypeDraft(upsert);
|
|
404
|
+
const id = normalized.id.trim();
|
|
405
|
+
if (!id) continue;
|
|
406
|
+
nextMap[id] = toOpenCodeAgentConfig(normalized, nextMap[id]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const nextAgentMap = Object.fromEntries(Object.entries(nextMap).sort(([left], [right]) => left.localeCompare(right)));
|
|
410
|
+
writeFileAgentConfigMap(fileConfigPath, nextAgentMap);
|
|
411
|
+
await disposeOpencodeInstance(config);
|
|
412
|
+
|
|
413
|
+
const refreshed = await listOpencodeAgentTypes();
|
|
414
|
+
return {
|
|
415
|
+
ok: true as const,
|
|
416
|
+
agentTypes: refreshed.agentTypes,
|
|
417
|
+
hash: refreshed.hash,
|
|
418
|
+
storage,
|
|
419
|
+
applied: {
|
|
420
|
+
upserted: input.upserts.map(agent => agent.id).sort((a, b) => a.localeCompare(b)),
|
|
421
|
+
deleted: input.deletes.slice().sort((a, b) => a.localeCompare(b)),
|
|
422
|
+
deletedAgentFiles: deletedAgentFiles.sort((a, b) => a.localeCompare(b)),
|
|
423
|
+
configFilePath: fileConfigPath,
|
|
424
|
+
workspaceDirectory: storage.workspaceDirectory,
|
|
425
|
+
configDirectory: storage.configDirectory,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agentTypeToLegacySpecialist,
|
|
3
|
+
} from "@agent-mockingbird/contracts/agentTypes";
|
|
4
|
+
import type { SpecialistAgent } from "@agent-mockingbird/contracts/dashboard";
|
|
5
|
+
|
|
6
|
+
import type { AgentTypeDefinition } from "../config/schema";
|
|
7
|
+
|
|
8
|
+
export function toLegacySpecialistAgent(agentType: AgentTypeDefinition): SpecialistAgent {
|
|
9
|
+
return agentTypeToLegacySpecialist(agentType) as SpecialistAgent;
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
import { agentMockingbirdConfigSchema } from "./schema";
|
|
5
|
+
import { resolveExampleConfigPath } from "./testFixtures";
|
|
6
|
+
|
|
7
|
+
test("agent-mockingbird.config.example.json stays aligned with schema", () => {
|
|
8
|
+
const filePath = resolveExampleConfigPath();
|
|
9
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
const runtime = raw.runtime as Record<string, unknown> | undefined;
|
|
12
|
+
const opencode = runtime?.opencode as Record<string, unknown> | undefined;
|
|
13
|
+
const heartbeat = runtime?.heartbeat as Record<string, unknown> | undefined;
|
|
14
|
+
expect(typeof opencode?.childSessionHideAfterDays).toBe("number");
|
|
15
|
+
expect(typeof heartbeat?.model).toBe("string");
|
|
16
|
+
expect(typeof runtime?.configPolicy).toBe("object");
|
|
17
|
+
|
|
18
|
+
const parsed = agentMockingbirdConfigSchema.safeParse(raw);
|
|
19
|
+
expect(parsed.success).toBe(true);
|
|
20
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { AgentMockingbirdConfig } from "./schema";
|
|
2
|
+
import { getConfigSnapshot } from "./service";
|
|
3
|
+
import {
|
|
4
|
+
connectRuntimeMcp,
|
|
5
|
+
disconnectRuntimeMcp,
|
|
6
|
+
listRuntimeMcps,
|
|
7
|
+
normalizeMcpIds,
|
|
8
|
+
removeRuntimeMcpAuth,
|
|
9
|
+
resolveConfiguredMcpIds,
|
|
10
|
+
resolveConfiguredMcpServers,
|
|
11
|
+
startRuntimeMcpAuth,
|
|
12
|
+
} from "../mcp/service";
|
|
13
|
+
import {
|
|
14
|
+
disposeOpencodeSkillInstance,
|
|
15
|
+
getDisabledSkillsRootPath,
|
|
16
|
+
getManagedSkillsRootPath,
|
|
17
|
+
listManagedSkillCatalog,
|
|
18
|
+
normalizeSkillId,
|
|
19
|
+
removeManagedSkill,
|
|
20
|
+
reconcileManagedSkillEnabledSet,
|
|
21
|
+
setManagedSkillEnabled,
|
|
22
|
+
writeManagedSkill,
|
|
23
|
+
} from "../skills/service";
|
|
24
|
+
|
|
25
|
+
interface RuntimeMcpSnapshot {
|
|
26
|
+
id: string;
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
status: "connected" | "disabled" | "failed" | "needs_auth" | "needs_client_registration" | "unknown";
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type RuntimeMcpAction = "connect" | "disconnect" | "authStart" | "authRemove";
|
|
33
|
+
|
|
34
|
+
interface RuntimeMcpActionResult {
|
|
35
|
+
id: string;
|
|
36
|
+
hash: string;
|
|
37
|
+
mcps: RuntimeMcpSnapshot[];
|
|
38
|
+
connected?: boolean;
|
|
39
|
+
disconnected?: boolean;
|
|
40
|
+
authorizationUrl?: string;
|
|
41
|
+
authRemoved?: { success: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface RuntimeSkillCatalogResult {
|
|
45
|
+
status: 200;
|
|
46
|
+
payload: {
|
|
47
|
+
skills: Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
location: string;
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
managed: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
enabled: string[];
|
|
56
|
+
disabled: string[];
|
|
57
|
+
invalid: Array<{
|
|
58
|
+
id?: string;
|
|
59
|
+
location: string;
|
|
60
|
+
reason: string;
|
|
61
|
+
}>;
|
|
62
|
+
hash: string;
|
|
63
|
+
revision: string;
|
|
64
|
+
managedPath: string;
|
|
65
|
+
disabledPath: string;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RuntimeMcpCatalogResult {
|
|
70
|
+
status: 200 | 502;
|
|
71
|
+
payload: {
|
|
72
|
+
mcps: RuntimeMcpSnapshot[];
|
|
73
|
+
enabled: string[];
|
|
74
|
+
servers: ReturnType<typeof resolveConfiguredMcpServers>;
|
|
75
|
+
hash: string;
|
|
76
|
+
error?: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function runRuntimeMcpAction(
|
|
81
|
+
config: AgentMockingbirdConfig,
|
|
82
|
+
id: string,
|
|
83
|
+
action: RuntimeMcpAction,
|
|
84
|
+
hash: string,
|
|
85
|
+
): Promise<RuntimeMcpActionResult> {
|
|
86
|
+
if (action === "connect") {
|
|
87
|
+
const connected = await connectRuntimeMcp(config, id);
|
|
88
|
+
const mcps = await listRuntimeMcps(config);
|
|
89
|
+
return { id, connected, mcps, hash };
|
|
90
|
+
}
|
|
91
|
+
if (action === "disconnect") {
|
|
92
|
+
const disconnected = await disconnectRuntimeMcp(config, id);
|
|
93
|
+
const mcps = await listRuntimeMcps(config);
|
|
94
|
+
return { id, disconnected, mcps, hash };
|
|
95
|
+
}
|
|
96
|
+
if (action === "authStart") {
|
|
97
|
+
const authorizationUrl = await startRuntimeMcpAuth(config, id);
|
|
98
|
+
const mcps = await listRuntimeMcps(config);
|
|
99
|
+
return { id, authorizationUrl, mcps, hash };
|
|
100
|
+
}
|
|
101
|
+
const authRemoved = await removeRuntimeMcpAuth(config, id);
|
|
102
|
+
const mcps = await listRuntimeMcps(config);
|
|
103
|
+
return { id, authRemoved, mcps, hash };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function runRuntimeMcpActionForCurrentConfig(
|
|
107
|
+
id: string,
|
|
108
|
+
action: RuntimeMcpAction,
|
|
109
|
+
): Promise<RuntimeMcpActionResult> {
|
|
110
|
+
const snapshot = getConfigSnapshot();
|
|
111
|
+
return runRuntimeMcpAction(snapshot.config, id, action, snapshot.hash);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function loadRuntimeSkillCatalog(): Promise<RuntimeSkillCatalogResult> {
|
|
115
|
+
const snapshot = getConfigSnapshot();
|
|
116
|
+
const catalog = listManagedSkillCatalog(snapshot.config.runtime.opencode.directory);
|
|
117
|
+
return {
|
|
118
|
+
status: 200,
|
|
119
|
+
payload: {
|
|
120
|
+
skills: catalog.skills,
|
|
121
|
+
enabled: catalog.enabled,
|
|
122
|
+
disabled: catalog.disabled,
|
|
123
|
+
invalid: catalog.invalid,
|
|
124
|
+
hash: catalog.revision,
|
|
125
|
+
revision: catalog.revision,
|
|
126
|
+
managedPath: catalog.enabledPath,
|
|
127
|
+
disabledPath: catalog.disabledPath,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function loadRuntimeMcpCatalog(): Promise<RuntimeMcpCatalogResult> {
|
|
133
|
+
const snapshot = getConfigSnapshot();
|
|
134
|
+
try {
|
|
135
|
+
const mcps = await listRuntimeMcps(snapshot.config);
|
|
136
|
+
return {
|
|
137
|
+
status: 200,
|
|
138
|
+
payload: {
|
|
139
|
+
mcps,
|
|
140
|
+
enabled: resolveConfiguredMcpIds(snapshot.config),
|
|
141
|
+
servers: resolveConfiguredMcpServers(snapshot.config),
|
|
142
|
+
hash: snapshot.hash,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : "Failed to load runtime MCP servers";
|
|
147
|
+
const enabled = resolveConfiguredMcpIds(snapshot.config);
|
|
148
|
+
return {
|
|
149
|
+
status: 502,
|
|
150
|
+
payload: {
|
|
151
|
+
mcps: normalizeMcpIds(enabled).map(id => ({
|
|
152
|
+
id,
|
|
153
|
+
enabled: true,
|
|
154
|
+
status: "unknown",
|
|
155
|
+
})),
|
|
156
|
+
enabled,
|
|
157
|
+
servers: resolveConfiguredMcpServers(snapshot.config),
|
|
158
|
+
hash: snapshot.hash,
|
|
159
|
+
error: message,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function importManagedSkillWithConfigUpdate(input: {
|
|
166
|
+
rawId: string;
|
|
167
|
+
content: string;
|
|
168
|
+
enable: boolean;
|
|
169
|
+
expectedHash?: string;
|
|
170
|
+
}): Promise<{
|
|
171
|
+
imported: { id: string; filePath: string };
|
|
172
|
+
skills: string[];
|
|
173
|
+
hash: string;
|
|
174
|
+
}> {
|
|
175
|
+
const id = normalizeSkillId(input.rawId);
|
|
176
|
+
const trimmedContent = input.content.trim();
|
|
177
|
+
if (!trimmedContent) {
|
|
178
|
+
throw new Error("skill content is required");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let created = false;
|
|
182
|
+
try {
|
|
183
|
+
const current = getConfigSnapshot();
|
|
184
|
+
const before = listManagedSkillCatalog(current.config.runtime.opencode.directory);
|
|
185
|
+
if (input.expectedHash && input.expectedHash !== before.revision && input.expectedHash !== current.hash) {
|
|
186
|
+
throw new Error("Skill catalog has changed; refresh and retry");
|
|
187
|
+
}
|
|
188
|
+
const writeResult = writeManagedSkill({
|
|
189
|
+
id,
|
|
190
|
+
content: trimmedContent,
|
|
191
|
+
overwrite: false,
|
|
192
|
+
workspaceDir: current.config.runtime.opencode.directory,
|
|
193
|
+
enabled: true,
|
|
194
|
+
});
|
|
195
|
+
created = writeResult.created;
|
|
196
|
+
if (!input.enable) {
|
|
197
|
+
setManagedSkillEnabled(id, false, current.config.runtime.opencode.directory);
|
|
198
|
+
}
|
|
199
|
+
await disposeOpencodeSkillInstance(current.config);
|
|
200
|
+
const next = listManagedSkillCatalog(current.config.runtime.opencode.directory);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
imported: {
|
|
204
|
+
id,
|
|
205
|
+
filePath: writeResult.filePath,
|
|
206
|
+
},
|
|
207
|
+
skills: next.enabled,
|
|
208
|
+
hash: next.revision,
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (created) {
|
|
212
|
+
const current = getConfigSnapshot();
|
|
213
|
+
removeManagedSkill(id, current.config.runtime.opencode.directory);
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function setEnabledSkillsFromCatalog(input: {
|
|
220
|
+
skills: string[];
|
|
221
|
+
expectedHash?: string;
|
|
222
|
+
}): Promise<{ skills: string[]; hash: string }> {
|
|
223
|
+
const current = getConfigSnapshot();
|
|
224
|
+
const before = listManagedSkillCatalog(current.config.runtime.opencode.directory);
|
|
225
|
+
if (input.expectedHash && input.expectedHash !== before.revision && input.expectedHash !== current.hash) {
|
|
226
|
+
throw new Error("Skill catalog has changed; refresh and retry");
|
|
227
|
+
}
|
|
228
|
+
reconcileManagedSkillEnabledSet(input.skills, current.config.runtime.opencode.directory);
|
|
229
|
+
await disposeOpencodeSkillInstance(current.config);
|
|
230
|
+
const next = listManagedSkillCatalog(current.config.runtime.opencode.directory);
|
|
231
|
+
return { skills: next.enabled, hash: next.revision };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function getEnabledSkillsFromCatalog() {
|
|
235
|
+
const current = getConfigSnapshot();
|
|
236
|
+
const catalog = listManagedSkillCatalog(current.config.runtime.opencode.directory);
|
|
237
|
+
return {
|
|
238
|
+
skills: catalog.enabled,
|
|
239
|
+
hash: catalog.revision,
|
|
240
|
+
managedPath: getManagedSkillsRootPath(current.config.runtime.opencode.directory),
|
|
241
|
+
disabledPath: getDisabledSkillsRootPath(current.config.runtime.opencode.directory),
|
|
242
|
+
};
|
|
243
|
+
}
|