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,135 @@
|
|
|
1
|
+
import type { Message, OpencodeClient, Part } from "@opencode-ai/sdk/client";
|
|
2
|
+
|
|
3
|
+
import type { RuntimeEngine } from "../contracts/runtime";
|
|
4
|
+
import { createOpencodeClient } from "../opencode/client";
|
|
5
|
+
import { getLaneQueue } from "../queue/service";
|
|
6
|
+
import type { OpencodeRuntimeBackgroundMethods } from "./opencodeRuntime/backgroundMethods";
|
|
7
|
+
import { opencodeRuntimeBackgroundMethods } from "./opencodeRuntime/backgroundMethods";
|
|
8
|
+
import type { OpencodeRuntimeCoreMethods } from "./opencodeRuntime/coreMethods";
|
|
9
|
+
import { opencodeRuntimeCoreMethods } from "./opencodeRuntime/coreMethods";
|
|
10
|
+
import type { OpencodeRuntimeEventMethods } from "./opencodeRuntime/eventMethods";
|
|
11
|
+
import { opencodeRuntimeEventMethods } from "./opencodeRuntime/eventMethods";
|
|
12
|
+
import type { OpencodeRuntimeMemoryMethods } from "./opencodeRuntime/memoryMethods";
|
|
13
|
+
import { opencodeRuntimeMemoryMethods } from "./opencodeRuntime/memoryMethods";
|
|
14
|
+
import type { OpencodeRuntimePromptMethods } from "./opencodeRuntime/promptMethods";
|
|
15
|
+
import { opencodeRuntimePromptMethods } from "./opencodeRuntime/promptMethods";
|
|
16
|
+
import type {
|
|
17
|
+
Listener,
|
|
18
|
+
MemoryInjectionStateEntry,
|
|
19
|
+
OpencodeRuntimeOptions,
|
|
20
|
+
RuntimeAgentCatalog,
|
|
21
|
+
RuntimeHealthSnapshot,
|
|
22
|
+
} from "./opencodeRuntime/shared";
|
|
23
|
+
|
|
24
|
+
class OpencodeRuntimeImpl {
|
|
25
|
+
private listeners = new Set<Listener>();
|
|
26
|
+
private client: OpencodeClient | null = null;
|
|
27
|
+
private clientConnectionKey: string | null = null;
|
|
28
|
+
private readonly disposeController = new AbortController();
|
|
29
|
+
private disposed = false;
|
|
30
|
+
private eventSyncStarted = false;
|
|
31
|
+
private busySessions = new Set<string>();
|
|
32
|
+
private healthSnapshot: RuntimeHealthSnapshot | null = null;
|
|
33
|
+
private healthCacheExpiresAtMs = 0;
|
|
34
|
+
private healthProbeInFlight: Promise<RuntimeHealthSnapshot> | null = null;
|
|
35
|
+
private runtimeConfigSyncKey: string | null = null;
|
|
36
|
+
private runtimeConfigSyncInFlight: Promise<void> | null = null;
|
|
37
|
+
private backgroundSyncStarted = false;
|
|
38
|
+
private backgroundSyncInFlight: Promise<void> | null = null;
|
|
39
|
+
private backgroundHydrationInFlight = new Set<string>();
|
|
40
|
+
private backgroundAnnouncementInFlight = new Set<string>();
|
|
41
|
+
private backgroundLastEmitByRunId = new Map<string, string>();
|
|
42
|
+
private backgroundMessageSyncAtByChildSessionId = new Map<string, number>();
|
|
43
|
+
private drainingSessions = new Set<string>();
|
|
44
|
+
private imageCapabilityByModelRef = new Map<string, boolean>();
|
|
45
|
+
private imageCapabilityFetchedAtMs = 0;
|
|
46
|
+
private messageRoleByScopedMessageId = new Map<string, Message["role"]>();
|
|
47
|
+
private partTypeByScopedPartId = new Map<string, Part["type"]>();
|
|
48
|
+
private memoryInjectionStateBySessionId = new Map<
|
|
49
|
+
string,
|
|
50
|
+
MemoryInjectionStateEntry
|
|
51
|
+
>();
|
|
52
|
+
private availableAgentNamesCache: {
|
|
53
|
+
fetchedAtMs: number;
|
|
54
|
+
catalog: RuntimeAgentCatalog;
|
|
55
|
+
} | null = null;
|
|
56
|
+
declare readonly sendUserMessage: RuntimeEngine["sendUserMessage"];
|
|
57
|
+
declare readonly checkHealth: RuntimeEngine["checkHealth"];
|
|
58
|
+
declare readonly syncSessionMessages: RuntimeEngine["syncSessionMessages"];
|
|
59
|
+
declare readonly abortSession: RuntimeEngine["abortSession"];
|
|
60
|
+
declare readonly compactSession: RuntimeEngine["compactSession"];
|
|
61
|
+
declare readonly startEventSync: OpencodeRuntimeEventMethods["startEventSync"];
|
|
62
|
+
declare readonly startBackgroundSync: OpencodeRuntimeEventMethods["startBackgroundSync"];
|
|
63
|
+
declare readonly clearAllTimers: OpencodeRuntimePromptMethods["clearAllTimers"];
|
|
64
|
+
|
|
65
|
+
constructor(private options: OpencodeRuntimeOptions) {
|
|
66
|
+
if (options.client) {
|
|
67
|
+
this.client = options.client;
|
|
68
|
+
} else if (!options.getRuntimeConfig) {
|
|
69
|
+
this.client = createOpencodeClient();
|
|
70
|
+
}
|
|
71
|
+
getLaneQueue().setDrainHandler(async (sessionId, messages) => {
|
|
72
|
+
for (const message of messages) {
|
|
73
|
+
await this.sendUserMessage({
|
|
74
|
+
sessionId,
|
|
75
|
+
content: message.content,
|
|
76
|
+
parts: message.parts,
|
|
77
|
+
agent: message.agent,
|
|
78
|
+
metadata: { ...message.metadata, __queueDrain: true },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (options.enableEventSync !== false) {
|
|
83
|
+
this.startEventSync();
|
|
84
|
+
}
|
|
85
|
+
if (options.enableBackgroundSync !== false) {
|
|
86
|
+
this.startBackgroundSync();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
subscribe(onEvent: Listener): () => void {
|
|
91
|
+
this.listeners.add(onEvent);
|
|
92
|
+
return () => {
|
|
93
|
+
this.listeners.delete(onEvent);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async dispose() {
|
|
98
|
+
if (this.disposed) return;
|
|
99
|
+
this.disposed = true;
|
|
100
|
+
this.disposeController.abort();
|
|
101
|
+
this.listeners.clear();
|
|
102
|
+
this.backgroundHydrationInFlight.clear();
|
|
103
|
+
this.backgroundAnnouncementInFlight.clear();
|
|
104
|
+
this.backgroundLastEmitByRunId.clear();
|
|
105
|
+
this.backgroundMessageSyncAtByChildSessionId.clear();
|
|
106
|
+
this.busySessions.clear();
|
|
107
|
+
this.drainingSessions.clear();
|
|
108
|
+
this.messageRoleByScopedMessageId.clear();
|
|
109
|
+
this.partTypeByScopedPartId.clear();
|
|
110
|
+
this.memoryInjectionStateBySessionId.clear();
|
|
111
|
+
if (this.backgroundSyncInFlight) {
|
|
112
|
+
await this.backgroundSyncInFlight.catch(() => {});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
Object.assign(
|
|
117
|
+
OpencodeRuntimeImpl.prototype,
|
|
118
|
+
opencodeRuntimeCoreMethods,
|
|
119
|
+
opencodeRuntimeMemoryMethods,
|
|
120
|
+
opencodeRuntimeEventMethods,
|
|
121
|
+
opencodeRuntimeBackgroundMethods,
|
|
122
|
+
opencodeRuntimePromptMethods,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
export type OpencodeRuntime = OpencodeRuntimeImpl &
|
|
126
|
+
OpencodeRuntimeCoreMethods &
|
|
127
|
+
OpencodeRuntimeEventMethods &
|
|
128
|
+
OpencodeRuntimeMemoryMethods &
|
|
129
|
+
OpencodeRuntimeBackgroundMethods &
|
|
130
|
+
OpencodeRuntimePromptMethods;
|
|
131
|
+
|
|
132
|
+
export const OpencodeRuntime = OpencodeRuntimeImpl as unknown as {
|
|
133
|
+
new (options: OpencodeRuntimeOptions): OpencodeRuntime;
|
|
134
|
+
prototype: OpencodeRuntime;
|
|
135
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CronJobDefinition } from "../cron/types";
|
|
2
|
+
import { getLocalSessionIdByRuntimeBinding } from "../db/repository";
|
|
3
|
+
import { isActiveHeartbeatSession } from "../heartbeat/state";
|
|
4
|
+
|
|
5
|
+
const OPENCODE_RUNTIME_ID = "opencode";
|
|
6
|
+
|
|
7
|
+
export interface SessionScopeResolver {
|
|
8
|
+
getJobByThreadSessionId(sessionId: string): CronJobDefinition | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RuntimeSessionScope {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
localSessionId: string | null;
|
|
14
|
+
isMain: boolean;
|
|
15
|
+
kind: "main" | "cron" | "heartbeat" | "other";
|
|
16
|
+
heartbeat: boolean;
|
|
17
|
+
cronJobId: string | null;
|
|
18
|
+
cronJobName: string | null;
|
|
19
|
+
mayNotifyMain: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveRuntimeSessionScope(
|
|
23
|
+
externalSessionId: string,
|
|
24
|
+
resolver: SessionScopeResolver,
|
|
25
|
+
): RuntimeSessionScope {
|
|
26
|
+
const sessionId = externalSessionId.trim();
|
|
27
|
+
const localSessionId = sessionId
|
|
28
|
+
? getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, sessionId)
|
|
29
|
+
: null;
|
|
30
|
+
const isMain = localSessionId === "main";
|
|
31
|
+
const heartbeat = localSessionId ? isActiveHeartbeatSession(localSessionId) : false;
|
|
32
|
+
const cronJob = localSessionId ? resolver.getJobByThreadSessionId(localSessionId) : null;
|
|
33
|
+
const kind = isMain ? "main" : heartbeat ? "heartbeat" : cronJob ? "cron" : "other";
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
sessionId,
|
|
37
|
+
localSessionId,
|
|
38
|
+
isMain,
|
|
39
|
+
kind,
|
|
40
|
+
heartbeat,
|
|
41
|
+
cronJobId: cronJob?.id ?? null,
|
|
42
|
+
cronJobName: cronJob?.name ?? null,
|
|
43
|
+
mayNotifyMain: kind === "cron" || kind === "heartbeat",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import type { Config } from "@opencode-ai/sdk/client";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
import type { AgentMockingbirdConfig } from "../config/schema";
|
|
18
|
+
import { createOpencodeClientFromConnection } from "../opencode/client";
|
|
19
|
+
|
|
20
|
+
const SKILL_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
21
|
+
const MANAGED_SKILLS_RELATIVE = path.join(".agents", "skills");
|
|
22
|
+
const DISABLED_SKILLS_RELATIVE = path.join(".agent-mockingbird", "disabled-skills");
|
|
23
|
+
|
|
24
|
+
interface RuntimeSkill {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
location: string;
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
managed: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ManagedSkillWriteResult {
|
|
34
|
+
id: string;
|
|
35
|
+
directoryPath: string;
|
|
36
|
+
filePath: string;
|
|
37
|
+
created: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface RuntimeSkillIssue {
|
|
41
|
+
id?: string;
|
|
42
|
+
location: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface RuntimeSkillCatalog {
|
|
47
|
+
skills: RuntimeSkill[];
|
|
48
|
+
enabled: string[];
|
|
49
|
+
disabled: string[];
|
|
50
|
+
invalid: RuntimeSkillIssue[];
|
|
51
|
+
revision: string;
|
|
52
|
+
enabledPath: string;
|
|
53
|
+
disabledPath: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveWorkspaceDirectory(workspaceDir?: string | null) {
|
|
57
|
+
const normalizedWorkspace = typeof workspaceDir === "string" ? workspaceDir.trim() : "";
|
|
58
|
+
return normalizedWorkspace ? path.resolve(normalizedWorkspace) : path.resolve(process.cwd());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveManagedSkillRoot(workspaceDir?: string | null) {
|
|
62
|
+
return path.join(resolveWorkspaceDirectory(workspaceDir), MANAGED_SKILLS_RELATIVE);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function workspaceFingerprint(workspaceDir?: string | null) {
|
|
66
|
+
const resolved = resolveWorkspaceDirectory(workspaceDir);
|
|
67
|
+
return createHash("sha256").update(resolved).digest("hex").slice(0, 16);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveDisabledSkillRoot(workspaceDir?: string | null) {
|
|
71
|
+
const homeDir = os.homedir();
|
|
72
|
+
return path.join(homeDir, DISABLED_SKILLS_RELATIVE, workspaceFingerprint(workspaceDir));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
76
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseSkillFrontmatter(content: string) {
|
|
80
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
81
|
+
if (!normalized.startsWith("---\n")) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false as const,
|
|
84
|
+
reason: "Missing YAML frontmatter",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const end = normalized.indexOf("\n---\n", 4);
|
|
88
|
+
if (end === -1) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false as const,
|
|
91
|
+
reason: "Invalid frontmatter delimiter",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const frontmatter = normalized.slice(4, end);
|
|
95
|
+
const record: Record<string, string> = {};
|
|
96
|
+
for (const line of frontmatter.split("\n")) {
|
|
97
|
+
const trimmed = line.trim();
|
|
98
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
99
|
+
const separator = trimmed.indexOf(":");
|
|
100
|
+
if (separator === -1) continue;
|
|
101
|
+
const key = trimmed.slice(0, separator).trim();
|
|
102
|
+
let value = trimmed.slice(separator + 1).trim();
|
|
103
|
+
if (!key) continue;
|
|
104
|
+
if (
|
|
105
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
106
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
107
|
+
) {
|
|
108
|
+
value = value.slice(1, -1);
|
|
109
|
+
}
|
|
110
|
+
record[key] = value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const name = record.name?.trim() ?? "";
|
|
114
|
+
const description = record.description?.trim() ?? "";
|
|
115
|
+
if (!name) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false as const,
|
|
118
|
+
reason: "Frontmatter missing required field: name",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (!description) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false as const,
|
|
124
|
+
reason: "Frontmatter missing required field: description",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
ok: true as const,
|
|
129
|
+
name,
|
|
130
|
+
description,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function moveDirectory(source: string, target: string) {
|
|
135
|
+
if (source === target) return;
|
|
136
|
+
if (!existsSync(source)) return;
|
|
137
|
+
if (existsSync(target)) {
|
|
138
|
+
throw new Error(`skill target already exists: ${target}`);
|
|
139
|
+
}
|
|
140
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
141
|
+
try {
|
|
142
|
+
renameSync(source, target);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const code = isObject(error) && typeof error.code === "string" ? error.code : "";
|
|
145
|
+
if (code !== "EXDEV") {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
cpSync(source, target, { recursive: true, force: false, errorOnExist: true });
|
|
149
|
+
rmSync(source, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function listSkillDirectories(root: string) {
|
|
154
|
+
if (!existsSync(root)) return [];
|
|
155
|
+
try {
|
|
156
|
+
return readdirSync(root, { withFileTypes: true })
|
|
157
|
+
.filter(entry => entry.isDirectory())
|
|
158
|
+
.map(entry => path.join(root, entry.name));
|
|
159
|
+
} catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function directoryMtimeMs(dir: string): number {
|
|
165
|
+
try {
|
|
166
|
+
return statSync(dir).mtimeMs;
|
|
167
|
+
} catch {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function skillInfoFromDirectory(directoryPath: string) {
|
|
173
|
+
const id = path.basename(directoryPath);
|
|
174
|
+
if (!SKILL_ID_PATTERN.test(id)) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false as const,
|
|
177
|
+
issue: {
|
|
178
|
+
id,
|
|
179
|
+
location: directoryPath,
|
|
180
|
+
reason: "Directory name is not a valid skill id",
|
|
181
|
+
} satisfies RuntimeSkillIssue,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const filePath = path.join(directoryPath, "SKILL.md");
|
|
185
|
+
if (!existsSync(filePath)) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false as const,
|
|
188
|
+
issue: {
|
|
189
|
+
id,
|
|
190
|
+
location: directoryPath,
|
|
191
|
+
reason: "Missing SKILL.md",
|
|
192
|
+
} satisfies RuntimeSkillIssue,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
let content = "";
|
|
196
|
+
try {
|
|
197
|
+
content = readFileSync(filePath, "utf8");
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false as const,
|
|
201
|
+
issue: {
|
|
202
|
+
id,
|
|
203
|
+
location: filePath,
|
|
204
|
+
reason: error instanceof Error ? error.message : "Failed to read SKILL.md",
|
|
205
|
+
} satisfies RuntimeSkillIssue,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
209
|
+
if (!frontmatter.ok) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false as const,
|
|
212
|
+
issue: {
|
|
213
|
+
id,
|
|
214
|
+
location: filePath,
|
|
215
|
+
reason: frontmatter.reason,
|
|
216
|
+
} satisfies RuntimeSkillIssue,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (frontmatter.name !== id) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false as const,
|
|
222
|
+
issue: {
|
|
223
|
+
id,
|
|
224
|
+
location: filePath,
|
|
225
|
+
reason: `Frontmatter name "${frontmatter.name}" must match directory "${id}"`,
|
|
226
|
+
} satisfies RuntimeSkillIssue,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
ok: true as const,
|
|
231
|
+
skill: {
|
|
232
|
+
id,
|
|
233
|
+
name: frontmatter.name,
|
|
234
|
+
description: frontmatter.description,
|
|
235
|
+
location: filePath,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function normalizePathList(values: unknown): string[] {
|
|
241
|
+
if (!Array.isArray(values)) return [];
|
|
242
|
+
const normalized = values
|
|
243
|
+
.map(value => (typeof value === "string" ? value.trim() : ""))
|
|
244
|
+
.filter(Boolean)
|
|
245
|
+
.map(value => path.resolve(value));
|
|
246
|
+
return [...new Set(normalized)];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function normalizeSkillIds(ids: Array<string>): string[] {
|
|
250
|
+
const normalized = ids.map(id => id.trim()).filter(Boolean);
|
|
251
|
+
return [...new Set(normalized)].sort((a, b) => a.localeCompare(b));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function normalizeSkillId(id: string) {
|
|
255
|
+
const normalized = id.trim();
|
|
256
|
+
if (!normalized) {
|
|
257
|
+
throw new Error("skill id is required");
|
|
258
|
+
}
|
|
259
|
+
if (!SKILL_ID_PATTERN.test(normalized)) {
|
|
260
|
+
throw new Error("skill id may only include letters, numbers, dot, underscore, or dash");
|
|
261
|
+
}
|
|
262
|
+
return normalized;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function getManagedSkillsRootPath(workspaceDir?: string | null) {
|
|
266
|
+
return resolveManagedSkillRoot(workspaceDir);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function getDisabledSkillsRootPath(workspaceDir?: string | null) {
|
|
270
|
+
return resolveDisabledSkillRoot(workspaceDir);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getManagedSkillDirectoryPath(skillId: string, workspaceDir?: string | null) {
|
|
274
|
+
return path.join(resolveManagedSkillRoot(workspaceDir), normalizeSkillId(skillId));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getDisabledSkillDirectoryPath(skillId: string, workspaceDir?: string | null) {
|
|
278
|
+
return path.join(resolveDisabledSkillRoot(workspaceDir), normalizeSkillId(skillId));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function writeManagedSkill(input: {
|
|
282
|
+
id: string;
|
|
283
|
+
content: string;
|
|
284
|
+
overwrite?: boolean;
|
|
285
|
+
workspaceDir?: string | null;
|
|
286
|
+
enabled?: boolean;
|
|
287
|
+
}): ManagedSkillWriteResult {
|
|
288
|
+
const id = normalizeSkillId(input.id);
|
|
289
|
+
const trimmedContent = input.content.trim();
|
|
290
|
+
if (!trimmedContent) {
|
|
291
|
+
throw new Error("skill content is required");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const directoryPath = input.enabled === false
|
|
295
|
+
? getDisabledSkillDirectoryPath(id, input.workspaceDir)
|
|
296
|
+
: getManagedSkillDirectoryPath(id, input.workspaceDir);
|
|
297
|
+
const filePath = path.join(directoryPath, "SKILL.md");
|
|
298
|
+
const created = !existsSync(filePath);
|
|
299
|
+
if (!created && input.overwrite !== true) {
|
|
300
|
+
throw new Error(`skill ${id} already exists in managed imports`);
|
|
301
|
+
}
|
|
302
|
+
mkdirSync(directoryPath, { recursive: true });
|
|
303
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
304
|
+
const serialized = `${trimmedContent}\n`;
|
|
305
|
+
writeFileSync(tempPath, serialized, "utf8");
|
|
306
|
+
renameSync(tempPath, filePath);
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
id,
|
|
310
|
+
directoryPath,
|
|
311
|
+
filePath,
|
|
312
|
+
created,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function removeManagedSkill(skillId: string, workspaceDir?: string | null) {
|
|
317
|
+
for (const directoryPath of [
|
|
318
|
+
getManagedSkillDirectoryPath(skillId, workspaceDir),
|
|
319
|
+
getDisabledSkillDirectoryPath(skillId, workspaceDir),
|
|
320
|
+
]) {
|
|
321
|
+
if (existsSync(directoryPath)) {
|
|
322
|
+
rmSync(directoryPath, { recursive: true, force: true });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function setManagedSkillEnabled(skillId: string, enabled: boolean, workspaceDir?: string | null) {
|
|
328
|
+
const id = normalizeSkillId(skillId);
|
|
329
|
+
const enabledPath = getManagedSkillDirectoryPath(id, workspaceDir);
|
|
330
|
+
const disabledPath = getDisabledSkillDirectoryPath(id, workspaceDir);
|
|
331
|
+
const source = enabled ? disabledPath : enabledPath;
|
|
332
|
+
const target = enabled ? enabledPath : disabledPath;
|
|
333
|
+
moveDirectory(source, target);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function reconcileManagedSkillEnabledSet(desiredIds: Array<string>, workspaceDir?: string | null) {
|
|
337
|
+
const desired = new Set(normalizeSkillIds(desiredIds));
|
|
338
|
+
for (const directoryPath of listSkillDirectories(getManagedSkillsRootPath(workspaceDir))) {
|
|
339
|
+
const id = path.basename(directoryPath);
|
|
340
|
+
if (!desired.has(id)) {
|
|
341
|
+
setManagedSkillEnabled(id, false, workspaceDir);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const directoryPath of listSkillDirectories(getDisabledSkillsRootPath(workspaceDir))) {
|
|
345
|
+
const id = path.basename(directoryPath);
|
|
346
|
+
if (desired.has(id)) {
|
|
347
|
+
setManagedSkillEnabled(id, true, workspaceDir);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function listManagedSkillCatalog(workspaceDir?: string | null): RuntimeSkillCatalog {
|
|
353
|
+
const enabledPath = getManagedSkillsRootPath(workspaceDir);
|
|
354
|
+
const disabledPath = getDisabledSkillsRootPath(workspaceDir);
|
|
355
|
+
mkdirSync(enabledPath, { recursive: true });
|
|
356
|
+
mkdirSync(disabledPath, { recursive: true });
|
|
357
|
+
|
|
358
|
+
const invalid: RuntimeSkillIssue[] = [];
|
|
359
|
+
const skillsById = new Map<string, RuntimeSkill>();
|
|
360
|
+
const enabledIds = new Set<string>();
|
|
361
|
+
const disabledIds = new Set<string>();
|
|
362
|
+
|
|
363
|
+
const register = (state: "enabled" | "disabled", directoryPath: string) => {
|
|
364
|
+
const parsed = skillInfoFromDirectory(directoryPath);
|
|
365
|
+
if (!parsed.ok) {
|
|
366
|
+
invalid.push(parsed.issue);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const current = skillsById.get(parsed.skill.id);
|
|
370
|
+
if (current) {
|
|
371
|
+
invalid.push({
|
|
372
|
+
id: parsed.skill.id,
|
|
373
|
+
location: parsed.skill.location,
|
|
374
|
+
reason: `Duplicate skill id detected (already loaded from ${current.location})`,
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const enabled = state === "enabled";
|
|
379
|
+
skillsById.set(parsed.skill.id, {
|
|
380
|
+
id: parsed.skill.id,
|
|
381
|
+
name: parsed.skill.name,
|
|
382
|
+
description: parsed.skill.description,
|
|
383
|
+
location: parsed.skill.location,
|
|
384
|
+
enabled,
|
|
385
|
+
managed: true,
|
|
386
|
+
});
|
|
387
|
+
if (enabled) {
|
|
388
|
+
enabledIds.add(parsed.skill.id);
|
|
389
|
+
} else {
|
|
390
|
+
disabledIds.add(parsed.skill.id);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
for (const directoryPath of listSkillDirectories(enabledPath)) {
|
|
395
|
+
register("enabled", directoryPath);
|
|
396
|
+
}
|
|
397
|
+
for (const directoryPath of listSkillDirectories(disabledPath)) {
|
|
398
|
+
register("disabled", directoryPath);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const skills = [...skillsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
402
|
+
const enabled = [...enabledIds].sort((a, b) => a.localeCompare(b));
|
|
403
|
+
const disabled = [...disabledIds].sort((a, b) => a.localeCompare(b));
|
|
404
|
+
invalid.sort((a, b) => `${a.id ?? ""}|${a.location}`.localeCompare(`${b.id ?? ""}|${b.location}`));
|
|
405
|
+
|
|
406
|
+
const revisionSource = JSON.stringify({
|
|
407
|
+
enabled,
|
|
408
|
+
disabled,
|
|
409
|
+
invalid,
|
|
410
|
+
enabledPath,
|
|
411
|
+
disabledPath,
|
|
412
|
+
enabledMtimeMs: directoryMtimeMs(enabledPath),
|
|
413
|
+
disabledMtimeMs: directoryMtimeMs(disabledPath),
|
|
414
|
+
});
|
|
415
|
+
const revision = createHash("sha256").update(revisionSource).digest("hex");
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
skills,
|
|
419
|
+
enabled,
|
|
420
|
+
disabled,
|
|
421
|
+
invalid,
|
|
422
|
+
revision,
|
|
423
|
+
enabledPath,
|
|
424
|
+
disabledPath,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export async function disposeOpencodeSkillInstance(config: AgentMockingbirdConfig) {
|
|
429
|
+
await createOpencodeClientFromConnection({
|
|
430
|
+
baseUrl: config.runtime.opencode.baseUrl,
|
|
431
|
+
directory: config.runtime.opencode.directory,
|
|
432
|
+
}).instance.dispose({
|
|
433
|
+
responseStyle: "data",
|
|
434
|
+
throwOnError: true,
|
|
435
|
+
signal: AbortSignal.timeout(config.runtime.opencode.timeoutMs),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function buildManagedSkillPaths(currentConfig: Config, workspaceDir?: string | null) {
|
|
440
|
+
const root = getManagedSkillsRootPath(workspaceDir);
|
|
441
|
+
return normalizePathList([root]);
|
|
442
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import type { AgentMockingbirdConfig } from "../config/schema";
|
|
4
|
+
import { getBinaryDir, resolveManagedOpencodeConfigDir } from "../paths";
|
|
5
|
+
|
|
6
|
+
function resolvePathFromBinaryRoot(dirPath: string) {
|
|
7
|
+
if (path.isAbsolute(dirPath)) return path.resolve(dirPath);
|
|
8
|
+
return path.resolve(getBinaryDir(), dirPath);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveOpencodeWorkspaceDir(config: AgentMockingbirdConfig) {
|
|
12
|
+
return resolvePathFromBinaryRoot(config.workspace.pinnedDirectory.trim());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveOpencodeConfigDir(config: AgentMockingbirdConfig) {
|
|
16
|
+
return resolveManagedOpencodeConfigDir(resolveOpencodeWorkspaceDir(config));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveWorkspaceAlignment(config: AgentMockingbirdConfig) {
|
|
20
|
+
const pinnedWorkspaceDir = resolvePathFromBinaryRoot(config.workspace.pinnedDirectory.trim());
|
|
21
|
+
return {
|
|
22
|
+
memoryWorkspaceDir: pinnedWorkspaceDir,
|
|
23
|
+
opencodeWorkspaceDir: pinnedWorkspaceDir,
|
|
24
|
+
opencodeDirectoryExplicit: true,
|
|
25
|
+
aligned: true,
|
|
26
|
+
};
|
|
27
|
+
}
|