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,225 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import { crossSpawn } from '../../utils/compat';
|
|
3
|
+
import { log } from '../../utils/logger';
|
|
4
|
+
import { preparePackageUpdate, resolveInstallContext } from './cache';
|
|
5
|
+
import {
|
|
6
|
+
extractChannel,
|
|
7
|
+
findPluginEntry,
|
|
8
|
+
getCachedVersion,
|
|
9
|
+
getLatestVersion,
|
|
10
|
+
getLocalDevVersion,
|
|
11
|
+
} from './checker';
|
|
12
|
+
import { CACHE_DIR, PACKAGE_NAME } from './constants';
|
|
13
|
+
import type { AutoUpdateCheckerOptions } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates an OpenCode hook that checks for plugin updates when a new session is created.
|
|
17
|
+
* @param ctx The plugin input context.
|
|
18
|
+
* @param options Configuration options for the update checker.
|
|
19
|
+
* @returns A hook object for the session.created event.
|
|
20
|
+
*/
|
|
21
|
+
export function createAutoUpdateCheckerHook(
|
|
22
|
+
ctx: PluginInput,
|
|
23
|
+
options: AutoUpdateCheckerOptions = {},
|
|
24
|
+
) {
|
|
25
|
+
const { autoUpdate = true } = options;
|
|
26
|
+
|
|
27
|
+
let hasChecked = false;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
event: ({ event }: { event: { type: string; properties?: unknown } }) => {
|
|
31
|
+
if (event.type !== 'session.created') return;
|
|
32
|
+
if (hasChecked) return;
|
|
33
|
+
|
|
34
|
+
const props = event.properties as
|
|
35
|
+
| { info?: { parentID?: string } }
|
|
36
|
+
| undefined;
|
|
37
|
+
if (props?.info?.parentID) return;
|
|
38
|
+
|
|
39
|
+
hasChecked = true;
|
|
40
|
+
|
|
41
|
+
setTimeout(async () => {
|
|
42
|
+
const localDevVersion = getLocalDevVersion(ctx.directory);
|
|
43
|
+
|
|
44
|
+
if (localDevVersion) {
|
|
45
|
+
log('[auto-update-checker] Local development mode');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
50
|
+
log('[auto-update-checker] Background update check failed:', err);
|
|
51
|
+
});
|
|
52
|
+
}, 0);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Orchestrates the version comparison and update process in the background.
|
|
59
|
+
* @param ctx The plugin input context.
|
|
60
|
+
* @param autoUpdate Whether to automatically install updates.
|
|
61
|
+
*/
|
|
62
|
+
async function runBackgroundUpdateCheck(
|
|
63
|
+
ctx: PluginInput,
|
|
64
|
+
autoUpdate: boolean,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const pluginInfo = findPluginEntry(ctx.directory);
|
|
67
|
+
if (!pluginInfo) {
|
|
68
|
+
log('[auto-update-checker] Plugin not found in config');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cachedVersion = getCachedVersion();
|
|
73
|
+
const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
|
|
74
|
+
if (!currentVersion) {
|
|
75
|
+
log('[auto-update-checker] No version found (cached or pinned)');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
|
|
80
|
+
const latestVersion = await getLatestVersion(channel);
|
|
81
|
+
if (!latestVersion) {
|
|
82
|
+
log(
|
|
83
|
+
'[auto-update-checker] Failed to fetch latest version for channel:',
|
|
84
|
+
channel,
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (currentVersion === latestVersion) {
|
|
90
|
+
log(
|
|
91
|
+
'[auto-update-checker] Already on latest version for channel:',
|
|
92
|
+
channel,
|
|
93
|
+
);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
log(
|
|
98
|
+
`[auto-update-checker] Update available (${channel}): ${currentVersion} → ${latestVersion}`,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (pluginInfo.isPinned) {
|
|
102
|
+
showToast(
|
|
103
|
+
ctx,
|
|
104
|
+
`OMO-Slim ${latestVersion}`,
|
|
105
|
+
`v${latestVersion} available.\nVersion is pinned. Update your plugin config to apply.`,
|
|
106
|
+
'info',
|
|
107
|
+
8000,
|
|
108
|
+
);
|
|
109
|
+
log(`[auto-update-checker] Version is pinned; skipping auto-update.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!autoUpdate) {
|
|
114
|
+
showToast(
|
|
115
|
+
ctx,
|
|
116
|
+
`OMO-Slim ${latestVersion}`,
|
|
117
|
+
`v${latestVersion} available. Auto-update is disabled.`,
|
|
118
|
+
'info',
|
|
119
|
+
8000,
|
|
120
|
+
);
|
|
121
|
+
log('[auto-update-checker] Auto-update disabled, notification only');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const installDir = preparePackageUpdate(latestVersion, PACKAGE_NAME);
|
|
126
|
+
if (!installDir) {
|
|
127
|
+
showToast(
|
|
128
|
+
ctx,
|
|
129
|
+
`OMO-Slim ${latestVersion}`,
|
|
130
|
+
`v${latestVersion} available. Auto-update could not prepare the active install.`,
|
|
131
|
+
'info',
|
|
132
|
+
8000,
|
|
133
|
+
);
|
|
134
|
+
log('[auto-update-checker] Failed to prepare install root for auto-update');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const installSuccess = await runBunInstallSafe(installDir);
|
|
139
|
+
|
|
140
|
+
if (installSuccess) {
|
|
141
|
+
showToast(
|
|
142
|
+
ctx,
|
|
143
|
+
'OMO-Slim Updated!',
|
|
144
|
+
`v${currentVersion} → v${latestVersion}\nRestart OpenCode to apply.`,
|
|
145
|
+
'success',
|
|
146
|
+
8000,
|
|
147
|
+
);
|
|
148
|
+
log(
|
|
149
|
+
`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
showToast(
|
|
153
|
+
ctx,
|
|
154
|
+
`OMO-Slim ${latestVersion}`,
|
|
155
|
+
`v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`,
|
|
156
|
+
'error',
|
|
157
|
+
8000,
|
|
158
|
+
);
|
|
159
|
+
log('[auto-update-checker] bun install failed; update not installed');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getAutoUpdateInstallDir(): string {
|
|
164
|
+
return resolveInstallContext()?.installDir ?? CACHE_DIR;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Spawns a background process to run 'bun install'.
|
|
169
|
+
* Includes a 60-second timeout to prevent stalling OpenCode.
|
|
170
|
+
* @param installDir The directory whose package manager context should be refreshed.
|
|
171
|
+
* @returns True if the installation succeeded within the timeout.
|
|
172
|
+
*/
|
|
173
|
+
async function runBunInstallSafe(installDir: string): Promise<boolean> {
|
|
174
|
+
try {
|
|
175
|
+
const proc = crossSpawn(['bun', 'install'], {
|
|
176
|
+
cwd: installDir,
|
|
177
|
+
stdout: 'pipe',
|
|
178
|
+
stderr: 'pipe',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const timeoutPromise = new Promise<'timeout'>((resolve) =>
|
|
182
|
+
setTimeout(() => resolve('timeout'), 60_000),
|
|
183
|
+
);
|
|
184
|
+
const exitPromise = proc.exited.then(() => 'completed' as const);
|
|
185
|
+
const result = await Promise.race([exitPromise, timeoutPromise]);
|
|
186
|
+
|
|
187
|
+
if (result === 'timeout') {
|
|
188
|
+
try {
|
|
189
|
+
proc.kill();
|
|
190
|
+
} catch {
|
|
191
|
+
/* empty */
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return proc.exitCode === 0;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
log('[auto-update-checker] bun install error:', err);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Helper to display a toast notification in the OpenCode TUI.
|
|
205
|
+
* @param ctx The plugin input context.
|
|
206
|
+
* @param title The toast title.
|
|
207
|
+
* @param message The toast message.
|
|
208
|
+
* @param variant The visual style of the toast.
|
|
209
|
+
* @param duration How long to show the toast in milliseconds.
|
|
210
|
+
*/
|
|
211
|
+
function showToast(
|
|
212
|
+
ctx: PluginInput,
|
|
213
|
+
title: string,
|
|
214
|
+
message: string,
|
|
215
|
+
variant: 'info' | 'success' | 'error' = 'info',
|
|
216
|
+
duration = 3000,
|
|
217
|
+
): void {
|
|
218
|
+
ctx.client.tui
|
|
219
|
+
.showToast({
|
|
220
|
+
body: { title, message, variant, duration },
|
|
221
|
+
})
|
|
222
|
+
.catch(() => {});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type { AutoUpdateCheckerOptions } from './types';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface NpmDistTags {
|
|
2
|
+
latest: string;
|
|
3
|
+
[key: string]: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface OpencodeConfig {
|
|
7
|
+
plugin?: unknown[];
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PackageJson {
|
|
12
|
+
version: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AutoUpdateCheckerOptions {
|
|
18
|
+
autoUpdate?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PluginEntryInfo {
|
|
22
|
+
entry: string;
|
|
23
|
+
isPinned: boolean;
|
|
24
|
+
pinnedVersion: string | null;
|
|
25
|
+
configPath: string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
3
|
+
import { createInternalAgentTextPart } from '../utils';
|
|
4
|
+
import {
|
|
5
|
+
__resetInternalMarkerCacheForTesting,
|
|
6
|
+
createChatHeadersHook,
|
|
7
|
+
} from './chat-headers';
|
|
8
|
+
|
|
9
|
+
function createMockContext(parts: unknown[] = []) {
|
|
10
|
+
return {
|
|
11
|
+
client: {
|
|
12
|
+
session: {
|
|
13
|
+
message: mock(async () => ({
|
|
14
|
+
data: {
|
|
15
|
+
info: { role: 'user' },
|
|
16
|
+
parts,
|
|
17
|
+
},
|
|
18
|
+
})),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
} as unknown as PluginInput;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createInput(
|
|
25
|
+
overrides?: Partial<{
|
|
26
|
+
sessionID: string;
|
|
27
|
+
providerID: string;
|
|
28
|
+
npm: string;
|
|
29
|
+
messageID: string;
|
|
30
|
+
}>,
|
|
31
|
+
) {
|
|
32
|
+
const sessionID = overrides?.sessionID ?? 'session-1';
|
|
33
|
+
return {
|
|
34
|
+
sessionID,
|
|
35
|
+
agent: 'orchestrator',
|
|
36
|
+
model: {
|
|
37
|
+
id: 'github-copilot/claude',
|
|
38
|
+
providerID: overrides?.providerID ?? 'github-copilot',
|
|
39
|
+
api: {
|
|
40
|
+
id: 'copilot',
|
|
41
|
+
url: 'https://example.com',
|
|
42
|
+
npm: overrides?.npm ?? '@custom/copilot',
|
|
43
|
+
},
|
|
44
|
+
name: 'Claude',
|
|
45
|
+
capabilities: {
|
|
46
|
+
temperature: true,
|
|
47
|
+
reasoning: true,
|
|
48
|
+
attachment: true,
|
|
49
|
+
toolcall: true,
|
|
50
|
+
input: {
|
|
51
|
+
text: true,
|
|
52
|
+
audio: false,
|
|
53
|
+
image: false,
|
|
54
|
+
video: false,
|
|
55
|
+
pdf: false,
|
|
56
|
+
},
|
|
57
|
+
output: {
|
|
58
|
+
text: true,
|
|
59
|
+
audio: false,
|
|
60
|
+
image: false,
|
|
61
|
+
video: false,
|
|
62
|
+
pdf: false,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
cost: {
|
|
66
|
+
input: 0,
|
|
67
|
+
output: 0,
|
|
68
|
+
cache: { read: 0, write: 0 },
|
|
69
|
+
},
|
|
70
|
+
limit: { context: 0, output: 0 },
|
|
71
|
+
status: 'active' as const,
|
|
72
|
+
options: {},
|
|
73
|
+
headers: {},
|
|
74
|
+
},
|
|
75
|
+
provider: {
|
|
76
|
+
id: overrides?.providerID ?? 'github-copilot',
|
|
77
|
+
source: 'config' as const,
|
|
78
|
+
info: {
|
|
79
|
+
id: overrides?.providerID ?? 'github-copilot',
|
|
80
|
+
} as never,
|
|
81
|
+
options: {},
|
|
82
|
+
},
|
|
83
|
+
message: {
|
|
84
|
+
id: overrides?.messageID ?? 'message-1',
|
|
85
|
+
sessionID,
|
|
86
|
+
role: 'user' as const,
|
|
87
|
+
time: { created: Date.now() },
|
|
88
|
+
agent: 'orchestrator',
|
|
89
|
+
model: {
|
|
90
|
+
providerID: 'github-copilot',
|
|
91
|
+
modelID: 'claude',
|
|
92
|
+
},
|
|
93
|
+
tools: {},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
describe('createChatHeadersHook', () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
__resetInternalMarkerCacheForTesting();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('sets x-initiator for marked Copilot messages', async () => {
|
|
104
|
+
const ctx = createMockContext([
|
|
105
|
+
createInternalAgentTextPart('internal notification'),
|
|
106
|
+
]);
|
|
107
|
+
const hook = createChatHeadersHook(ctx);
|
|
108
|
+
const output = { headers: {} };
|
|
109
|
+
|
|
110
|
+
await hook['chat.headers'](createInput(), output);
|
|
111
|
+
|
|
112
|
+
expect(output.headers['x-initiator']).toBe('agent');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('skips non-Copilot providers', async () => {
|
|
116
|
+
const ctx = createMockContext([
|
|
117
|
+
createInternalAgentTextPart('internal notification'),
|
|
118
|
+
]);
|
|
119
|
+
const hook = createChatHeadersHook(ctx);
|
|
120
|
+
const output = { headers: {} };
|
|
121
|
+
|
|
122
|
+
await hook['chat.headers'](
|
|
123
|
+
createInput({ providerID: 'anthropic' }),
|
|
124
|
+
output,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(output.headers['x-initiator']).toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('skips requests handled by @ai-sdk/github-copilot', async () => {
|
|
131
|
+
const ctx = createMockContext([
|
|
132
|
+
createInternalAgentTextPart('internal notification'),
|
|
133
|
+
]);
|
|
134
|
+
const hook = createChatHeadersHook(ctx);
|
|
135
|
+
const output = { headers: {} };
|
|
136
|
+
|
|
137
|
+
await hook['chat.headers'](
|
|
138
|
+
createInput({ npm: '@ai-sdk/github-copilot' }),
|
|
139
|
+
output,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(output.headers['x-initiator']).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('skips normal user messages', async () => {
|
|
146
|
+
const ctx = createMockContext([{ type: 'text', text: 'normal prompt' }]);
|
|
147
|
+
const hook = createChatHeadersHook(ctx);
|
|
148
|
+
const output = { headers: {} };
|
|
149
|
+
|
|
150
|
+
await hook['chat.headers'](
|
|
151
|
+
createInput({ messageID: 'message-normal' }),
|
|
152
|
+
output,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(output.headers['x-initiator']).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('caches marked internal messages', async () => {
|
|
159
|
+
const ctx = createMockContext([
|
|
160
|
+
createInternalAgentTextPart('internal notification'),
|
|
161
|
+
]);
|
|
162
|
+
const hook = createChatHeadersHook(ctx);
|
|
163
|
+
const firstOutput = { headers: {} };
|
|
164
|
+
const secondOutput = { headers: {} };
|
|
165
|
+
|
|
166
|
+
await hook['chat.headers'](
|
|
167
|
+
createInput({ messageID: 'message-internal' }),
|
|
168
|
+
firstOutput,
|
|
169
|
+
);
|
|
170
|
+
await hook['chat.headers'](
|
|
171
|
+
createInput({ messageID: 'message-internal' }),
|
|
172
|
+
secondOutput,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(firstOutput.headers['x-initiator']).toBe('agent');
|
|
176
|
+
expect(secondOutput.headers['x-initiator']).toBe('agent');
|
|
177
|
+
expect(ctx.client.session.message).toHaveBeenCalledTimes(1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('does not cache transient message lookup failures', async () => {
|
|
181
|
+
let calls = 0;
|
|
182
|
+
const messageMock = mock(async () => {
|
|
183
|
+
calls += 1;
|
|
184
|
+
if (calls === 1) {
|
|
185
|
+
throw new Error('temporary failure');
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
data: {
|
|
189
|
+
info: { role: 'user' },
|
|
190
|
+
parts: [createInternalAgentTextPart('internal notification')],
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
const ctx = {
|
|
195
|
+
client: {
|
|
196
|
+
session: {
|
|
197
|
+
message: messageMock,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
} as unknown as PluginInput;
|
|
201
|
+
const hook = createChatHeadersHook(ctx);
|
|
202
|
+
const firstOutput = { headers: {} };
|
|
203
|
+
const secondOutput = { headers: {} };
|
|
204
|
+
|
|
205
|
+
await hook['chat.headers'](
|
|
206
|
+
createInput({ messageID: 'message-retry' }),
|
|
207
|
+
firstOutput,
|
|
208
|
+
);
|
|
209
|
+
await hook['chat.headers'](
|
|
210
|
+
createInput({ messageID: 'message-retry' }),
|
|
211
|
+
secondOutput,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(firstOutput.headers['x-initiator']).toBeUndefined();
|
|
215
|
+
expect(secondOutput.headers['x-initiator']).toBe('agent');
|
|
216
|
+
expect(messageMock).toHaveBeenCalledTimes(2);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('caches marked messages by session and message id', async () => {
|
|
220
|
+
const ctx = createMockContext([
|
|
221
|
+
createInternalAgentTextPart('internal notification'),
|
|
222
|
+
]);
|
|
223
|
+
const hook = createChatHeadersHook(ctx);
|
|
224
|
+
|
|
225
|
+
await hook['chat.headers'](
|
|
226
|
+
createInput({ sessionID: 'session-a', messageID: 'message-normal' }),
|
|
227
|
+
{ headers: {} },
|
|
228
|
+
);
|
|
229
|
+
await hook['chat.headers'](
|
|
230
|
+
createInput({ sessionID: 'session-b', messageID: 'message-normal' }),
|
|
231
|
+
{ headers: {} },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(ctx.client.session.message).toHaveBeenCalledTimes(2);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { PluginInput, ProviderContext } from '@opencode-ai/plugin';
|
|
2
|
+
import type { Model, UserMessage } from '@opencode-ai/sdk';
|
|
3
|
+
import { hasInternalInitiatorMarker } from '../utils';
|
|
4
|
+
|
|
5
|
+
interface ChatHeadersInput {
|
|
6
|
+
sessionID: string;
|
|
7
|
+
model: Model;
|
|
8
|
+
provider: ProviderContext;
|
|
9
|
+
message: UserMessage;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ChatHeadersOutput {
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const INTERNAL_MARKER_CACHE_LIMIT = 1000;
|
|
17
|
+
const internalMarkerCache = new Map<string, boolean>();
|
|
18
|
+
|
|
19
|
+
export function __resetInternalMarkerCacheForTesting(): void {
|
|
20
|
+
internalMarkerCache.clear();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getProviderID(input: ChatHeadersInput): string {
|
|
24
|
+
return input.provider.info?.id || input.model.providerID;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isCopilotProvider(providerID: string): boolean {
|
|
28
|
+
return (
|
|
29
|
+
providerID === 'github-copilot' ||
|
|
30
|
+
providerID === 'github-copilot-enterprise'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function hasInternalMarker(
|
|
35
|
+
client: PluginInput['client'],
|
|
36
|
+
sessionID: string,
|
|
37
|
+
messageID: string,
|
|
38
|
+
): Promise<boolean> {
|
|
39
|
+
const cacheKey = `${sessionID}:${messageID}`;
|
|
40
|
+
const cached = internalMarkerCache.get(cacheKey);
|
|
41
|
+
if (cached !== undefined) {
|
|
42
|
+
return cached;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const response = await client.session.message({
|
|
47
|
+
path: { id: sessionID, messageID },
|
|
48
|
+
});
|
|
49
|
+
const hasMarker = (response.data?.parts ?? []).some(
|
|
50
|
+
hasInternalInitiatorMarker,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (hasMarker) {
|
|
54
|
+
if (internalMarkerCache.size >= INTERNAL_MARKER_CACHE_LIMIT) {
|
|
55
|
+
internalMarkerCache.clear();
|
|
56
|
+
}
|
|
57
|
+
internalMarkerCache.set(cacheKey, true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return hasMarker;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createChatHeadersHook(ctx: PluginInput) {
|
|
67
|
+
return {
|
|
68
|
+
'chat.headers': async (
|
|
69
|
+
input: ChatHeadersInput,
|
|
70
|
+
output: ChatHeadersOutput,
|
|
71
|
+
): Promise<void> => {
|
|
72
|
+
if (!isCopilotProvider(getProviderID(input))) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (input.model.api.npm === '@ai-sdk/github-copilot') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!input.message.id || input.message.role !== 'user') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
!(await hasInternalMarker(
|
|
86
|
+
ctx.client,
|
|
87
|
+
input.sessionID,
|
|
88
|
+
input.message.id,
|
|
89
|
+
))
|
|
90
|
+
) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
output.headers['x-initiator'] = 'agent';
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|