diff --git a/daemon/harness-bridge.js b/daemon/harness-bridge.js index 05eadbe..3b4fc56 100644 --- a/daemon/harness-bridge.js +++ b/daemon/harness-bridge.js @@ -6,12 +6,12 @@ import { createQvacTools } from '../skills/qvac-tools.js'; import { VOICE_SYSTEM_PROMPT, parseHudSidecar } from '../skills/voice-prompt.js'; import { createComputerObserveTools } from '../skills/computer-observe.js'; import { createComputerActTools } from '../skills/computer-act.js'; -import { createPhase9Tools } from '../skills/phase9-tools.js'; +import { createPhase9GatewayTool } from '../skills/phase9-tools.js'; export class HarnessBridge extends EventEmitter { constructor({ cwd = process.cwd(), model = 'qwen3.5-4b', tools = [], computer, observer, actuator, permissionMode = 'ask' } = {}) { super(); - this.options = { cwd, model, tools: [...createRuntimeTools({ computer }), ...createPhase2Tools({ cwd, computer }), ...createComputerObserveTools({ computer, observer }), ...createComputerActTools({ actuator }), ...createQvacTools(), ...createPhase9Tools(), ...tools], permissionMode, origin: 'jarvis-qvac', system: VOICE_SYSTEM_PROMPT }; + this.options = { cwd, model, tools: [...createRuntimeTools({ computer }), ...createPhase2Tools({ cwd, computer }), ...createComputerObserveTools({ computer, observer }), ...createComputerActTools({ actuator }), ...createQvacTools(), ...createPhase9GatewayTool(), ...tools], permissionMode, origin: 'jarvis-qvac', system: VOICE_SYSTEM_PROMPT }; this.session = null; } diff --git a/skills/phase9-tools.js b/skills/phase9-tools.js index 3e928cc..59df5eb 100644 --- a/skills/phase9-tools.js +++ b/skills/phase9-tools.js @@ -53,4 +53,34 @@ export function createPhase9Tools() { return tools; } +// The harness reserves 32 custom-tool slots. Keep the complete per-capability +// catalog available to callers, while exposing one gateway to the agent so +// every QVAC capability remains reachable without exceeding that limit. +export function createPhase9GatewayTool() { + const operations = [...specs.map(([name]) => name), 'job_cancel', 'p2p_status']; + return [{ + name: 'qvac_capability', + permission: 'write', + description: `Run one local QVAC capability through the single master adapter. Operations: ${operations.join(', ')}.`, + parameters: { + type: 'object', + properties: { + operation: { type: 'string', enum: operations }, + input: { type: 'object' }, + }, + required: ['operation'], + }, + execute: async ({ operation, input = {} } = {}) => { + if (operation === 'p2p_status') return p2pStatus(); + if (operation === 'job_cancel') return callQvac('cancel', input); + const spec = specs.find(([name]) => name === operation); + if (!spec) throw new Error(`unknown QVAC capability: ${operation}`); + const [, method] = spec; + if (method === 'modelRegistryGetModel') return resultShape(await callQvac(method, [input.registryPath || input.path, input.registrySource || input.source])); + if (method === 'ocr') { const result = await callQvac(method, input); return { blocks: await result.blocks }; } + return resultShape(await callQvac(method, input)); + }, + }]; +} + export { specs as PHASE9_SPECS }; diff --git a/test/phase9-tools.test.js b/test/phase9-tools.test.js index 5b232ab..c040c34 100644 --- a/test/phase9-tools.test.js +++ b/test/phase9-tools.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { createPhase9Tools, PHASE9_SPECS } from '../skills/phase9-tools.js'; +import { createPhase9GatewayTool, createPhase9Tools, PHASE9_SPECS } from '../skills/phase9-tools.js'; import { SKILLS } from '../skills/catalog.js'; test('Phase 9 exposes the complete local QVAC capability surface', () => { @@ -13,3 +13,9 @@ test('Phase 9 exposes the complete local QVAC capability surface', () => { assert.ok(SKILLS.some(([id]) => id === 'model-registry')); for (const tool of tools) assert.ok(tool.parameters?.type === 'object', tool.name); }); + +test('Phase 9 gateway keeps the complete capability surface within harness custom-tool limits', () => { + const [gateway] = createPhase9GatewayTool(); + assert.ok(gateway.parameters.properties.operation.enum.length >= PHASE9_SPECS.length); + assert.ok(gateway.parameters.properties.operation.enum.includes('p2p_status')); +});