diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82d8473..9bfdabd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,12 @@ jobs: echo " [typecheck] packages/$pkg" (cd packages/$pkg && bun run typecheck) || exit 1 done + - name: Typecheck plugins + run: | + for plugin in agent-workbench-hermes; do + echo " [typecheck] plugins/$plugin" + (cd plugins/$plugin && bun run typecheck) || exit 1 + done - name: Typecheck apps run: | for app in server tui cli mobile-web dashboard; do diff --git a/bun.lock b/bun.lock index 6cccdbe..0060c08 100644 --- a/bun.lock +++ b/bun.lock @@ -305,6 +305,17 @@ "name": "@agent-workbench/ui", "version": "0.0.0", }, + "plugins/agent-workbench-hermes": { + "name": "@agent-workbench/hermes-bridge", + "version": "0.0.0", + "dependencies": { + "@agent-workbench/plugin-sdk": "*", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "@types/node": "^26.1.0", + }, + }, "tests": { "name": "@agent-workbench/tests", "version": "0.0.0", @@ -356,6 +367,8 @@ "@agent-workbench/events": ["@agent-workbench/events@workspace:packages/events"], + "@agent-workbench/hermes-bridge": ["@agent-workbench/hermes-bridge@workspace:plugins/agent-workbench-hermes"], + "@agent-workbench/mobile-web": ["@agent-workbench/mobile-web@workspace:apps/mobile-web"], "@agent-workbench/models": ["@agent-workbench/models@workspace:packages/models"], @@ -1226,7 +1239,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], "@types/pegjs": ["@types/pegjs@0.10.6", "", {}, "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw=="], @@ -2642,6 +2655,10 @@ "@opentui/solid/solid-js": ["solid-js@1.9.12", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw=="], + "@slack/logger/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + + "@slack/web-api/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], @@ -2654,6 +2671,10 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/cors/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + + "@types/ws/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "@typespec/ts-http-runtime/http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -2674,6 +2695,8 @@ "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "bun-types/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "cli-progress/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -2688,6 +2711,8 @@ "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "engine.io/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "engine.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -2940,6 +2965,8 @@ "mongodb-connection-string-url/whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + "onnxruntime-web/protobufjs/@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "rimraf/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], diff --git a/docs/27_PROJECT_ROADMAP.md b/docs/27_PROJECT_ROADMAP.md index 04fef57..bf5bfd1 100644 --- a/docs/27_PROJECT_ROADMAP.md +++ b/docs/27_PROJECT_ROADMAP.md @@ -1,6 +1,6 @@ # 27 — Project Roadmap -Status: Phase 27 complete — Phase 29 (model experimentation & eval) in progress +Status: Phase 29 complete — Phase 30 (enterprise readiness) next Document type: Roadmap for Phases 19–30 Supersedes: incremental updates in docs/04_IMPLEMENTATION_PHASE_CHECKLIST.md @@ -21,8 +21,8 @@ Phase 25 ✅ complete ███████████████████ Phase 26 ✅ complete ██████████████████████ plugin system & extensibility Phase 27 ✅ complete ██████████████████████ remote access & collaboration Phase 28 ⏸️ ░░░░░░░░░░░░░░░░░░░░ ⏸️ desktop application (deferred) -Phase 29 ▌ ░░░░░░░░░░░░░░░░░░░░ model experimentation & eval -Phase 30 ░░░░░░░░░░░░░░░░░░░░░░░░░ enterprise readiness & compliance +Phase 29 ✅ complete ██████████████████████ model experimentation & eval +Phase 30 ▌ ░░░░░░░░░░░░░░░░░░░░ enterprise readiness & compliance ``` ### Timeline @@ -284,7 +284,7 @@ These bridges connect agent-workbench with existing developer tooling: [ ] GDPR: right to access, right to delete endpoints [ ] Supply chain: SBOM generation, dependency vulnerability scanning [ ] FIPS 140-2 compliance for cryptographic operations -[ ] Hermes Agent bridge auto-discovers provider config from ~/.hermes/ +[x] Hermes Agent bridge auto-discovers provider config from ~/.hermes/ [ ] OpenCode bridge syncs provider registry bidirectionally ``` @@ -360,5 +360,5 @@ Dependencies: Phase N --- -*Last updated: 2026-07-03 (Phase 29 in progress — prompt library, playground, ModelComparer committed)* -*Next review: After Phase 29 completion* +*Last updated: 2026-07-03 (Phase 29 complete, Phase 30 started — Hermes bridge)* +*Next review: After Phase 30 completion* diff --git a/package.json b/package.json index 3a19c62..ae4b12e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "workspaces": [ "apps/*", "packages/*", - "tests" + "tests", + "plugins/*" ], "scripts": { "phase": "echo Phase 1 workspace scaffold only", diff --git a/plugins/agent-workbench-hermes/package.json b/plugins/agent-workbench-hermes/package.json new file mode 100644 index 0000000..4591f53 --- /dev/null +++ b/plugins/agent-workbench-hermes/package.json @@ -0,0 +1,26 @@ +{ + "name": "@agent-workbench/hermes-bridge", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Hermes Agent Bridge — auto-discovers Hermes providers and maps them to agent-workbench", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@agent-workbench/plugin-sdk": "*" + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "@types/node": "^26.1.0" + } +} diff --git a/plugins/agent-workbench-hermes/src/copilot-adapter.ts b/plugins/agent-workbench-hermes/src/copilot-adapter.ts index 90382f3..f18c37c 100644 --- a/plugins/agent-workbench-hermes/src/copilot-adapter.ts +++ b/plugins/agent-workbench-hermes/src/copilot-adapter.ts @@ -41,7 +41,7 @@ export class CopilotAdapter { async call( messages: PluginModelMessage[], tools?: PluginToolDefinition[], - signal?: AbortSignal, + signal?: AbortSignal | null, ): Promise { const body = this.buildBody(messages, tools, false); const headers = this.buildHeaders(); @@ -50,7 +50,7 @@ export class CopilotAdapter { method: "POST", headers, body: JSON.stringify(body), - signal, + signal: signal ?? null, }); if (!response.ok) { @@ -70,19 +70,21 @@ export class CopilotAdapter { return { content, - usage: usage + ...(usage ? { - inputTokens: (usage.prompt_tokens as number) ?? 0, - outputTokens: (usage.completion_tokens as number) ?? 0, + usage: { + inputTokens: (usage.prompt_tokens as number) ?? 0, + outputTokens: (usage.completion_tokens as number) ?? 0, + }, } - : undefined, + : {}), }; } async *stream( messages: PluginModelMessage[], tools?: PluginToolDefinition[], - signal?: AbortSignal, + signal?: AbortSignal | null, ): AsyncGenerator { const body = this.buildBody(messages, tools, true); const headers = this.buildHeaders(); @@ -91,7 +93,7 @@ export class CopilotAdapter { method: "POST", headers, body: JSON.stringify(body), - signal, + signal: signal ?? null, }); if (!response.ok) { diff --git a/plugins/agent-workbench-hermes/src/hermes-config.ts b/plugins/agent-workbench-hermes/src/hermes-config.ts index dd221c5..f46033b 100644 --- a/plugins/agent-workbench-hermes/src/hermes-config.ts +++ b/plugins/agent-workbench-hermes/src/hermes-config.ts @@ -18,9 +18,9 @@ export interface HermesProviderEntry { /** Model name (e.g. "deepseek-v4-flash", "kimi-k2.7-code"). */ readonly model: string; /** Base URL from credentials (if available). */ - readonly baseUrl?: string; + readonly baseUrl?: string | undefined; /** API key from credentials. */ - readonly apiKey?: string; + readonly apiKey?: string | undefined; /** Whether this is the primary (default) provider. */ readonly isPrimary: boolean; } @@ -135,8 +135,8 @@ function parseConfig(raw: string, auth: AuthFile | null): HermesConfig { // Look backwards for the default model for (let j = i - 1; j >= 0 && j > i - 10; j--) { const prev = lines[j]?.trim(); - if (prev.startsWith("default:")) { - const model = prev.slice("default:".length).trim(); + if (prev?.startsWith("default:")) { + const model = prev!.slice("default:".length).trim(); if (model) { addEntry(entries, provider, model, true, auth); } @@ -169,7 +169,7 @@ function addEntry( entries.push({ provider, model, - baseUrl: cred?.base_url, + ...(cred?.base_url ? { baseUrl: cred.base_url } : {}), apiKey: resolveApiKey(cred), isPrimary, }); diff --git a/plugins/agent-workbench-hermes/src/openai-adapter.ts b/plugins/agent-workbench-hermes/src/openai-adapter.ts index d73171f..7a8e66d 100644 --- a/plugins/agent-workbench-hermes/src/openai-adapter.ts +++ b/plugins/agent-workbench-hermes/src/openai-adapter.ts @@ -40,7 +40,7 @@ export class OpenAIAdapter { async call( messages: PluginModelMessage[], _tools?: PluginToolDefinition[], - signal?: AbortSignal, + signal?: AbortSignal | null, ): Promise { const body = this.buildBody(messages, _tools, false); @@ -51,7 +51,7 @@ export class OpenAIAdapter { Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify(body), - signal, + signal: signal ?? null, }); if (!response.ok) { @@ -76,27 +76,33 @@ export class OpenAIAdapter { return { content, - toolCalls: toolCallsRaw?.map((tc) => ({ - id: tc.id as string, - name: (tc.function as Record)?.name as string, - arguments: JSON.parse( - ((tc.function as Record)?.arguments as string) ?? - "{}", - ) as Record, - })), - usage: usage + ...(toolCallsRaw + ? { + toolCalls: toolCallsRaw.map((tc) => ({ + id: tc.id as string, + name: (tc.function as Record)?.name as string, + arguments: JSON.parse( + ((tc.function as Record) + ?.arguments as string) ?? "{}", + ) as Record, + })), + } + : {}), + ...(usage ? { - inputTokens: (usage.prompt_tokens as number) ?? 0, - outputTokens: (usage.completion_tokens as number) ?? 0, + usage: { + inputTokens: (usage.prompt_tokens as number) ?? 0, + outputTokens: (usage.completion_tokens as number) ?? 0, + }, } - : undefined, + : {}), }; } async *stream( messages: PluginModelMessage[], _tools?: PluginToolDefinition[], - signal?: AbortSignal, + signal?: AbortSignal | null, ): AsyncGenerator { const body = this.buildBody(messages, _tools, true); @@ -107,7 +113,7 @@ export class OpenAIAdapter { Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify(body), - signal, + signal: signal ?? null, }); if (!response.ok) { diff --git a/plugins/agent-workbench-hermes/tsconfig.json b/plugins/agent-workbench-hermes/tsconfig.json new file mode 100644 index 0000000..2e00981 --- /dev/null +++ b/plugins/agent-workbench-hermes/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "types": ["bun"] + }, + "include": ["src/**/*.ts"] +} diff --git a/scripts/build-all.sh b/scripts/build-all.sh index 67bf488..1422bc2 100755 --- a/scripts/build-all.sh +++ b/scripts/build-all.sh @@ -18,6 +18,10 @@ for pkg in events sdk shell permissions cache planner collab eval; do (cd "$ROOT/packages/$pkg" && bun run build 2>&1) || exit 1 done +# Plugins +echo " [build] plugins/agent-workbench-hermes" +(cd "$ROOT/plugins/agent-workbench-hermes" && bun run build 2>&1) || exit 1 + # Level 2: depends on cache, diff, protocol, shell, storage echo " [build] packages/tools" (cd "$ROOT/packages/tools" && bun run build 2>&1) || exit 1