summaryrefslogtreecommitdiffhomepage
path: root/packages/host-bin/src/main.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
committerAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
commita980aba97aed34692dd655e716747c4ff53fb186 (patch)
tree56b32ed9db8f66f9f6141eda923f5d1f7c1ba6d9 /packages/host-bin/src/main.ts
parent52ce1bb8b25cc6ba4ba7d2734c35c95e0a08d723 (diff)
downloaddispatch-a980aba97aed34692dd655e716747c4ff53fb186.tar.gz
dispatch-a980aba97aed34692dd655e716747c4ff53fb186.zip
host-bin: external-extension loader + claude credential wiring
Add loadExternalExtensions(): fault-isolated dynamic import of out-of-repo extensions declared via DISPATCH_EXTERNAL_EXTENSIONS. main.ts assembles the credential-store in boot() so a 'claude' credential is registered when an external anthropic provider is loaded; config.ts surfaces the anthropic model / credential-key settings those extensions read.
Diffstat (limited to 'packages/host-bin/src/main.ts')
-rw-r--r--packages/host-bin/src/main.ts42
1 files changed, 37 insertions, 5 deletions
diff --git a/packages/host-bin/src/main.ts b/packages/host-bin/src/main.ts
index b18f105..ec51d11 100644
--- a/packages/host-bin/src/main.ts
+++ b/packages/host-bin/src/main.ts
@@ -29,6 +29,7 @@ import { createTransportWsExtension } from "@dispatch/transport-ws";
import type { ChildHandle } from "./collector-supervisor.js";
import { createCollectorSupervisor } from "./collector-supervisor.js";
import { configMapToAccess, envToConfigMap } from "./config.js";
+import { loadExternalExtensions } from "./load-external.js";
function createEmptySecrets(): SecretsAccess {
return {
@@ -52,16 +53,15 @@ function createNoopEvents(): EventsEmitter {
return { emit: () => {} };
}
+// Core extensions EXCEPT the credential-store, which is assembled in boot() so
+// its credential list can include any credentials backed by external providers
+// (e.g. a `claude` credential once the external Anthropic provider is loaded).
const CORE_EXTENSIONS: readonly Extension[] = [
storageSqliteExt,
conversationStoreExt,
authApikeyExt,
providerOpenaiCompatExt,
toolReadFileExt,
- // MVP single hardcoded credential; future work makes it config/TOML-driven.
- createCredentialStoreExtension({
- credentials: [{ name: "opencode", providerId: "openai-compat" }],
- }),
sessionOrchestratorExt,
createTransportHttpExtension(),
// Surface extensions — dependency order: surface-registry first, then consumers.
@@ -70,6 +70,14 @@ const CORE_EXTENSIONS: readonly Extension[] = [
createLoadedExtensionsExtension(),
];
+/** Parse the comma-separated list of external extension module specifiers. */
+function parseExternalSpecifiers(env: Readonly<Record<string, string | undefined>>): string[] {
+ return (env.DISPATCH_EXTERNAL_EXTENSIONS ?? "")
+ .split(",")
+ .map((s) => s.trim())
+ .filter((s) => s.length > 0);
+}
+
async function boot(): Promise<void> {
const journalPath = process.env.DISPATCH_JOURNAL ?? "./.dispatch/journal/app.ndjson";
mkdirSync(dirname(journalPath), { recursive: true });
@@ -115,7 +123,31 @@ async function boot(): Promise<void> {
logDeps,
};
- const host = createHost(CORE_EXTENSIONS, deps);
+ // Load external (out-of-repo) extensions declared via DISPATCH_EXTERNAL_EXTENSIONS.
+ const externalSpecifiers = parseExternalSpecifiers(
+ process.env as Readonly<Record<string, string | undefined>>,
+ );
+ const externalExtensions = await loadExternalExtensions(externalSpecifiers, logger);
+
+ // Assemble the credential list. MVP keeps the hardcoded `opencode` credential
+ // and adds a `claude` credential when an external Anthropic provider is loaded.
+ const credentials = [{ name: "opencode", providerId: "openai-compat" }];
+ const hasAnthropic = externalExtensions.some((e) =>
+ e.manifest.contributes?.providers?.includes("anthropic"),
+ );
+ if (hasAnthropic) {
+ const claudeName = process.env.DISPATCH_CLAUDE_CREDENTIAL ?? "claude";
+ credentials.push({ name: claudeName, providerId: "anthropic" });
+ logger.info(`Registered credential "${claudeName}" → anthropic provider`);
+ }
+
+ const extensions: Extension[] = [
+ ...CORE_EXTENSIONS,
+ createCredentialStoreExtension({ credentials }),
+ ...externalExtensions,
+ ];
+
+ const host = createHost(extensions, deps);
await host.activate();
const disabled = host.getDisabled();