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,244 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { PluginConfig } from '../config';
|
|
3
|
+
import {
|
|
4
|
+
applyAgentVariant,
|
|
5
|
+
normalizeAgentName,
|
|
6
|
+
resolveAgentVariant,
|
|
7
|
+
resolveRuntimeAgentName,
|
|
8
|
+
rewriteDisplayNameMentions,
|
|
9
|
+
} from './agent-variant';
|
|
10
|
+
|
|
11
|
+
describe('normalizeAgentName', () => {
|
|
12
|
+
test('returns name unchanged if no @ prefix', () => {
|
|
13
|
+
expect(normalizeAgentName('oracle')).toBe('oracle');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('strips @ prefix from agent name', () => {
|
|
17
|
+
expect(normalizeAgentName('@oracle')).toBe('oracle');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('trims whitespace', () => {
|
|
21
|
+
expect(normalizeAgentName(' oracle ')).toBe('oracle');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('handles @ prefix with whitespace', () => {
|
|
25
|
+
expect(normalizeAgentName(' @explore ')).toBe('explore');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('handles empty string', () => {
|
|
29
|
+
expect(normalizeAgentName('')).toBe('');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('resolveAgentVariant', () => {
|
|
34
|
+
test('returns undefined when config is undefined', () => {
|
|
35
|
+
expect(resolveAgentVariant(undefined, 'oracle')).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('returns undefined when agents is undefined', () => {
|
|
39
|
+
const config = {} as PluginConfig;
|
|
40
|
+
expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('returns undefined when agent has no variant', () => {
|
|
44
|
+
const config = {
|
|
45
|
+
agents: {
|
|
46
|
+
oracle: { model: 'gpt-4' },
|
|
47
|
+
},
|
|
48
|
+
} as PluginConfig;
|
|
49
|
+
expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('returns variant when configured', () => {
|
|
53
|
+
const config = {
|
|
54
|
+
agents: {
|
|
55
|
+
oracle: { variant: 'high' },
|
|
56
|
+
},
|
|
57
|
+
} as PluginConfig;
|
|
58
|
+
expect(resolveAgentVariant(config, 'oracle')).toBe('high');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('normalizes agent name with @ prefix', () => {
|
|
62
|
+
const config = {
|
|
63
|
+
agents: {
|
|
64
|
+
oracle: { variant: 'low' },
|
|
65
|
+
},
|
|
66
|
+
} as PluginConfig;
|
|
67
|
+
expect(resolveAgentVariant(config, '@oracle')).toBe('low');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('returns undefined for empty string variant', () => {
|
|
71
|
+
const config = {
|
|
72
|
+
agents: {
|
|
73
|
+
oracle: { variant: '' },
|
|
74
|
+
},
|
|
75
|
+
} as PluginConfig;
|
|
76
|
+
expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('returns undefined for whitespace-only variant', () => {
|
|
80
|
+
const config = {
|
|
81
|
+
agents: {
|
|
82
|
+
oracle: { variant: ' ' },
|
|
83
|
+
},
|
|
84
|
+
} as PluginConfig;
|
|
85
|
+
expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('trims variant whitespace', () => {
|
|
89
|
+
const config = {
|
|
90
|
+
agents: {
|
|
91
|
+
oracle: { variant: ' medium ' },
|
|
92
|
+
},
|
|
93
|
+
} as PluginConfig;
|
|
94
|
+
expect(resolveAgentVariant(config, 'oracle')).toBe('medium');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('returns undefined for non-string variant', () => {
|
|
98
|
+
const config = {
|
|
99
|
+
agents: {
|
|
100
|
+
oracle: { variant: 123 as unknown as string },
|
|
101
|
+
},
|
|
102
|
+
} as PluginConfig;
|
|
103
|
+
expect(resolveAgentVariant(config, 'oracle')).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('resolves displayName alias to internal agent for variant lookup', () => {
|
|
107
|
+
const config = {
|
|
108
|
+
agents: {
|
|
109
|
+
oracle: { displayName: 'advisor', variant: 'high' },
|
|
110
|
+
},
|
|
111
|
+
} as PluginConfig;
|
|
112
|
+
expect(resolveAgentVariant(config, '@advisor')).toBe('high');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('resolveRuntimeAgentName', () => {
|
|
117
|
+
test('keeps internal agent names unchanged', () => {
|
|
118
|
+
const config = {
|
|
119
|
+
agents: {
|
|
120
|
+
oracle: { displayName: 'advisor' },
|
|
121
|
+
},
|
|
122
|
+
} as PluginConfig;
|
|
123
|
+
|
|
124
|
+
expect(resolveRuntimeAgentName(config, 'oracle')).toBe('oracle');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('resolves displayName to internal name', () => {
|
|
128
|
+
const config = {
|
|
129
|
+
agents: {
|
|
130
|
+
oracle: { displayName: 'advisor' },
|
|
131
|
+
},
|
|
132
|
+
} as PluginConfig;
|
|
133
|
+
|
|
134
|
+
expect(resolveRuntimeAgentName(config, 'advisor')).toBe('oracle');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('resolves displayName with @ prefix and whitespace', () => {
|
|
138
|
+
const config = {
|
|
139
|
+
agents: {
|
|
140
|
+
oracle: { displayName: 'advisor' },
|
|
141
|
+
},
|
|
142
|
+
} as PluginConfig;
|
|
143
|
+
|
|
144
|
+
expect(resolveRuntimeAgentName(config, ' @advisor ')).toBe('oracle');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('resolves displayName configured via legacy alias key', () => {
|
|
148
|
+
const config = {
|
|
149
|
+
agents: {
|
|
150
|
+
explore: { displayName: 'researcher' },
|
|
151
|
+
},
|
|
152
|
+
} as PluginConfig;
|
|
153
|
+
|
|
154
|
+
expect(resolveRuntimeAgentName(config, 'researcher')).toBe('explorer');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('returns normalized name when no displayName match exists', () => {
|
|
158
|
+
const config = {
|
|
159
|
+
agents: {
|
|
160
|
+
oracle: { displayName: 'advisor' },
|
|
161
|
+
},
|
|
162
|
+
} as PluginConfig;
|
|
163
|
+
|
|
164
|
+
expect(resolveRuntimeAgentName(config, ' @unknown ')).toBe('unknown');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('rewriteDisplayNameMentions', () => {
|
|
169
|
+
test('rewrites displayName mentions to internal names for direct invocation', () => {
|
|
170
|
+
const config = {
|
|
171
|
+
agents: {
|
|
172
|
+
oracle: { displayName: 'advisor' },
|
|
173
|
+
},
|
|
174
|
+
} as PluginConfig;
|
|
175
|
+
|
|
176
|
+
expect(rewriteDisplayNameMentions(config, 'ask @advisor about this')).toBe(
|
|
177
|
+
'ask @oracle about this',
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('keeps internal mentions working while rewriting aliases', () => {
|
|
182
|
+
const config = {
|
|
183
|
+
agents: {
|
|
184
|
+
oracle: { displayName: 'advisor' },
|
|
185
|
+
},
|
|
186
|
+
} as PluginConfig;
|
|
187
|
+
|
|
188
|
+
expect(
|
|
189
|
+
rewriteDisplayNameMentions(config, 'compare @advisor with @oracle'),
|
|
190
|
+
).toBe('compare @oracle with @oracle');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('does not rewrite embedded text such as email addresses', () => {
|
|
194
|
+
const config = {
|
|
195
|
+
agents: {
|
|
196
|
+
oracle: { displayName: 'advisor' },
|
|
197
|
+
},
|
|
198
|
+
} as PluginConfig;
|
|
199
|
+
|
|
200
|
+
expect(
|
|
201
|
+
rewriteDisplayNameMentions(
|
|
202
|
+
config,
|
|
203
|
+
'email foo@advisor.com and ask @advisor directly',
|
|
204
|
+
),
|
|
205
|
+
).toBe('email foo@advisor.com and ask @oracle directly');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('applyAgentVariant', () => {
|
|
210
|
+
test('returns body unchanged when variant is undefined', () => {
|
|
211
|
+
const body = { agent: 'oracle', parts: [] };
|
|
212
|
+
const result = applyAgentVariant(undefined, body);
|
|
213
|
+
expect(result).toEqual(body);
|
|
214
|
+
expect(result).toBe(body); // Same reference
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('returns body unchanged when body already has variant', () => {
|
|
218
|
+
const body = { agent: 'oracle', variant: 'medium', parts: [] };
|
|
219
|
+
const result = applyAgentVariant('high', body);
|
|
220
|
+
expect(result.variant).toBe('medium');
|
|
221
|
+
expect(result).toBe(body); // Same reference
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('applies variant to body without variant', () => {
|
|
225
|
+
const body = { agent: 'oracle', parts: [] };
|
|
226
|
+
const result = applyAgentVariant('high', body);
|
|
227
|
+
expect(result.variant).toBe('high');
|
|
228
|
+
expect(result.agent).toBe('oracle');
|
|
229
|
+
expect(result).not.toBe(body); // New object
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('preserves all existing body properties', () => {
|
|
233
|
+
const body = {
|
|
234
|
+
agent: 'oracle',
|
|
235
|
+
parts: [{ type: 'text' as const, text: 'hello' }],
|
|
236
|
+
tools: { task: false },
|
|
237
|
+
};
|
|
238
|
+
const result = applyAgentVariant('low', body);
|
|
239
|
+
expect(result.agent).toBe('oracle');
|
|
240
|
+
expect(result.parts).toEqual([{ type: 'text', text: 'hello' }]);
|
|
241
|
+
expect(result.tools).toEqual({ task: false });
|
|
242
|
+
expect(result.variant).toBe('low');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALL_AGENT_NAMES,
|
|
3
|
+
getAgentOverride,
|
|
4
|
+
type PluginConfig,
|
|
5
|
+
} from '../config';
|
|
6
|
+
import { log } from './logger';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Normalizes an agent name by trimming whitespace and removing the optional @ prefix.
|
|
10
|
+
*
|
|
11
|
+
* @param agentName - The agent name to normalize (e.g., "@oracle" or "oracle")
|
|
12
|
+
* @returns The normalized agent name without @ prefix and trimmed of whitespace
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* normalizeAgentName("@oracle") // returns "oracle"
|
|
16
|
+
* normalizeAgentName(" explore ") // returns "explore"
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeAgentName(agentName: string): string {
|
|
19
|
+
const trimmed = agentName.trim();
|
|
20
|
+
return trimmed.startsWith('@') ? trimmed.slice(1) : trimmed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getRuntimeAgentNames(_config?: PluginConfig): string[] {
|
|
24
|
+
return [...ALL_AGENT_NAMES];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the variant configuration for a specific agent.
|
|
29
|
+
*
|
|
30
|
+
* Looks up the agent's variant in the plugin configuration. Returns undefined if:
|
|
31
|
+
* - No config is provided
|
|
32
|
+
* - The agent has no variant configured
|
|
33
|
+
* - The variant is not a string
|
|
34
|
+
* - The variant is empty or whitespace-only
|
|
35
|
+
*
|
|
36
|
+
* @param config - The plugin configuration object
|
|
37
|
+
* @param agentName - The name of the agent (with or without @ prefix)
|
|
38
|
+
* @returns The trimmed variant string, or undefined if no valid variant is found
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* resolveAgentVariant(config, "@oracle") // returns "high" if configured
|
|
42
|
+
*/
|
|
43
|
+
export function resolveAgentVariant(
|
|
44
|
+
config: PluginConfig | undefined,
|
|
45
|
+
agentName: string,
|
|
46
|
+
): string | undefined {
|
|
47
|
+
const normalized = resolveRuntimeAgentName(config, agentName);
|
|
48
|
+
const rawVariant = getAgentOverride(config, normalized)?.variant;
|
|
49
|
+
|
|
50
|
+
if (typeof rawVariant !== 'string') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const trimmed = rawVariant.trim();
|
|
55
|
+
if (trimmed.length === 0) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log(`[variant] resolved variant="${trimmed}" for agent "${normalized}"`);
|
|
60
|
+
return trimmed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a runtime-provided agent name to an internal agent name.
|
|
65
|
+
*
|
|
66
|
+
* Supports:
|
|
67
|
+
* - internal names (e.g. "oracle")
|
|
68
|
+
* - @-prefixed names (e.g. "@oracle")
|
|
69
|
+
* - displayName aliases (e.g. "advisor" -> "oracle")
|
|
70
|
+
*/
|
|
71
|
+
export function resolveRuntimeAgentName(
|
|
72
|
+
config: PluginConfig | undefined,
|
|
73
|
+
agentName: string,
|
|
74
|
+
): string {
|
|
75
|
+
const normalized = normalizeAgentName(agentName);
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((ALL_AGENT_NAMES as readonly string[]).includes(normalized)) {
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const internalName of getRuntimeAgentNames(config)) {
|
|
85
|
+
const displayName = getAgentOverride(config, internalName)?.displayName;
|
|
86
|
+
if (!displayName) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (normalizeAgentName(displayName) === normalized) {
|
|
91
|
+
return internalName;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function escapeRegExp(value: string): string {
|
|
99
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type DisplayNameMentionRewriter = (text: string) => string;
|
|
103
|
+
|
|
104
|
+
export function createDisplayNameMentionRewriter(
|
|
105
|
+
config: PluginConfig | undefined,
|
|
106
|
+
): DisplayNameMentionRewriter {
|
|
107
|
+
const replacements: Array<{ regex: RegExp; internalName: string }> = [];
|
|
108
|
+
|
|
109
|
+
for (const internalName of getRuntimeAgentNames(config)) {
|
|
110
|
+
const displayName = getAgentOverride(config, internalName)?.displayName;
|
|
111
|
+
if (!displayName) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const normalizedDisplayName = normalizeAgentName(displayName);
|
|
116
|
+
if (!normalizedDisplayName || normalizedDisplayName === internalName) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
replacements.push({
|
|
121
|
+
regex: new RegExp(
|
|
122
|
+
`(^|[^\\w.])@${escapeRegExp(normalizedDisplayName)}\\b`,
|
|
123
|
+
'g',
|
|
124
|
+
),
|
|
125
|
+
internalName,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (replacements.length === 0) {
|
|
130
|
+
return (text) => text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (text) => {
|
|
134
|
+
if (!text.includes('@')) {
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let rewritten = text;
|
|
139
|
+
for (const replacement of replacements) {
|
|
140
|
+
rewritten = rewritten.replace(
|
|
141
|
+
replacement.regex,
|
|
142
|
+
`$1@${replacement.internalName}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return rewritten;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Rewrites user-facing display-name mentions (e.g. @advisor) into internal
|
|
152
|
+
* agent mentions (e.g. @oracle) for runtime routing.
|
|
153
|
+
*/
|
|
154
|
+
export function rewriteDisplayNameMentions(
|
|
155
|
+
config: PluginConfig | undefined,
|
|
156
|
+
text: string,
|
|
157
|
+
): string {
|
|
158
|
+
return createDisplayNameMentionRewriter(config)(text);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Applies a variant to a request body if the body doesn't already have one.
|
|
163
|
+
*
|
|
164
|
+
* This function will NOT override an existing variant in the body. If no variant
|
|
165
|
+
* is provided or the body already has a variant, the original body is returned.
|
|
166
|
+
*
|
|
167
|
+
* @template T - The type of the body object, must have an optional variant property
|
|
168
|
+
* @param variant - The variant string to apply (or undefined)
|
|
169
|
+
* @param body - The request body object
|
|
170
|
+
* @returns The body with the variant applied (new object) or the original body unchanged
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* applyAgentVariant("high", { agent: "oracle" }) // returns { agent: "oracle", variant: "high" }
|
|
174
|
+
* applyAgentVariant("high", { agent: "oracle", variant: "low" }) // returns original body with variant: "low"
|
|
175
|
+
*/
|
|
176
|
+
export function applyAgentVariant<T extends { variant?: string }>(
|
|
177
|
+
variant: string | undefined,
|
|
178
|
+
body: T,
|
|
179
|
+
): T {
|
|
180
|
+
if (!variant) {
|
|
181
|
+
return body;
|
|
182
|
+
}
|
|
183
|
+
if (body.variant) {
|
|
184
|
+
return body;
|
|
185
|
+
}
|
|
186
|
+
return { ...body, variant };
|
|
187
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
3
|
+
import { writeFile as fsWriteFile } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
export const isBun = typeof globalThis.Bun !== 'undefined';
|
|
6
|
+
|
|
7
|
+
export interface CrossSpawnResult {
|
|
8
|
+
proc: ChildProcess;
|
|
9
|
+
/** Collects all stdout into a string */
|
|
10
|
+
stdout: () => Promise<string>;
|
|
11
|
+
/** Collects all stderr into a string */
|
|
12
|
+
stderr: () => Promise<string>;
|
|
13
|
+
/** Resolves when process exits with exit code */
|
|
14
|
+
exited: Promise<number>;
|
|
15
|
+
/** Kill the process */
|
|
16
|
+
kill: (signal?: NodeJS.Signals | number) => boolean;
|
|
17
|
+
/** Current exit code or null if running */
|
|
18
|
+
get exitCode(): number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function collectStream(
|
|
22
|
+
stream: NodeJS.ReadableStream | null,
|
|
23
|
+
): () => Promise<string> {
|
|
24
|
+
if (!stream) return () => Promise.resolve('');
|
|
25
|
+
const chunks: Buffer[] = [];
|
|
26
|
+
stream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
27
|
+
return () =>
|
|
28
|
+
new Promise<string>((resolve, reject) => {
|
|
29
|
+
if (!stream.readable) {
|
|
30
|
+
resolve(Buffer.concat(chunks).toString('utf-8'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
34
|
+
stream.on('error', reject);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cross-runtime spawn that works in both Bun and Node.js.
|
|
40
|
+
* API mimics Bun.spawn but uses node:child_process internally.
|
|
41
|
+
*/
|
|
42
|
+
export function crossSpawn(
|
|
43
|
+
command: string[],
|
|
44
|
+
options?: {
|
|
45
|
+
stdout?: 'pipe' | 'inherit' | 'ignore';
|
|
46
|
+
stderr?: 'pipe' | 'inherit' | 'ignore';
|
|
47
|
+
stdin?: 'pipe' | 'inherit' | 'ignore';
|
|
48
|
+
cwd?: string;
|
|
49
|
+
env?: Record<string, string | undefined>;
|
|
50
|
+
},
|
|
51
|
+
): CrossSpawnResult {
|
|
52
|
+
const [cmd, ...args] = command;
|
|
53
|
+
const proc = nodeSpawn(cmd, args, {
|
|
54
|
+
stdio: [
|
|
55
|
+
options?.stdin ?? 'ignore',
|
|
56
|
+
options?.stdout ?? 'pipe',
|
|
57
|
+
options?.stderr ?? 'pipe',
|
|
58
|
+
],
|
|
59
|
+
cwd: options?.cwd,
|
|
60
|
+
env: options?.env as NodeJS.ProcessEnv,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const stdoutCollector = collectStream(proc.stdout);
|
|
64
|
+
const stderrCollector = collectStream(proc.stderr);
|
|
65
|
+
|
|
66
|
+
const exited = new Promise<number>((resolve, reject) => {
|
|
67
|
+
proc.on('error', reject);
|
|
68
|
+
proc.on('close', (code) => resolve(code ?? 1));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
proc,
|
|
73
|
+
stdout: stdoutCollector,
|
|
74
|
+
stderr: stderrCollector,
|
|
75
|
+
exited,
|
|
76
|
+
kill: (signal) => proc.kill(signal as NodeJS.Signals),
|
|
77
|
+
get exitCode() {
|
|
78
|
+
return proc.exitCode;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Cross-runtime file write that works in both Bun and Node.js.
|
|
85
|
+
*/
|
|
86
|
+
export async function crossWrite(
|
|
87
|
+
path: string,
|
|
88
|
+
data: ArrayBuffer | Buffer | string,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
await fsWriteFile(path, Buffer.from(data as ArrayBuffer));
|
|
91
|
+
}
|
package/src/utils/env.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function getEnv(name: string): string | undefined {
|
|
2
|
+
const bunValue = (globalThis as { Bun?: { env?: Record<string, string> } })
|
|
3
|
+
.Bun?.env?.[name];
|
|
4
|
+
if (typeof bunValue === 'string' && bunValue.length > 0) return bunValue;
|
|
5
|
+
|
|
6
|
+
const processValue = (
|
|
7
|
+
globalThis as { process?: { env?: Record<string, string | undefined> } }
|
|
8
|
+
).process?.env?.[name];
|
|
9
|
+
return typeof processValue === 'string' && processValue.length > 0
|
|
10
|
+
? processValue
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './agent-variant';
|
|
2
|
+
export * from './env';
|
|
3
|
+
export * from './internal-initiator';
|
|
4
|
+
export { getLogDir, initLogger, log, resetLogger } from './logger';
|
|
5
|
+
export * from './polling';
|
|
6
|
+
export * from './session';
|
|
7
|
+
export * from './session-manager';
|
|
8
|
+
export * from './task';
|
|
9
|
+
export { extractZip } from './zip-extractor';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const SLIM_INTERNAL_INITIATOR_MARKER =
|
|
2
|
+
'<!-- SLIM_INTERNAL_INITIATOR -->';
|
|
3
|
+
|
|
4
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createInternalAgentTextPart(text: string): {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
} {
|
|
12
|
+
return {
|
|
13
|
+
type: 'text',
|
|
14
|
+
text: `${text}\n${SLIM_INTERNAL_INITIATOR_MARKER}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function hasInternalInitiatorMarker(part: unknown): boolean {
|
|
19
|
+
if (!isRecord(part) || part.type !== 'text') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof part.text !== 'string') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
28
|
+
}
|