opencode-dux 1.0.0
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/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { PluginConfig } from '../config';
|
|
3
|
+
import { AgentOverrideConfigSchema, SUBAGENT_NAMES } from '../config';
|
|
4
|
+
import { createAgents, getAgentConfigs, isSubagent } from './index';
|
|
5
|
+
|
|
6
|
+
describe('agent alias backward compatibility', () => {
|
|
7
|
+
test("applies 'explore' config to 'explorer' agent", async () => {
|
|
8
|
+
const config: PluginConfig = {
|
|
9
|
+
agents: {
|
|
10
|
+
explore: { model: 'test/old-explore-model' },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const agents = await createAgents(config);
|
|
14
|
+
const explorer = agents.find((a) => a.name === 'explorer');
|
|
15
|
+
expect(explorer).toBeDefined();
|
|
16
|
+
expect(explorer?.config.model).toBe('test/old-explore-model');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("applies 'frontend-ui-ux-engineer' config to 'designer' agent", async () => {
|
|
20
|
+
const config: PluginConfig = {
|
|
21
|
+
agents: {
|
|
22
|
+
'frontend-ui-ux-engineer': { model: 'test/old-frontend-model' },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const agents = await createAgents(config);
|
|
26
|
+
const designer = agents.find((a) => a.name === 'designer');
|
|
27
|
+
expect(designer).toBeDefined();
|
|
28
|
+
expect(designer?.config.model).toBe('test/old-frontend-model');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('new name takes priority over old alias', async () => {
|
|
32
|
+
const config: PluginConfig = {
|
|
33
|
+
agents: {
|
|
34
|
+
explore: { model: 'old-model' },
|
|
35
|
+
explorer: { model: 'new-model' },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const agents = await createAgents(config);
|
|
39
|
+
const explorer = agents.find((a) => a.name === 'explorer');
|
|
40
|
+
expect(explorer?.config.model).toBe('new-model');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('new agent names work directly', async () => {
|
|
44
|
+
const config: PluginConfig = {
|
|
45
|
+
agents: {
|
|
46
|
+
explorer: { model: 'direct-explorer' },
|
|
47
|
+
designer: { model: 'direct-designer' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const agents = await createAgents(config);
|
|
51
|
+
expect(agents.find((a) => a.name === 'explorer')?.config.model).toBe(
|
|
52
|
+
'direct-explorer',
|
|
53
|
+
);
|
|
54
|
+
expect(agents.find((a) => a.name === 'designer')?.config.model).toBe(
|
|
55
|
+
'direct-designer',
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('temperature override via old alias', async () => {
|
|
60
|
+
const config: PluginConfig = {
|
|
61
|
+
agents: {
|
|
62
|
+
explore: { temperature: 0.5 },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const agents = await createAgents(config);
|
|
66
|
+
const explorer = agents.find((a) => a.name === 'explorer');
|
|
67
|
+
expect(explorer?.config.temperature).toBe(0.5);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('variant override via old alias', async () => {
|
|
71
|
+
const config: PluginConfig = {
|
|
72
|
+
agents: {
|
|
73
|
+
explore: { variant: 'low' },
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const agents = await createAgents(config);
|
|
77
|
+
const explorer = agents.find((a) => a.name === 'explorer');
|
|
78
|
+
expect(explorer?.config.variant).toBe('low');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('fixer agent model selection', () => {
|
|
83
|
+
test('fixer does not inherit librarian model when no fixer config provided', async () => {
|
|
84
|
+
const config: PluginConfig = {
|
|
85
|
+
agents: {
|
|
86
|
+
librarian: { model: 'librarian-custom-model' },
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const agents = await createAgents(config);
|
|
90
|
+
const fixer = agents.find((a) => a.name === 'fixer');
|
|
91
|
+
expect(fixer?.config.model).not.toBe('librarian-custom-model');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('fixer uses its own model when explicitly configured', async () => {
|
|
95
|
+
const config: PluginConfig = {
|
|
96
|
+
agents: {
|
|
97
|
+
librarian: { model: 'librarian-model' },
|
|
98
|
+
fixer: { model: 'fixer-specific-model' },
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const agents = await createAgents(config);
|
|
102
|
+
const fixer = agents.find((a) => a.name === 'fixer');
|
|
103
|
+
expect(fixer?.config.model).toBe('fixer-specific-model');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('orchestrator agent', () => {
|
|
108
|
+
test('orchestrator is first in agents array', async () => {
|
|
109
|
+
const agents = await createAgents();
|
|
110
|
+
expect(agents[0].name).toBe('orchestrator');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('orchestrator has question permission set to allow', async () => {
|
|
114
|
+
const agents = await createAgents();
|
|
115
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
116
|
+
expect(orchestrator?.config.permission).toBeDefined();
|
|
117
|
+
expect((orchestrator?.config.permission as any).question).toBe('allow');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('orchestrator is denied access to built-in task tool', async () => {
|
|
121
|
+
const agents = await createAgents();
|
|
122
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
123
|
+
expect((orchestrator?.config.permission as any).task).toBe('deny');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('orchestrator accepts overrides', async () => {
|
|
127
|
+
const config: PluginConfig = {
|
|
128
|
+
agents: {
|
|
129
|
+
orchestrator: { model: 'custom-orchestrator-model', temperature: 0.3 },
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const agents = await createAgents(config);
|
|
133
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
134
|
+
expect(orchestrator?.config.model).toBe('custom-orchestrator-model');
|
|
135
|
+
expect(orchestrator?.config.temperature).toBe(0.3);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('orchestrator accepts variant override', async () => {
|
|
139
|
+
const config: PluginConfig = {
|
|
140
|
+
agents: {
|
|
141
|
+
orchestrator: { variant: 'high' },
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
const agents = await createAgents(config);
|
|
145
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
146
|
+
expect(orchestrator?.config.variant).toBe('high');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('orchestrator stores model array with per-model variants in _modelArray', async () => {
|
|
150
|
+
const config: PluginConfig = {
|
|
151
|
+
agents: {
|
|
152
|
+
orchestrator: {
|
|
153
|
+
model: [
|
|
154
|
+
{ id: 'google/gemini-3-pro', variant: 'high' },
|
|
155
|
+
{ id: 'github-copilot/claude-3.5-haiku' },
|
|
156
|
+
'openai/gpt-4',
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const agents = await createAgents(config);
|
|
162
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
163
|
+
expect(orchestrator?._modelArray).toEqual([
|
|
164
|
+
{ id: 'google/gemini-3-pro', variant: 'high' },
|
|
165
|
+
{ id: 'github-copilot/claude-3.5-haiku' },
|
|
166
|
+
{ id: 'openai/gpt-4' },
|
|
167
|
+
]);
|
|
168
|
+
expect(orchestrator?.config.model).toBeUndefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('per-model variant in array config', () => {
|
|
173
|
+
test('subagent stores model array with per-model variants', async () => {
|
|
174
|
+
const config: PluginConfig = {
|
|
175
|
+
agents: {
|
|
176
|
+
explorer: {
|
|
177
|
+
model: [
|
|
178
|
+
{ id: 'google/gemini-3-flash', variant: 'low' },
|
|
179
|
+
'openai/gpt-4o-mini',
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const agents = await createAgents(config);
|
|
185
|
+
const explorer = agents.find((a) => a.name === 'explorer');
|
|
186
|
+
expect(explorer?._modelArray).toEqual([
|
|
187
|
+
{ id: 'google/gemini-3-flash', variant: 'low' },
|
|
188
|
+
{ id: 'openai/gpt-4o-mini' },
|
|
189
|
+
]);
|
|
190
|
+
expect(explorer?.config.model).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('top-level variant preserved alongside per-model variants', async () => {
|
|
194
|
+
const config: PluginConfig = {
|
|
195
|
+
agents: {
|
|
196
|
+
orchestrator: {
|
|
197
|
+
model: [
|
|
198
|
+
{ id: 'google/gemini-3-pro', variant: 'high' },
|
|
199
|
+
'openai/gpt-4',
|
|
200
|
+
],
|
|
201
|
+
variant: 'low',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
const agents = await createAgents(config);
|
|
206
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
207
|
+
// top-level variant still set as default
|
|
208
|
+
expect(orchestrator?.config.variant).toBe('low');
|
|
209
|
+
// per-model variants stored in _modelArray
|
|
210
|
+
expect(orchestrator?._modelArray?.[0]?.variant).toBe('high');
|
|
211
|
+
expect(orchestrator?._modelArray?.[1]?.variant).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('skill permissions', () => {
|
|
216
|
+
test('orchestrator gets deny all by default when no skill config provided', async () => {
|
|
217
|
+
const agents = await createAgents();
|
|
218
|
+
const orchestrator = agents.find((a) => a.name === 'orchestrator');
|
|
219
|
+
expect(orchestrator).toBeDefined();
|
|
220
|
+
const skillPerm = (
|
|
221
|
+
orchestrator?.config.permission as Record<string, unknown>
|
|
222
|
+
)?.skill as Record<string, string>;
|
|
223
|
+
// Without explicit skill config, all skills are denied
|
|
224
|
+
expect(skillPerm?.['*']).toBe('deny');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('isSubagent type guard', () => {
|
|
229
|
+
test('returns true for valid subagent names', () => {
|
|
230
|
+
expect(isSubagent('explorer')).toBe(true);
|
|
231
|
+
expect(isSubagent('librarian')).toBe(true);
|
|
232
|
+
expect(isSubagent('oracle')).toBe(true);
|
|
233
|
+
expect(isSubagent('designer')).toBe(true);
|
|
234
|
+
expect(isSubagent('fixer')).toBe(true);
|
|
235
|
+
expect(isSubagent('steward')).toBe(true);
|
|
236
|
+
expect(isSubagent('interpreter')).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('returns false for orchestrator', () => {
|
|
240
|
+
expect(isSubagent('orchestrator')).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('returns false for invalid agent names', () => {
|
|
244
|
+
expect(isSubagent('invalid-agent')).toBe(false);
|
|
245
|
+
expect(isSubagent('')).toBe(false);
|
|
246
|
+
expect(isSubagent('explore')).toBe(false); // old alias, not actual agent name
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('agent classification', () => {
|
|
251
|
+
test('SUBAGENT_NAMES excludes orchestrator', () => {
|
|
252
|
+
expect(SUBAGENT_NAMES).not.toContain('orchestrator');
|
|
253
|
+
expect(SUBAGENT_NAMES).toContain('explorer');
|
|
254
|
+
expect(SUBAGENT_NAMES).toContain('fixer');
|
|
255
|
+
expect(SUBAGENT_NAMES).toContain('steward');
|
|
256
|
+
expect(SUBAGENT_NAMES).toContain('interpreter');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('getAgentConfigs applies correct classification visibility and mode', async () => {
|
|
260
|
+
// Enable all agents for classification testing
|
|
261
|
+
const configs = await getAgentConfigs();
|
|
262
|
+
|
|
263
|
+
// Primary agent
|
|
264
|
+
expect(configs.orchestrator.mode).toBe('primary');
|
|
265
|
+
|
|
266
|
+
// All subagents
|
|
267
|
+
for (const name of SUBAGENT_NAMES) {
|
|
268
|
+
expect(configs[name].mode).toBe('subagent');
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('createAgents', () => {
|
|
274
|
+
test('creates all agents without config', async () => {
|
|
275
|
+
const agents = await createAgents();
|
|
276
|
+
const names = agents.map((a) => a.name);
|
|
277
|
+
expect(names).toContain('orchestrator');
|
|
278
|
+
expect(names).toContain('explorer');
|
|
279
|
+
expect(names).toContain('designer');
|
|
280
|
+
expect(names).toContain('oracle');
|
|
281
|
+
expect(names).toContain('librarian');
|
|
282
|
+
expect(names).toContain('fixer');
|
|
283
|
+
expect(names).toContain('steward');
|
|
284
|
+
expect(names).toContain('interpreter');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('creates exactly 8 agents (1 orchestrator + 7 subagents)', async () => {
|
|
288
|
+
const agents = await createAgents();
|
|
289
|
+
expect(agents.length).toBe(8);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('getAgentConfigs', () => {
|
|
294
|
+
test('returns config record keyed by agent name', async () => {
|
|
295
|
+
const configs = await getAgentConfigs();
|
|
296
|
+
expect(configs.orchestrator).toBeDefined();
|
|
297
|
+
expect(configs.explorer).toBeDefined();
|
|
298
|
+
// orchestrator has no hardcoded default model; resolved at runtime via
|
|
299
|
+
// chat.message hook when _modelArray is configured, or left to the user
|
|
300
|
+
expect(configs.explorer.model).toBeDefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('includes description in SDK config', async () => {
|
|
304
|
+
const configs = await getAgentConfigs();
|
|
305
|
+
expect(configs.orchestrator.description).toBeDefined();
|
|
306
|
+
expect(configs.explorer.description).toBeDefined();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('options passthrough', () => {
|
|
311
|
+
test('options are applied to agent config via overrides', async () => {
|
|
312
|
+
const config: PluginConfig = {
|
|
313
|
+
agents: {
|
|
314
|
+
oracle: {
|
|
315
|
+
model: 'openai/gpt-5.5',
|
|
316
|
+
options: { textVerbosity: 'low' },
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
const agents = await createAgents(config);
|
|
321
|
+
const oracle = agents.find((a) => a.name === 'oracle');
|
|
322
|
+
expect(oracle?.config.options).toEqual({ textVerbosity: 'low' });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('options with nested objects are passed through', async () => {
|
|
326
|
+
const config: PluginConfig = {
|
|
327
|
+
agents: {
|
|
328
|
+
oracle: {
|
|
329
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
330
|
+
options: {
|
|
331
|
+
thinking: { type: 'enabled', budgetTokens: 16000 },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
const agents = await createAgents(config);
|
|
337
|
+
const oracle = agents.find((a) => a.name === 'oracle');
|
|
338
|
+
expect(oracle?.config.options).toEqual({
|
|
339
|
+
thinking: { type: 'enabled', budgetTokens: 16000 },
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('options work with other overrides', async () => {
|
|
344
|
+
const config: PluginConfig = {
|
|
345
|
+
agents: {
|
|
346
|
+
oracle: {
|
|
347
|
+
model: 'openai/gpt-5.5',
|
|
348
|
+
variant: 'high',
|
|
349
|
+
temperature: 0.7,
|
|
350
|
+
options: { textVerbosity: 'low', reasoningEffort: 'medium' },
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
const agents = await createAgents(config);
|
|
355
|
+
const oracle = agents.find((a) => a.name === 'oracle');
|
|
356
|
+
expect(oracle?.config.model).toBe('openai/gpt-5.5');
|
|
357
|
+
expect(oracle?.config.variant).toBe('high');
|
|
358
|
+
expect(oracle?.config.temperature).toBe(0.7);
|
|
359
|
+
expect(oracle?.config.options).toEqual({
|
|
360
|
+
textVerbosity: 'low',
|
|
361
|
+
reasoningEffort: 'medium',
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('options are absent when not configured', async () => {
|
|
366
|
+
const config: PluginConfig = {
|
|
367
|
+
agents: {
|
|
368
|
+
oracle: { model: 'openai/gpt-5.5' },
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
const agents = await createAgents(config);
|
|
372
|
+
const oracle = agents.find((a) => a.name === 'oracle');
|
|
373
|
+
expect(oracle?.config.options).toBeUndefined();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('options flow through getAgentConfigs to SDK output', async () => {
|
|
377
|
+
const config: PluginConfig = {
|
|
378
|
+
agents: {
|
|
379
|
+
oracle: {
|
|
380
|
+
model: 'openai/gpt-5.5',
|
|
381
|
+
options: { textVerbosity: 'low' },
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
const configs = await getAgentConfigs(config);
|
|
386
|
+
expect(configs.oracle.options).toEqual({ textVerbosity: 'low' });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('options are shallow-merged with existing agent config options', async () => {
|
|
390
|
+
// Simulate an agent factory setting default options
|
|
391
|
+
const config: PluginConfig = {
|
|
392
|
+
agents: {
|
|
393
|
+
oracle: {
|
|
394
|
+
model: 'openai/gpt-5.5',
|
|
395
|
+
options: { reasoningEffort: 'medium' },
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
const agents = await createAgents(config);
|
|
400
|
+
const oracle = agents.find((a) => a.name === 'oracle');
|
|
401
|
+
// Override options should merge with (not replace) any factory defaults
|
|
402
|
+
expect(oracle?.config.options).toEqual({ reasoningEffort: 'medium' });
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe('AgentOverrideConfigSchema options validation', () => {
|
|
407
|
+
test('accepts valid options object', () => {
|
|
408
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
409
|
+
options: { textVerbosity: 'low' },
|
|
410
|
+
});
|
|
411
|
+
expect(result.success).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('accepts empty options object', () => {
|
|
415
|
+
const result = AgentOverrideConfigSchema.safeParse({ options: {} });
|
|
416
|
+
expect(result.success).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('accepts nested values in options', () => {
|
|
420
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
421
|
+
options: {
|
|
422
|
+
thinking: { type: 'enabled', budgetTokens: 16000 },
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
expect(result.success).toBe(true);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test('accepts options alongside other fields', () => {
|
|
429
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
430
|
+
model: 'openai/gpt-5.5',
|
|
431
|
+
variant: 'high',
|
|
432
|
+
temperature: 0.7,
|
|
433
|
+
options: { textVerbosity: 'low' },
|
|
434
|
+
});
|
|
435
|
+
expect(result.success).toBe(true);
|
|
436
|
+
if (result.success) {
|
|
437
|
+
expect(result.data.options).toEqual({ textVerbosity: 'low' });
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('config without options is valid', () => {
|
|
442
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
443
|
+
model: 'openai/gpt-5.5',
|
|
444
|
+
});
|
|
445
|
+
expect(result.success).toBe(true);
|
|
446
|
+
if (result.success) {
|
|
447
|
+
expect(result.data.options).toBeUndefined();
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('rejects non-object options', () => {
|
|
452
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
453
|
+
options: 'not-an-object',
|
|
454
|
+
});
|
|
455
|
+
expect(result.success).toBe(false);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('rejects empty model arrays', () => {
|
|
459
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
460
|
+
model: [],
|
|
461
|
+
});
|
|
462
|
+
expect(result.success).toBe(false);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test('rejects description field on overrides', () => {
|
|
466
|
+
const result = AgentOverrideConfigSchema.safeParse({
|
|
467
|
+
model: 'openai/gpt-5.5',
|
|
468
|
+
description: 'not supported for custom agents',
|
|
469
|
+
} as Record<string, unknown>);
|
|
470
|
+
expect(result.success).toBe(false);
|
|
471
|
+
});
|
|
472
|
+
});
|