@cortexkit/opencode-magic-context 0.5.1 → 0.5.2
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/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli.js +9 -6
- package/dist/index.js +3 -2
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/shared/assistant-message-extractor.ts +74 -0
- package/src/shared/conflict-detector.ts +310 -0
- package/src/shared/conflict-fixer.ts +216 -0
- package/src/shared/data-path.ts +10 -0
- package/src/shared/error-message.ts +3 -0
- package/src/shared/format-bytes.ts +5 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/internal-initiator-marker.ts +1 -0
- package/src/shared/jsonc-parser.ts +138 -0
- package/src/shared/logger.ts +63 -0
- package/src/shared/model-requirements.ts +84 -0
- package/src/shared/model-suggestion-retry.ts +160 -0
- package/src/shared/normalize-sdk-response.ts +40 -0
- package/src/shared/opencode-compaction-detector.test.ts +222 -0
- package/src/shared/opencode-compaction-detector.ts +81 -0
- package/src/shared/opencode-config-dir-types.ts +15 -0
- package/src/shared/opencode-config-dir.ts +38 -0
- package/src/shared/record-type-guard.ts +3 -0
- package/src/shared/system-directive.ts +9 -0
- package/src/shared/tui-config.ts +60 -0
package/dist/cli/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAYA,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAwHjD"}
|
package/dist/cli/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AA6JA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAyPhD"}
|
package/dist/cli.js
CHANGED
|
@@ -8368,6 +8368,7 @@ if (!isTestEnv) {
|
|
|
8368
8368
|
|
|
8369
8369
|
// src/shared/tui-config.ts
|
|
8370
8370
|
var PLUGIN_NAME = "@cortexkit/opencode-magic-context";
|
|
8371
|
+
var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
|
|
8371
8372
|
function resolveTuiConfigPath() {
|
|
8372
8373
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
8373
8374
|
const jsoncPath = join5(configDir, "tui.jsonc");
|
|
@@ -8390,7 +8391,7 @@ function ensureTuiPluginEntry() {
|
|
|
8390
8391
|
if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
|
|
8391
8392
|
return false;
|
|
8392
8393
|
}
|
|
8393
|
-
plugins.push(
|
|
8394
|
+
plugins.push(PLUGIN_ENTRY);
|
|
8394
8395
|
config.plugin = plugins;
|
|
8395
8396
|
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
8396
8397
|
writeFileSync2(configPath, `${import_comment_json.stringify(config, null, 2)}
|
|
@@ -9595,6 +9596,7 @@ async function selectOne(message, options) {
|
|
|
9595
9596
|
|
|
9596
9597
|
// src/cli/doctor.ts
|
|
9597
9598
|
var PLUGIN_NAME2 = "@cortexkit/opencode-magic-context";
|
|
9599
|
+
var PLUGIN_ENTRY_WITH_VERSION = `${PLUGIN_NAME2}@latest`;
|
|
9598
9600
|
async function runDoctor() {
|
|
9599
9601
|
Wt2("Magic Context Doctor");
|
|
9600
9602
|
let issues = 0;
|
|
@@ -9628,7 +9630,7 @@ async function runDoctor() {
|
|
|
9628
9630
|
if (hasPlugin) {
|
|
9629
9631
|
R2.success(`Plugin registered in ${configName}`);
|
|
9630
9632
|
} else {
|
|
9631
|
-
const updatedPlugins = [...plugins,
|
|
9633
|
+
const updatedPlugins = [...plugins, PLUGIN_ENTRY_WITH_VERSION];
|
|
9632
9634
|
config.plugin = updatedPlugins;
|
|
9633
9635
|
writeFileSync3(paths.opencodeConfig, `${import_comment_json2.stringify(config, null, 2)}
|
|
9634
9636
|
`);
|
|
@@ -9691,6 +9693,7 @@ var import_comment_json3 = __toESM(require_src2(), 1);
|
|
|
9691
9693
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9692
9694
|
import { dirname as dirname3 } from "node:path";
|
|
9693
9695
|
var PLUGIN_NAME3 = "@cortexkit/opencode-magic-context";
|
|
9696
|
+
var PLUGIN_ENTRY2 = "@cortexkit/opencode-magic-context@latest";
|
|
9694
9697
|
function ensureDir(dir) {
|
|
9695
9698
|
if (!existsSync6(dir)) {
|
|
9696
9699
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -9709,7 +9712,7 @@ function addPluginToOpenCodeConfig(configPath, format) {
|
|
|
9709
9712
|
ensureDir(dirname3(configPath));
|
|
9710
9713
|
if (format === "none") {
|
|
9711
9714
|
const config = {
|
|
9712
|
-
plugin: [
|
|
9715
|
+
plugin: [PLUGIN_ENTRY2],
|
|
9713
9716
|
compaction: { auto: false, prune: false }
|
|
9714
9717
|
};
|
|
9715
9718
|
writeFileSync4(configPath, `${import_comment_json3.stringify(config, null, 2)}
|
|
@@ -9724,7 +9727,7 @@ function addPluginToOpenCodeConfig(configPath, format) {
|
|
|
9724
9727
|
const plugins = existing.plugin ?? [];
|
|
9725
9728
|
const hasPlugin = plugins.some((p) => p === PLUGIN_NAME3 || p.startsWith(`${PLUGIN_NAME3}@`));
|
|
9726
9729
|
if (!hasPlugin) {
|
|
9727
|
-
plugins.push(
|
|
9730
|
+
plugins.push(PLUGIN_ENTRY2);
|
|
9728
9731
|
}
|
|
9729
9732
|
existing.plugin = plugins;
|
|
9730
9733
|
const compaction = existing.compaction ?? {};
|
|
@@ -9737,7 +9740,7 @@ function addPluginToOpenCodeConfig(configPath, format) {
|
|
|
9737
9740
|
function addPluginToTuiConfig(configPath, format) {
|
|
9738
9741
|
ensureDir(dirname3(configPath));
|
|
9739
9742
|
if (format === "none") {
|
|
9740
|
-
writeFileSync4(configPath, `${import_comment_json3.stringify({ plugin: [
|
|
9743
|
+
writeFileSync4(configPath, `${import_comment_json3.stringify({ plugin: [PLUGIN_ENTRY2] }, null, 2)}
|
|
9741
9744
|
`);
|
|
9742
9745
|
return;
|
|
9743
9746
|
}
|
|
@@ -9749,7 +9752,7 @@ function addPluginToTuiConfig(configPath, format) {
|
|
|
9749
9752
|
const plugins = existing.plugin ?? [];
|
|
9750
9753
|
const hasPlugin = plugins.some((p) => p === PLUGIN_NAME3 || p.startsWith(`${PLUGIN_NAME3}@`));
|
|
9751
9754
|
if (!hasPlugin) {
|
|
9752
|
-
plugins.push(
|
|
9755
|
+
plugins.push(PLUGIN_ENTRY2);
|
|
9753
9756
|
}
|
|
9754
9757
|
existing.plugin = plugins;
|
|
9755
9758
|
writeFileSync4(configPath, `${import_comment_json3.stringify(existing, null, 2)}
|
package/dist/index.js
CHANGED
|
@@ -8502,7 +8502,7 @@ function ensureTuiPluginEntry() {
|
|
|
8502
8502
|
if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
|
|
8503
8503
|
return false;
|
|
8504
8504
|
}
|
|
8505
|
-
plugins.push(
|
|
8505
|
+
plugins.push(PLUGIN_ENTRY);
|
|
8506
8506
|
config2.plugin = plugins;
|
|
8507
8507
|
mkdirSync3(dirname(configPath), { recursive: true });
|
|
8508
8508
|
writeFileSync2(configPath, `${import_comment_json.stringify(config2, null, 2)}
|
|
@@ -8514,11 +8514,12 @@ function ensureTuiPluginEntry() {
|
|
|
8514
8514
|
return false;
|
|
8515
8515
|
}
|
|
8516
8516
|
}
|
|
8517
|
-
var import_comment_json, PLUGIN_NAME = "@cortexkit/opencode-magic-context";
|
|
8517
|
+
var import_comment_json, PLUGIN_NAME = "@cortexkit/opencode-magic-context", PLUGIN_ENTRY;
|
|
8518
8518
|
var init_tui_config = __esm(() => {
|
|
8519
8519
|
init_logger();
|
|
8520
8520
|
init_opencode_config_dir();
|
|
8521
8521
|
import_comment_json = __toESM(require_src2(), 1);
|
|
8522
|
+
PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
|
|
8522
8523
|
});
|
|
8523
8524
|
|
|
8524
8525
|
// src/agents/dreamer.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CA+B9C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"src/tui",
|
|
31
|
+
"src/shared",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
type MessageTime = { created?: number };
|
|
2
|
+
|
|
3
|
+
type MessageInfo = {
|
|
4
|
+
role?: string;
|
|
5
|
+
time?: MessageTime;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type MessagePart = {
|
|
9
|
+
type?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SessionMessage = {
|
|
14
|
+
info?: MessageInfo;
|
|
15
|
+
parts?: unknown;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
import { isRecord } from "./record-type-guard";
|
|
19
|
+
|
|
20
|
+
function asSessionMessage(value: unknown): SessionMessage | null {
|
|
21
|
+
if (!isRecord(value)) return null;
|
|
22
|
+
const info = value.info;
|
|
23
|
+
const parts = value.parts;
|
|
24
|
+
return {
|
|
25
|
+
info: isRecord(info)
|
|
26
|
+
? {
|
|
27
|
+
role: typeof info.role === "string" ? info.role : undefined,
|
|
28
|
+
time: isRecord(info.time)
|
|
29
|
+
? {
|
|
30
|
+
created:
|
|
31
|
+
typeof info.time.created === "number"
|
|
32
|
+
? info.time.created
|
|
33
|
+
: undefined,
|
|
34
|
+
}
|
|
35
|
+
: undefined,
|
|
36
|
+
}
|
|
37
|
+
: undefined,
|
|
38
|
+
parts,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getCreatedTime(message: SessionMessage): number {
|
|
43
|
+
return message.info?.time?.created ?? 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getTextParts(message: SessionMessage): MessagePart[] {
|
|
47
|
+
if (!Array.isArray(message.parts)) return [];
|
|
48
|
+
return message.parts
|
|
49
|
+
.filter((part): part is Record<string, unknown> => isRecord(part))
|
|
50
|
+
.map((part) => ({
|
|
51
|
+
type: typeof part.type === "string" ? part.type : undefined,
|
|
52
|
+
text: typeof part.text === "string" ? part.text : undefined,
|
|
53
|
+
}))
|
|
54
|
+
.filter((part) => part.type === "text" && Boolean(part.text));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function extractLatestAssistantText(messages: unknown): string | null {
|
|
58
|
+
if (!Array.isArray(messages) || messages.length === 0) return null;
|
|
59
|
+
|
|
60
|
+
const assistantMessages = messages
|
|
61
|
+
.map(asSessionMessage)
|
|
62
|
+
.filter((message): message is SessionMessage => message !== null)
|
|
63
|
+
.filter((message) => message.info?.role === "assistant")
|
|
64
|
+
.sort((a, b) => getCreatedTime(b) - getCreatedTime(a));
|
|
65
|
+
|
|
66
|
+
const latest = assistantMessages[0];
|
|
67
|
+
if (!latest) return null;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
getTextParts(latest)
|
|
71
|
+
.map((part) => part.text)
|
|
72
|
+
.join("\n") || null
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readJsoncFile } from "./jsonc-parser";
|
|
3
|
+
import { getOpenCodeConfigPaths } from "./opencode-config-dir";
|
|
4
|
+
|
|
5
|
+
interface OpenCodeConfig {
|
|
6
|
+
compaction?: {
|
|
7
|
+
auto?: boolean;
|
|
8
|
+
prune?: boolean;
|
|
9
|
+
};
|
|
10
|
+
plugin?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface OmoConfig {
|
|
14
|
+
disabled_hooks?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ConflictResult {
|
|
18
|
+
/** Whether any blocking conflict was found */
|
|
19
|
+
hasConflict: boolean;
|
|
20
|
+
/** Human-readable reasons for each conflict */
|
|
21
|
+
reasons: string[];
|
|
22
|
+
/** Which conflicts were found — used for targeted fixes */
|
|
23
|
+
conflicts: {
|
|
24
|
+
compactionAuto: boolean;
|
|
25
|
+
compactionPrune: boolean;
|
|
26
|
+
dcpPlugin: boolean;
|
|
27
|
+
omoPreemptiveCompaction: boolean;
|
|
28
|
+
omoContextWindowMonitor: boolean;
|
|
29
|
+
omoAnthropicRecovery: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect all conflicts that would prevent magic-context from working correctly.
|
|
35
|
+
* Checks: OpenCode compaction, DCP plugin, OMO conflicting hooks.
|
|
36
|
+
*/
|
|
37
|
+
export function detectConflicts(directory: string): ConflictResult {
|
|
38
|
+
const conflicts: ConflictResult["conflicts"] = {
|
|
39
|
+
compactionAuto: false,
|
|
40
|
+
compactionPrune: false,
|
|
41
|
+
dcpPlugin: false,
|
|
42
|
+
omoPreemptiveCompaction: false,
|
|
43
|
+
omoContextWindowMonitor: false,
|
|
44
|
+
omoAnthropicRecovery: false,
|
|
45
|
+
};
|
|
46
|
+
const reasons: string[] = [];
|
|
47
|
+
|
|
48
|
+
// --- Check OpenCode compaction config ---
|
|
49
|
+
const compactionResult = checkCompaction(directory);
|
|
50
|
+
if (compactionResult.auto) {
|
|
51
|
+
conflicts.compactionAuto = true;
|
|
52
|
+
reasons.push("OpenCode auto-compaction is enabled (compaction.auto=true)");
|
|
53
|
+
}
|
|
54
|
+
if (compactionResult.prune) {
|
|
55
|
+
conflicts.compactionPrune = true;
|
|
56
|
+
reasons.push("OpenCode prune is enabled (compaction.prune=true)");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Check for DCP plugin ---
|
|
60
|
+
const dcpFound = checkDcpPlugin(directory);
|
|
61
|
+
if (dcpFound) {
|
|
62
|
+
conflicts.dcpPlugin = true;
|
|
63
|
+
reasons.push(
|
|
64
|
+
"opencode-dcp plugin is installed — it conflicts with Magic Context's context management",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Check OMO conflicting hooks ---
|
|
69
|
+
const omoResult = checkOmoHooks(directory);
|
|
70
|
+
if (omoResult.preemptiveCompaction) {
|
|
71
|
+
conflicts.omoPreemptiveCompaction = true;
|
|
72
|
+
reasons.push(
|
|
73
|
+
"oh-my-opencode preemptive-compaction hook is active — it triggers compaction that conflicts with historian",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (omoResult.contextWindowMonitor) {
|
|
77
|
+
conflicts.omoContextWindowMonitor = true;
|
|
78
|
+
reasons.push(
|
|
79
|
+
"oh-my-opencode context-window-monitor hook is active — it injects usage warnings that overlap with Magic Context nudges",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (omoResult.anthropicRecovery) {
|
|
83
|
+
conflicts.omoAnthropicRecovery = true;
|
|
84
|
+
reasons.push(
|
|
85
|
+
"oh-my-opencode anthropic-context-window-limit-recovery hook is active — it triggers emergency compaction that bypasses historian",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
hasConflict: reasons.length > 0,
|
|
91
|
+
reasons,
|
|
92
|
+
conflicts,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Compaction detection (extracted from opencode-compaction-detector.ts) ---
|
|
97
|
+
|
|
98
|
+
function checkCompaction(directory: string): { auto: boolean; prune: boolean } {
|
|
99
|
+
if (process.env.OPENCODE_DISABLE_AUTOCOMPACT) {
|
|
100
|
+
return { auto: false, prune: false };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check project-level config first (higher precedence)
|
|
104
|
+
const projectResult = readProjectCompaction(directory);
|
|
105
|
+
if (projectResult.resolved) return projectResult;
|
|
106
|
+
|
|
107
|
+
// Fall back to user-level config
|
|
108
|
+
const userResult = readUserCompaction();
|
|
109
|
+
if (userResult.resolved) return userResult;
|
|
110
|
+
|
|
111
|
+
// Default: OpenCode has compaction enabled by default
|
|
112
|
+
return { auto: true, prune: false };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readProjectCompaction(directory: string): {
|
|
116
|
+
auto: boolean;
|
|
117
|
+
prune: boolean;
|
|
118
|
+
resolved: boolean;
|
|
119
|
+
} {
|
|
120
|
+
// .opencode/ config has higher precedence
|
|
121
|
+
const dotOcJsonc = join(directory, ".opencode", "opencode.jsonc");
|
|
122
|
+
const dotOcJson = join(directory, ".opencode", "opencode.json");
|
|
123
|
+
const dotOcConfig =
|
|
124
|
+
readJsoncFile<OpenCodeConfig>(dotOcJsonc) ?? readJsoncFile<OpenCodeConfig>(dotOcJson);
|
|
125
|
+
|
|
126
|
+
if (dotOcConfig?.compaction) {
|
|
127
|
+
const c = dotOcConfig.compaction;
|
|
128
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
129
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Root-level project config
|
|
134
|
+
const rootJsonc = join(directory, "opencode.jsonc");
|
|
135
|
+
const rootJson = join(directory, "opencode.json");
|
|
136
|
+
const rootConfig =
|
|
137
|
+
readJsoncFile<OpenCodeConfig>(rootJsonc) ?? readJsoncFile<OpenCodeConfig>(rootJson);
|
|
138
|
+
|
|
139
|
+
if (rootConfig?.compaction) {
|
|
140
|
+
const c = rootConfig.compaction;
|
|
141
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
142
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { auto: false, prune: false, resolved: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function readUserCompaction(): { auto: boolean; prune: boolean; resolved: boolean } {
|
|
150
|
+
try {
|
|
151
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
152
|
+
const config =
|
|
153
|
+
readJsoncFile<OpenCodeConfig>(paths.configJsonc) ??
|
|
154
|
+
readJsoncFile<OpenCodeConfig>(paths.configJson);
|
|
155
|
+
|
|
156
|
+
if (config?.compaction) {
|
|
157
|
+
const c = config.compaction;
|
|
158
|
+
if (c.auto !== undefined || c.prune !== undefined) {
|
|
159
|
+
return { auto: c.auto === true, prune: c.prune === true, resolved: true };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Intentional: config read is best-effort
|
|
164
|
+
}
|
|
165
|
+
return { auto: false, prune: false, resolved: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- DCP detection ---
|
|
169
|
+
|
|
170
|
+
function checkDcpPlugin(directory: string): boolean {
|
|
171
|
+
const plugins = collectPluginEntries(directory);
|
|
172
|
+
return plugins.some((p) => p.includes("opencode-dcp"));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function collectPluginEntries(directory: string): string[] {
|
|
176
|
+
const plugins: string[] = [];
|
|
177
|
+
|
|
178
|
+
// Project-level configs
|
|
179
|
+
for (const configPath of [
|
|
180
|
+
join(directory, ".opencode", "opencode.jsonc"),
|
|
181
|
+
join(directory, ".opencode", "opencode.json"),
|
|
182
|
+
join(directory, "opencode.jsonc"),
|
|
183
|
+
join(directory, "opencode.json"),
|
|
184
|
+
]) {
|
|
185
|
+
const config = readJsoncFile<OpenCodeConfig>(configPath);
|
|
186
|
+
if (config?.plugin) {
|
|
187
|
+
plugins.push(...config.plugin);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// User-level config
|
|
192
|
+
try {
|
|
193
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
194
|
+
for (const configPath of [paths.configJsonc, paths.configJson]) {
|
|
195
|
+
const config = readJsoncFile<OpenCodeConfig>(configPath);
|
|
196
|
+
if (config?.plugin) {
|
|
197
|
+
plugins.push(...config.plugin);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// best-effort
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return plugins;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- OMO hook detection ---
|
|
208
|
+
|
|
209
|
+
function checkOmoHooks(directory: string): {
|
|
210
|
+
preemptiveCompaction: boolean;
|
|
211
|
+
contextWindowMonitor: boolean;
|
|
212
|
+
anthropicRecovery: boolean;
|
|
213
|
+
} {
|
|
214
|
+
const result = {
|
|
215
|
+
preemptiveCompaction: false,
|
|
216
|
+
contextWindowMonitor: false,
|
|
217
|
+
anthropicRecovery: false,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// First check if OMO is even installed
|
|
221
|
+
const plugins = collectPluginEntries(directory);
|
|
222
|
+
const hasOmo = plugins.some(
|
|
223
|
+
(p) =>
|
|
224
|
+
p.includes("oh-my-opencode") ||
|
|
225
|
+
p.includes("oh-my-openagent") ||
|
|
226
|
+
p.includes("@code-yeongyu/"),
|
|
227
|
+
);
|
|
228
|
+
if (!hasOmo) return result;
|
|
229
|
+
|
|
230
|
+
// Read OMO config to check disabled_hooks
|
|
231
|
+
const disabledHooks = readOmoDisabledHooks(directory);
|
|
232
|
+
|
|
233
|
+
// Hooks are ACTIVE unless explicitly in disabled_hooks
|
|
234
|
+
result.preemptiveCompaction = !disabledHooks.has("preemptive-compaction");
|
|
235
|
+
result.contextWindowMonitor = !disabledHooks.has("context-window-monitor");
|
|
236
|
+
result.anthropicRecovery = !disabledHooks.has("anthropic-context-window-limit-recovery");
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readOmoDisabledHooks(directory: string): Set<string> {
|
|
242
|
+
const disabled = new Set<string>();
|
|
243
|
+
|
|
244
|
+
// Check both old and new OMO config names
|
|
245
|
+
const configNames = [
|
|
246
|
+
"oh-my-opencode.jsonc",
|
|
247
|
+
"oh-my-opencode.json",
|
|
248
|
+
"oh-my-openagent.jsonc",
|
|
249
|
+
"oh-my-openagent.json",
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode" });
|
|
254
|
+
for (const name of configNames) {
|
|
255
|
+
const configPath = join(paths.configDir, name);
|
|
256
|
+
const config = readJsoncFile<OmoConfig>(configPath);
|
|
257
|
+
if (config?.disabled_hooks) {
|
|
258
|
+
for (const hook of config.disabled_hooks) {
|
|
259
|
+
disabled.add(hook);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// best-effort
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Also check project-level OMO configs
|
|
268
|
+
for (const name of configNames) {
|
|
269
|
+
const config = readJsoncFile<OmoConfig>(join(directory, name));
|
|
270
|
+
if (config?.disabled_hooks) {
|
|
271
|
+
for (const hook of config.disabled_hooks) {
|
|
272
|
+
disabled.add(hook);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return disabled;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Generate a user-facing summary of conflicts for display in dialogs/notifications.
|
|
282
|
+
*/
|
|
283
|
+
export function formatConflictSummary(result: ConflictResult): string {
|
|
284
|
+
if (!result.hasConflict) return "";
|
|
285
|
+
|
|
286
|
+
const lines = [
|
|
287
|
+
"⚠️ Magic Context is disabled due to conflicting configuration:\n",
|
|
288
|
+
...result.reasons.map((r) => ` • ${r}`),
|
|
289
|
+
"",
|
|
290
|
+
"Run `bunx @cortexkit/opencode-magic-context doctor` to fix,",
|
|
291
|
+
"or resolve these conflicts manually in your OpenCode config.",
|
|
292
|
+
];
|
|
293
|
+
return lines.join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate a short conflict summary for ignored message display.
|
|
298
|
+
*/
|
|
299
|
+
export function formatConflictShort(result: ConflictResult): string {
|
|
300
|
+
if (!result.hasConflict) return "";
|
|
301
|
+
|
|
302
|
+
const lines = [
|
|
303
|
+
"⚠️ Magic Context is disabled due to conflicting configuration:",
|
|
304
|
+
"",
|
|
305
|
+
...result.reasons.map((r) => `• ${r}`),
|
|
306
|
+
"",
|
|
307
|
+
"Fix: run `bunx @cortexkit/opencode-magic-context doctor`",
|
|
308
|
+
];
|
|
309
|
+
return lines.join("\n");
|
|
310
|
+
}
|