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,179 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { TuiSnapshot } from '../../tui-state';
|
|
3
|
+
import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
|
|
4
|
+
import {
|
|
5
|
+
applyContextPressureReminder,
|
|
6
|
+
CONTEXT_PRESSURE_HEADING,
|
|
7
|
+
} from './index';
|
|
8
|
+
|
|
9
|
+
function snapshotWithOrchestratorUsage(
|
|
10
|
+
sessionID: string,
|
|
11
|
+
contextUsed: number,
|
|
12
|
+
contextLimit: number,
|
|
13
|
+
): TuiSnapshot {
|
|
14
|
+
return {
|
|
15
|
+
version: 6,
|
|
16
|
+
updatedAt: Date.now(),
|
|
17
|
+
sessions: {
|
|
18
|
+
bundle: {
|
|
19
|
+
rootSessionId: 'bundle',
|
|
20
|
+
lastActivityAt: Date.now(),
|
|
21
|
+
tree: {
|
|
22
|
+
[sessionID]: {
|
|
23
|
+
title: 'orch',
|
|
24
|
+
agent: 'orchestrator',
|
|
25
|
+
model: 'anthropic/claude-sonnet',
|
|
26
|
+
childIds: [],
|
|
27
|
+
status: 'idle',
|
|
28
|
+
usage: {
|
|
29
|
+
contextUsed,
|
|
30
|
+
contextLimit,
|
|
31
|
+
contextPct: (contextUsed / contextLimit) * 100,
|
|
32
|
+
input: 0,
|
|
33
|
+
output: 0,
|
|
34
|
+
reasoning: 0,
|
|
35
|
+
cacheRead: 0,
|
|
36
|
+
cacheWrite: 0,
|
|
37
|
+
updatedAt: Date.now(),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
orchestrationUsageLastSeen: {},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
subscriptionUsage: {},
|
|
45
|
+
activeSubscriptionByProvider: {},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('applyContextPressureReminder', () => {
|
|
50
|
+
test('appends reminder when context is at threshold', () => {
|
|
51
|
+
const sid = 'main-orchestrator';
|
|
52
|
+
const snap = snapshotWithOrchestratorUsage(sid, 220_000, 262_144);
|
|
53
|
+
const messages = [
|
|
54
|
+
{
|
|
55
|
+
info: { role: 'user' as const, agent: 'orchestrator', sessionID: sid },
|
|
56
|
+
parts: [{ type: 'text' as const, text: 'next step' }],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
applyContextPressureReminder(messages, snap, {
|
|
61
|
+
enabled: true,
|
|
62
|
+
warnThresholdPct: 75,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(messages[0].parts[0].text).toContain(CONTEXT_PRESSURE_HEADING);
|
|
66
|
+
expect(messages[0].parts[0].text).toContain('/compact');
|
|
67
|
+
expect(messages[0].parts[0].text).toMatch(/84%/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('skips when below threshold', () => {
|
|
71
|
+
const sid = 's1';
|
|
72
|
+
const snap = snapshotWithOrchestratorUsage(sid, 190_000, 262_144);
|
|
73
|
+
const messages = [
|
|
74
|
+
{
|
|
75
|
+
info: { role: 'user' as const, agent: 'orchestrator', sessionID: sid },
|
|
76
|
+
parts: [{ type: 'text' as const, text: 'ok' }],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
applyContextPressureReminder(messages, snap, {
|
|
81
|
+
enabled: true,
|
|
82
|
+
warnThresholdPct: 75,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(messages[0].parts[0].text).toBe('ok');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('respects enabled false', () => {
|
|
89
|
+
const sid = 's2';
|
|
90
|
+
const snap = snapshotWithOrchestratorUsage(sid, 260_000, 262_144);
|
|
91
|
+
const messages = [
|
|
92
|
+
{
|
|
93
|
+
info: { role: 'user' as const, agent: 'orchestrator', sessionID: sid },
|
|
94
|
+
parts: [{ type: 'text' as const, text: 'x' }],
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
applyContextPressureReminder(messages, snap, {
|
|
99
|
+
enabled: false,
|
|
100
|
+
warnThresholdPct: 75,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(messages[0].parts[0].text).toBe('x');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('skips non-orchestrator last user message', () => {
|
|
107
|
+
const sid = 's3';
|
|
108
|
+
const snap = snapshotWithOrchestratorUsage(sid, 260_000, 262_144);
|
|
109
|
+
const messages = [
|
|
110
|
+
{
|
|
111
|
+
info: { role: 'user' as const, agent: 'explorer', sessionID: sid },
|
|
112
|
+
parts: [{ type: 'text' as const, text: 'find X' }],
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
applyContextPressureReminder(messages, snap, {
|
|
117
|
+
enabled: true,
|
|
118
|
+
warnThresholdPct: 75,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(messages[0].parts[0].text).toBe('find X');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('skips without sessionID', () => {
|
|
125
|
+
const sid = 's4';
|
|
126
|
+
const snap = snapshotWithOrchestratorUsage(sid, 260_000, 262_144);
|
|
127
|
+
const messages = [
|
|
128
|
+
{
|
|
129
|
+
info: { role: 'user' as const, agent: 'orchestrator' },
|
|
130
|
+
parts: [{ type: 'text' as const, text: 'y' }],
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
applyContextPressureReminder(messages, snap, {
|
|
135
|
+
enabled: true,
|
|
136
|
+
warnThresholdPct: 75,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(messages[0].parts[0].text).toBe('y');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('does not mutate internal notification turns', () => {
|
|
143
|
+
const sid = 's5';
|
|
144
|
+
const snap = snapshotWithOrchestratorUsage(sid, 260_000, 262_144);
|
|
145
|
+
const text = `[Background task "x" completed]\n${SLIM_INTERNAL_INITIATOR_MARKER}`;
|
|
146
|
+
const messages = [
|
|
147
|
+
{
|
|
148
|
+
info: { role: 'user' as const, agent: 'orchestrator', sessionID: sid },
|
|
149
|
+
parts: [{ type: 'text' as const, text }],
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
applyContextPressureReminder(messages, snap, {
|
|
154
|
+
enabled: true,
|
|
155
|
+
warnThresholdPct: 75,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(messages[0].parts[0].text).toBe(text);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('does not duplicate reminder', () => {
|
|
162
|
+
const sid = 's6';
|
|
163
|
+
const snap = snapshotWithOrchestratorUsage(sid, 260_000, 262_144);
|
|
164
|
+
const prior = `hello\n\n---\n\n${CONTEXT_PRESSURE_HEADING}\n\nstub`;
|
|
165
|
+
const messages = [
|
|
166
|
+
{
|
|
167
|
+
info: { role: 'user' as const, agent: 'orchestrator', sessionID: sid },
|
|
168
|
+
parts: [{ type: 'text' as const, text: prior }],
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
applyContextPressureReminder(messages, snap, {
|
|
173
|
+
enabled: true,
|
|
174
|
+
warnThresholdPct: 75,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(messages[0].parts[0].text).toBe(prior);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects a /compact heads-up into the orchestrator's latest user turn when
|
|
3
|
+
* sidebar-style context telemetry shows the session is near the model window.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
deriveSessionContextPct,
|
|
7
|
+
mergedSessionUsage,
|
|
8
|
+
readTuiSnapshot,
|
|
9
|
+
type TuiSnapshot,
|
|
10
|
+
} from '../../tui-state';
|
|
11
|
+
import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
|
|
12
|
+
|
|
13
|
+
/** Marker in the injected block; keep in sync with orchestrator prompt guidance. */
|
|
14
|
+
export const CONTEXT_PRESSURE_HEADING = '### Context budget (plugin telemetry)';
|
|
15
|
+
|
|
16
|
+
interface MessageInfo {
|
|
17
|
+
role: string;
|
|
18
|
+
agent?: string;
|
|
19
|
+
sessionID?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MessagePart {
|
|
23
|
+
type: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MessageWithParts {
|
|
29
|
+
info: MessageInfo;
|
|
30
|
+
parts: MessagePart[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ContextPressureReminderOptions {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
warnThresholdPct: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildReminderText(
|
|
39
|
+
roundedPct: number,
|
|
40
|
+
contextUsed: number,
|
|
41
|
+
contextLimit: number,
|
|
42
|
+
): string {
|
|
43
|
+
return [
|
|
44
|
+
CONTEXT_PRESSURE_HEADING,
|
|
45
|
+
'',
|
|
46
|
+
`Telemetry for this orchestrator session is about **${roundedPct}%** full ` +
|
|
47
|
+
`(${contextUsed.toLocaleString('en-US')} / ${contextLimit.toLocaleString('en-US')} tokens in the model context window). ` +
|
|
48
|
+
'Further turns may fail with no context left.',
|
|
49
|
+
'',
|
|
50
|
+
'Before starting heavy new delegation or large tool payloads:',
|
|
51
|
+
'1. Ask the user to run **`/compact`** (or continue the next phase in a **new session**).',
|
|
52
|
+
'2. If a blocking delegation is mid-flight, finish the smallest safe step first, then compact.',
|
|
53
|
+
'',
|
|
54
|
+
'Do not assume unlimited context remains.',
|
|
55
|
+
].join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @internal Exported for tests */
|
|
59
|
+
export function applyContextPressureReminder(
|
|
60
|
+
messages: MessageWithParts[],
|
|
61
|
+
snapshot: TuiSnapshot,
|
|
62
|
+
options: ContextPressureReminderOptions,
|
|
63
|
+
): void {
|
|
64
|
+
if (!options.enabled || messages.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let lastUserMessageIndex = -1;
|
|
69
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
70
|
+
if (messages[i].info.role === 'user') {
|
|
71
|
+
lastUserMessageIndex = i;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (lastUserMessageIndex === -1) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
81
|
+
const agent = lastUserMessage.info.agent;
|
|
82
|
+
if (agent && agent !== 'orchestrator') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sessionID = lastUserMessage.info.sessionID;
|
|
87
|
+
if (!sessionID) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const textPartIndex = lastUserMessage.parts.findIndex(
|
|
92
|
+
(p) => p.type === 'text' && p.text !== undefined,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (textPartIndex === -1) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const originalText = lastUserMessage.parts[textPartIndex].text ?? '';
|
|
100
|
+
if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (originalText.includes(CONTEXT_PRESSURE_HEADING)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const usageBySession = mergedSessionUsage(snapshot);
|
|
108
|
+
const usage = usageBySession[sessionID];
|
|
109
|
+
const limit = usage?.contextLimit ?? 0;
|
|
110
|
+
if (!(limit > 0)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const used = usage?.contextUsed ?? 0;
|
|
115
|
+
const roundedPct = Math.round(deriveSessionContextPct(used, limit));
|
|
116
|
+
|
|
117
|
+
if (roundedPct < options.warnThresholdPct) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const block = buildReminderText(roundedPct, used, limit);
|
|
122
|
+
lastUserMessage.parts[textPartIndex].text =
|
|
123
|
+
`${originalText}\n\n---\n\n${block}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createContextPressureReminderHook(
|
|
127
|
+
options: ContextPressureReminderOptions,
|
|
128
|
+
) {
|
|
129
|
+
return {
|
|
130
|
+
'experimental.chat.messages.transform': async (
|
|
131
|
+
_input: Record<string, never>,
|
|
132
|
+
output: { messages: MessageWithParts[] },
|
|
133
|
+
): Promise<void> => {
|
|
134
|
+
applyContextPressureReminder(output.messages, readTuiSnapshot(), options);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DELEGATE_TASK_ERROR_PATTERNS, type DetectedError } from './patterns';
|
|
2
|
+
|
|
3
|
+
function extractAvailableList(output: string): string | null {
|
|
4
|
+
const match = output.match(/Allowed agents:\s*(.+)$/m);
|
|
5
|
+
if (match) return match[1].trim();
|
|
6
|
+
|
|
7
|
+
const available = output.match(/Available[^:]*:\s*(.+)$/m);
|
|
8
|
+
if (available) return available[1].trim();
|
|
9
|
+
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildRetryGuidance(errorInfo: DetectedError): string {
|
|
14
|
+
const pattern = DELEGATE_TASK_ERROR_PATTERNS.find(
|
|
15
|
+
(p) => p.errorType === errorInfo.errorType,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (!pattern) {
|
|
19
|
+
return '\n[delegate-task retry] Fix parameters and retry with corrected arguments.';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const available = extractAvailableList(errorInfo.originalOutput);
|
|
23
|
+
|
|
24
|
+
const lines = [
|
|
25
|
+
'',
|
|
26
|
+
'[delegate-task retry suggestion]',
|
|
27
|
+
`Error type: ${errorInfo.errorType}`,
|
|
28
|
+
`Fix: ${pattern.fixHint}`,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (available) {
|
|
32
|
+
lines.push(`Available: ${available}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lines.push(
|
|
36
|
+
'Retry now with corrected parameters. Example:',
|
|
37
|
+
'task(description="...", prompt="...", category="unspecified-low", run_in_background=false, load_skills=[])',
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return lines.join('\n');
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import { buildRetryGuidance } from './guidance';
|
|
3
|
+
import { detectDelegateTaskError } from './patterns';
|
|
4
|
+
|
|
5
|
+
export function createDelegateTaskRetryHook(_ctx: PluginInput) {
|
|
6
|
+
return {
|
|
7
|
+
'tool.execute.after': async (
|
|
8
|
+
input: { tool: string },
|
|
9
|
+
output: { output: unknown },
|
|
10
|
+
): Promise<void> => {
|
|
11
|
+
const toolName = input.tool.toLowerCase();
|
|
12
|
+
const isDelegateTool = toolName === 'task';
|
|
13
|
+
if (!isDelegateTool) return;
|
|
14
|
+
|
|
15
|
+
if (typeof output.output !== 'string') return;
|
|
16
|
+
|
|
17
|
+
const detected = detectDelegateTaskError(output.output);
|
|
18
|
+
if (!detected) return;
|
|
19
|
+
|
|
20
|
+
output.output += `\n${buildRetryGuidance(detected)}`;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { createDelegateTaskRetryHook } from './hook';
|
|
3
|
+
|
|
4
|
+
describe('delegate-task-retry hook', () => {
|
|
5
|
+
test('appends guidance for task argument errors', async () => {
|
|
6
|
+
const hook = createDelegateTaskRetryHook({} as never);
|
|
7
|
+
const output = {
|
|
8
|
+
output:
|
|
9
|
+
'[ERROR] Invalid arguments: Must provide either category or subagent_type. Available categories: quick, unspecified-low',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
await hook['tool.execute.after']({ tool: 'task' }, output);
|
|
13
|
+
|
|
14
|
+
expect(output.output).toContain('[delegate-task retry suggestion]');
|
|
15
|
+
expect(output.output).toContain('missing_category_or_agent');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('appends guidance for task agent allowlist errors', async () => {
|
|
19
|
+
const hook = createDelegateTaskRetryHook({} as never);
|
|
20
|
+
const output = {
|
|
21
|
+
output: "Agent 'oracle' is not allowed. Allowed agents: explorer, fixer",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
await hook['tool.execute.after']({ tool: 'task' }, output);
|
|
25
|
+
|
|
26
|
+
expect(output.output).toContain('background_agent_not_allowed');
|
|
27
|
+
expect(output.output).toContain('Available: explorer, fixer');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('does nothing for unrelated tool output', async () => {
|
|
31
|
+
const hook = createDelegateTaskRetryHook({} as never);
|
|
32
|
+
const output = { output: 'all good' };
|
|
33
|
+
|
|
34
|
+
await hook['tool.execute.after']({ tool: 'read' }, output);
|
|
35
|
+
|
|
36
|
+
expect(output.output).toBe('all good');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { buildRetryGuidance } from './guidance';
|
|
2
|
+
export { createDelegateTaskRetryHook } from './hook';
|
|
3
|
+
export type { DelegateTaskErrorPattern, DetectedError } from './patterns';
|
|
4
|
+
export {
|
|
5
|
+
DELEGATE_TASK_ERROR_PATTERNS,
|
|
6
|
+
detectDelegateTaskError,
|
|
7
|
+
} from './patterns';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface DelegateTaskErrorPattern {
|
|
2
|
+
pattern: string;
|
|
3
|
+
errorType: string;
|
|
4
|
+
fixHint: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const DELEGATE_TASK_ERROR_PATTERNS: DelegateTaskErrorPattern[] = [
|
|
8
|
+
{
|
|
9
|
+
pattern: 'run_in_background',
|
|
10
|
+
errorType: 'missing_run_in_background',
|
|
11
|
+
fixHint:
|
|
12
|
+
'Add run_in_background=false (delegation) or run_in_background=true (parallel exploration).',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
pattern: 'load_skills',
|
|
16
|
+
errorType: 'missing_load_skills',
|
|
17
|
+
fixHint: 'Add load_skills=[] (empty array when no skill is needed).',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
pattern: 'category OR subagent_type',
|
|
21
|
+
errorType: 'mutual_exclusion',
|
|
22
|
+
fixHint:
|
|
23
|
+
'Provide only one: category (e.g., "unspecified-low") OR subagent_type (e.g., "explorer").',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: 'Must provide either category or subagent_type',
|
|
27
|
+
errorType: 'missing_category_or_agent',
|
|
28
|
+
fixHint:
|
|
29
|
+
'Add either category="unspecified-low" or subagent_type="explorer".',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: 'Unknown category',
|
|
33
|
+
errorType: 'unknown_category',
|
|
34
|
+
fixHint: 'Use a valid category listed in the error output.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: 'Unknown agent',
|
|
38
|
+
errorType: 'unknown_agent',
|
|
39
|
+
fixHint: 'Use a valid agent name from the available list.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: 'Skills not found',
|
|
43
|
+
errorType: 'unknown_skills',
|
|
44
|
+
fixHint: 'Use valid skill names listed in the error output.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: 'is not allowed. Allowed agents:',
|
|
48
|
+
errorType: 'background_agent_not_allowed',
|
|
49
|
+
fixHint:
|
|
50
|
+
'Use one of the allowed agents shown in the error or delegate from a parent agent that can call this subagent.',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export interface DetectedError {
|
|
55
|
+
errorType: string;
|
|
56
|
+
originalOutput: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function detectDelegateTaskError(output: string): DetectedError | null {
|
|
60
|
+
if (!output || typeof output !== 'string') return null;
|
|
61
|
+
|
|
62
|
+
const hasErrorSignal =
|
|
63
|
+
output.includes('[ERROR]') ||
|
|
64
|
+
output.includes('Invalid arguments') ||
|
|
65
|
+
output.includes('is not allowed. Allowed agents:');
|
|
66
|
+
|
|
67
|
+
if (!hasErrorSignal) return null;
|
|
68
|
+
|
|
69
|
+
for (const pattern of DELEGATE_TASK_ERROR_PATTERNS) {
|
|
70
|
+
if (output.includes(pattern.pattern)) {
|
|
71
|
+
return {
|
|
72
|
+
errorType: pattern.errorType,
|
|
73
|
+
originalOutput: output,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|