@cortexkit/opencode-magic-context 0.33.0 → 0.34.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/dist/config/agent-disable.d.ts +22 -0
- package/dist/config/agent-disable.d.ts.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +7 -0
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +144 -24
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config/transform-mode.d.ts +2 -0
- package/dist/config/transform-mode.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-bwa99a04.js → conflict-warning-hook-fvpfvt0w.js} +1 -1
- package/dist/{derive-budgets-e9j09sjr.js → derive-budgets-9crp0cvd.js} +1 -1
- package/dist/features/builtin-commands/commands.d.ts +1 -1
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compaction-marker.d.ts +106 -0
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +0 -2
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +0 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/fail-closed-block.d.ts +10 -0
- package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/index.d.ts +2 -2
- package/dist/features/magic-context/git-commits/index.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +0 -2
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +0 -13
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +7 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts +1 -2
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts +0 -6
- package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity.d.ts +2 -6
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +0 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +1 -6
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +0 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
- package/dist/features/magic-context/mural/render-mural.d.ts.map +1 -1
- package/dist/features/magic-context/mural/storage-mural.d.ts +0 -1
- package/dist/features/magic-context/mural/storage-mural.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +8 -1
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +16 -2
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/schema-fence-probe.d.ts +28 -0
- package/dist/features/magic-context/schema-fence-probe.d.ts.map +1 -0
- package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +0 -2
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +0 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +0 -16
- package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +35 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +38 -5
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +0 -2
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/types.d.ts +0 -3
- package/dist/hooks/auto-update-checker/types.d.ts.map +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/child-session-spawn.d.ts +26 -0
- package/dist/hooks/magic-context/child-session-spawn.d.ts.map +1 -0
- package/dist/hooks/magic-context/command-handler.d.ts +2 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +49 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-off-transition.d.ts +96 -0
- package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts +0 -12
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +12 -5
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +47 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +2 -2
- package/dist/hooks/magic-context/event-handler.d.ts +7 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +13 -21
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +11 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +18 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts +20 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +39 -0
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +19 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +30 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +27 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-kxz4tn6m.js → index-56yjaeb2.js} +56 -4
- package/dist/{index-bmkea2dq.js → index-b563sga5.js} +40 -287
- package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
- package/dist/{index-k5y60ymm.js → index-dtas91s5.js} +27 -6
- package/dist/index-fcyvznch.js +279 -0
- package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
- package/dist/{index-h86twgsp.js → index-s0mdn690.js} +2113 -1067
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1356 -627
- package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +19 -8
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts +10 -0
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +3 -7
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts +8 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-yh7pepvx.js → recomp-orchestrator-8e82vmvb.js} +6 -5
- package/dist/send-session-notification-6njqewzk.js +20 -0
- package/dist/shared/conflict-detector.d.ts +38 -1
- package/dist/shared/conflict-detector.d.ts.map +1 -1
- package/dist/shared/conflict-fixer.d.ts +18 -1
- package/dist/shared/conflict-fixer.d.ts.map +1 -1
- package/dist/shared/context-limit-provenance.d.ts +3 -0
- package/dist/shared/context-limit-provenance.d.ts.map +1 -0
- package/dist/shared/escalation-bands.d.ts +9 -0
- package/dist/shared/escalation-bands.d.ts.map +1 -0
- package/dist/shared/format-threshold.d.ts +1 -1
- package/dist/shared/models-dev-cache.d.ts +29 -3
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +46 -1
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +9 -4
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/storage-permissions.d.ts +5 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -0
- package/dist/{storage-meta-session-1phy0b1z.js → storage-meta-session-xt41zbw4.js} +2 -1
- package/dist/{task-config-dn9k9szr.js → task-config-5nx3qfz8.js} +1 -2
- package/dist/tui/compaction-off.d.ts +13 -0
- package/dist/tui/compaction-off.d.ts.map +1 -0
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/compaction-off.d.ts +13 -0
- package/dist/tui-compiled/compaction-off.d.ts.map +1 -0
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/agents/dreamer.ts +68 -0
- package/src/agents/hidden-agent-registrations.ts +331 -0
- package/src/agents/historian.ts +3 -0
- package/src/agents/language-directive.test.ts +138 -0
- package/src/agents/language-directive.ts +121 -0
- package/src/agents/magic-context-prompt.language.test.ts +35 -0
- package/src/agents/magic-context-prompt.test.ts +292 -0
- package/src/agents/magic-context-prompt.ts +182 -0
- package/src/agents/permissions.test.ts +252 -0
- package/src/agents/permissions.ts +186 -0
- package/src/agents/sidekick.ts +1 -0
- package/src/agents/smart-note-compiler.ts +1 -0
- package/src/config/agent-disable.ts +106 -0
- package/src/config/compaction-accessor-guard.test.ts +82 -0
- package/src/config/compaction-backcompat.test.ts +52 -0
- package/src/config/index.test.ts +904 -0
- package/src/config/index.ts +601 -0
- package/src/config/migrate-config-location.test.ts +332 -0
- package/src/config/migrate-config-location.ts +635 -0
- package/src/config/migrate-dreamer-v2.test.ts +287 -0
- package/src/config/migrate-dreamer-v2.ts +336 -0
- package/src/config/migrate-experimental.ts +159 -0
- package/src/config/project-security.test.ts +366 -0
- package/src/config/project-security.ts +588 -0
- package/src/config/prune-config-leaf.test.ts +78 -0
- package/src/config/prune-config-leaf.ts +68 -0
- package/src/config/removed-config-keys.test.ts +116 -0
- package/src/config/schema/agent-overrides.ts +45 -0
- package/src/config/schema/magic-context.test.ts +358 -0
- package/src/config/schema/magic-context.ts +1002 -0
- package/src/config/transform-mode.test.ts +62 -0
- package/src/config/transform-mode.ts +34 -0
- package/src/config/variable.test.ts +323 -0
- package/src/config/variable.ts +192 -0
- package/src/features/magic-context/defaults.ts +1 -0
- package/src/features/magic-context/dreamer/cron.ts +243 -0
- package/src/features/magic-context/dreamer/task-registry.ts +104 -0
- package/src/shared/conflict-detector.test.ts +251 -1
- package/src/shared/conflict-detector.ts +84 -5
- package/src/shared/conflict-fixer.test.ts +309 -0
- package/src/shared/conflict-fixer.ts +82 -5
- package/src/shared/context-limit-provenance.ts +6 -0
- package/src/shared/escalation-bands.test.ts +23 -0
- package/src/shared/escalation-bands.ts +18 -0
- package/src/shared/format-threshold.ts +1 -1
- package/src/shared/models-dev-cache.test.ts +171 -0
- package/src/shared/models-dev-cache.ts +195 -38
- package/src/shared/rpc-server.ts +27 -18
- package/src/shared/rpc-types.ts +46 -1
- package/src/shared/sqlite.ts +13 -33
- package/src/shared/storage-permissions.ts +20 -0
- package/src/tui/compaction-off.test.ts +56 -0
- package/src/tui/compaction-off.ts +29 -0
- package/src/tui/data/context-db.ts +6 -0
- package/src/tui/index.tsx +113 -65
- package/src/tui/slots/sidebar-content.tsx +139 -83
- package/src/tui-compiled/compaction-off.ts +29 -0
- package/src/tui-compiled/data/context-db.ts +6 -0
- package/src/tui-compiled/index.tsx +558 -455
- package/src/tui-compiled/slots/sidebar-content.tsx +237 -161
- package/dist/config/schema.d.ts +0 -3
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/features/magic-context/plugin-messages.d.ts +0 -75
- package/dist/features/magic-context/plugin-messages.d.ts.map +0 -1
- package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts +0 -21
- package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts.map +0 -1
- package/dist/index-anhrfygk.js +0 -57
- package/dist/send-session-notification-tghq9gnh.js +0 -11
- package/dist/shared/opencode-compaction-detector.d.ts +0 -2
- package/dist/shared/opencode-compaction-detector.d.ts.map +0 -1
- package/dist/shared/transcript-opencode.d.ts +0 -71
- package/dist/shared/transcript-opencode.d.ts.map +0 -1
- package/dist/tools/index.d.ts +0 -6
- package/dist/tools/index.d.ts.map +0 -1
- package/src/shared/opencode-compaction-detector.test.ts +0 -239
- package/src/shared/opencode-compaction-detector.ts +0 -81
- package/src/shared/transcript-opencode.ts +0 -302
- /package/dist/{index-v7fmc1s2.js → index-ygcng9t8.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { readJsoncFile } from "./jsonc-parser";
|
|
3
4
|
import { getOpenCodeConfigPaths } from "./opencode-config-dir";
|
|
@@ -15,6 +16,14 @@ interface OmoConfig {
|
|
|
15
16
|
disabled_hooks?: string[];
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/** Shape of the new unified omo.jsonc (oh-my-openagent >= 4.19.0).
|
|
20
|
+
* Hook config lives inside the `[opencode]` harness block. */
|
|
21
|
+
interface OmoV2Config {
|
|
22
|
+
"[opencode]"?: {
|
|
23
|
+
disabled_hooks?: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
export interface ConflictResult {
|
|
19
28
|
/** Whether any blocking conflict was found */
|
|
20
29
|
hasConflict: boolean;
|
|
@@ -29,13 +38,55 @@ export interface ConflictResult {
|
|
|
29
38
|
omoContextWindowMonitor: boolean;
|
|
30
39
|
omoAnthropicRecovery: boolean;
|
|
31
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolved native compaction state observed during detection, for honest
|
|
43
|
+
* reporting in both MC modes. `auto`/`prune` reflect the OpenCode
|
|
44
|
+
* `compaction` block as resolved by the detector (env override, project
|
|
45
|
+
* then user, default-on). They are populated even when MC compaction is
|
|
46
|
+
* OFF (in which case they are NOT flagged as conflicts).
|
|
47
|
+
*/
|
|
48
|
+
nativeCompaction: {
|
|
49
|
+
auto: boolean;
|
|
50
|
+
prune: boolean;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Options for {@link detectConflicts}.
|
|
56
|
+
*
|
|
57
|
+
* `compactionEnabled` is the boot-resolved Magic Context compaction mode
|
|
58
|
+
* (the result of {@link isCompactionEnabled} on the resolved user-tier
|
|
59
|
+
* config). It MUST be threaded through every production call site — plugin
|
|
60
|
+
* boot, setup, doctor, conflict-fixer — so the MC-mode decision is never
|
|
61
|
+
* re-derived at a call site. A call site that genuinely cannot supply it
|
|
62
|
+
* (e.g. a low-level native-config reader with no MC config handle) MUST
|
|
63
|
+
* omit it and accept the default `true` (mode-on) behavior, which preserves
|
|
64
|
+
* today's conflict semantics; it must never silently skip the check.
|
|
65
|
+
*
|
|
66
|
+
* When `compactionEnabled` is `false` (compaction-off mode), OpenCode
|
|
67
|
+
* `compaction.auto=true` / `compaction.prune=true` are NOT plugin-disabling
|
|
68
|
+
* conflicts — native compaction is the user's chosen window manager. DCP
|
|
69
|
+
* and the three OMO conflict classes keep their existing policy in BOTH
|
|
70
|
+
* modes.
|
|
71
|
+
*/
|
|
72
|
+
export interface DetectConflictsOptions {
|
|
73
|
+
compactionEnabled?: boolean;
|
|
32
74
|
}
|
|
33
75
|
|
|
34
76
|
/**
|
|
35
77
|
* Detect all conflicts that would prevent magic-context from working correctly.
|
|
36
78
|
* Checks: OpenCode compaction, DCP plugin, OMO conflicting hooks.
|
|
79
|
+
*
|
|
80
|
+
* `compactionEnabled` (default `true`) is the resolved MC compaction mode.
|
|
81
|
+
* When `false` (compaction-off mode), native `compaction.auto`/`prune` are
|
|
82
|
+
* reported in {@link ConflictResult.nativeCompaction} but are NOT flagged as
|
|
83
|
+
* conflicts — native compaction is the intended window manager in that mode.
|
|
37
84
|
*/
|
|
38
|
-
export function detectConflicts(
|
|
85
|
+
export function detectConflicts(
|
|
86
|
+
directory: string,
|
|
87
|
+
options?: DetectConflictsOptions,
|
|
88
|
+
): ConflictResult {
|
|
89
|
+
const compactionEnabled = options?.compactionEnabled ?? true;
|
|
39
90
|
const conflicts: ConflictResult["conflicts"] = {
|
|
40
91
|
compactionAuto: false,
|
|
41
92
|
compactionPrune: false,
|
|
@@ -48,11 +99,15 @@ export function detectConflicts(directory: string): ConflictResult {
|
|
|
48
99
|
|
|
49
100
|
// --- Check OpenCode compaction config ---
|
|
50
101
|
const compactionResult = checkCompaction(directory);
|
|
51
|
-
|
|
102
|
+
// Native compaction is a conflict ONLY when MC compaction is ON. In
|
|
103
|
+
// compaction-off mode the user has explicitly handed the window to native
|
|
104
|
+
// compaction (or nothing), so compaction.auto=true / prune=true are the
|
|
105
|
+
// intended state, not a plugin-disabling conflict.
|
|
106
|
+
if (compactionEnabled && compactionResult.auto) {
|
|
52
107
|
conflicts.compactionAuto = true;
|
|
53
108
|
reasons.push("OpenCode auto-compaction is enabled (compaction.auto=true)");
|
|
54
109
|
}
|
|
55
|
-
if (compactionResult.prune) {
|
|
110
|
+
if (compactionEnabled && compactionResult.prune) {
|
|
56
111
|
conflicts.compactionPrune = true;
|
|
57
112
|
reasons.push("OpenCode prune is enabled (compaction.prune=true)");
|
|
58
113
|
}
|
|
@@ -91,6 +146,7 @@ export function detectConflicts(directory: string): ConflictResult {
|
|
|
91
146
|
hasConflict: reasons.length > 0,
|
|
92
147
|
reasons,
|
|
93
148
|
conflicts,
|
|
149
|
+
nativeCompaction: { auto: compactionResult.auto, prune: compactionResult.prune },
|
|
94
150
|
};
|
|
95
151
|
}
|
|
96
152
|
|
|
@@ -318,7 +374,7 @@ function checkOmoHooks(directory: string): {
|
|
|
318
374
|
function readOmoDisabledHooks(directory: string): Set<string> {
|
|
319
375
|
const disabled = new Set<string>();
|
|
320
376
|
|
|
321
|
-
// Check both old and new OMO config names
|
|
377
|
+
// Check both old and new OMO config names in the OpenCode config dir
|
|
322
378
|
const configNames = [
|
|
323
379
|
"oh-my-opencode.jsonc",
|
|
324
380
|
"oh-my-opencode.json",
|
|
@@ -341,7 +397,7 @@ function readOmoDisabledHooks(directory: string): Set<string> {
|
|
|
341
397
|
// best-effort
|
|
342
398
|
}
|
|
343
399
|
|
|
344
|
-
// Also check project-level OMO configs
|
|
400
|
+
// Also check project-level OMO configs (old format)
|
|
345
401
|
for (const name of configNames) {
|
|
346
402
|
const config = readJsoncFile<OmoConfig>(join(directory, name));
|
|
347
403
|
if (config?.disabled_hooks) {
|
|
@@ -351,6 +407,29 @@ function readOmoDisabledHooks(directory: string): Set<string> {
|
|
|
351
407
|
}
|
|
352
408
|
}
|
|
353
409
|
|
|
410
|
+
// --- New unified omo.jsonc (oh-my-openagent >= 4.19.0) ---
|
|
411
|
+
// User-level: ~/.omo/omo.jsonc (fallback ~/.omo/omo.json)
|
|
412
|
+
const homeDir = process.env.HOME || homedir();
|
|
413
|
+
const omoHomeDir = join(homeDir, ".omo");
|
|
414
|
+
for (const name of ["omo.jsonc", "omo.json"]) {
|
|
415
|
+
const config = readJsoncFile<OmoV2Config>(join(omoHomeDir, name));
|
|
416
|
+
if (config?.["[opencode]"]?.disabled_hooks) {
|
|
417
|
+
for (const hook of config["[opencode]"].disabled_hooks) {
|
|
418
|
+
disabled.add(hook);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Project-level: .omo/omo.jsonc (fallback .omo/omo.json)
|
|
424
|
+
for (const name of ["omo.jsonc", "omo.json"]) {
|
|
425
|
+
const config = readJsoncFile<OmoV2Config>(join(directory, ".omo", name));
|
|
426
|
+
if (config?.["[opencode]"]?.disabled_hooks) {
|
|
427
|
+
for (const hook of config["[opencode]"].disabled_hooks) {
|
|
428
|
+
disabled.add(hook);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
354
433
|
return disabled;
|
|
355
434
|
}
|
|
356
435
|
|
|
@@ -5,6 +5,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { parse as parseJsonc } from "comment-json";
|
|
8
|
+
import { detectConflicts } from "./conflict-detector";
|
|
8
9
|
import { fixConflicts } from "./conflict-fixer";
|
|
9
10
|
|
|
10
11
|
const noOmoConflicts = {
|
|
@@ -17,19 +18,24 @@ describe("fixConflicts", () => {
|
|
|
17
18
|
let root: string;
|
|
18
19
|
let projectDir: string;
|
|
19
20
|
let userConfigDir: string;
|
|
21
|
+
let homeDir: string;
|
|
20
22
|
let originalEnv: Record<string, string | undefined>;
|
|
21
23
|
|
|
22
24
|
beforeEach(() => {
|
|
23
25
|
root = mkdtempSync(join(tmpdir(), "mc-conflict-fixer-"));
|
|
24
26
|
projectDir = join(root, "project");
|
|
25
27
|
userConfigDir = join(root, "user-config", "opencode");
|
|
28
|
+
homeDir = join(root, "home");
|
|
26
29
|
mkdirSync(projectDir, { recursive: true });
|
|
27
30
|
mkdirSync(userConfigDir, { recursive: true });
|
|
31
|
+
mkdirSync(homeDir, { recursive: true });
|
|
28
32
|
originalEnv = {
|
|
29
33
|
OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
|
|
30
34
|
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
35
|
+
HOME: process.env.HOME,
|
|
31
36
|
};
|
|
32
37
|
process.env.OPENCODE_CONFIG_DIR = userConfigDir;
|
|
38
|
+
process.env.HOME = homeDir;
|
|
33
39
|
delete process.env.XDG_CONFIG_HOME;
|
|
34
40
|
});
|
|
35
41
|
|
|
@@ -125,4 +131,307 @@ describe("fixConflicts", () => {
|
|
|
125
131
|
["@other/opencode-dcp-slim@latest", { enabled: true }],
|
|
126
132
|
]);
|
|
127
133
|
});
|
|
134
|
+
|
|
135
|
+
// --- Unified OMO config (oh-my-openagent >= 4.19.0) ---
|
|
136
|
+
|
|
137
|
+
describe("unified OMO config paths", () => {
|
|
138
|
+
const omoConflicts = {
|
|
139
|
+
compactionAuto: false,
|
|
140
|
+
compactionPrune: false,
|
|
141
|
+
dcpPlugin: false,
|
|
142
|
+
omoPreemptiveCompaction: true,
|
|
143
|
+
omoContextWindowMonitor: true,
|
|
144
|
+
omoAnthropicRecovery: true,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
it("disables hooks inside [opencode] block in ~/.omo/omo.jsonc, comments survive, detector confirms", () => {
|
|
148
|
+
const omoDir = join(homeDir, ".omo");
|
|
149
|
+
mkdirSync(omoDir, { recursive: true });
|
|
150
|
+
const configPath = join(omoDir, "omo.jsonc");
|
|
151
|
+
writeFileSync(
|
|
152
|
+
configPath,
|
|
153
|
+
`{
|
|
154
|
+
// top-level comment
|
|
155
|
+
"some-omo-setting": true,
|
|
156
|
+
"[opencode]": {
|
|
157
|
+
// opencode block comment
|
|
158
|
+
"other_setting": "value"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
165
|
+
|
|
166
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
167
|
+
|
|
168
|
+
const updatedText = readFileSync(configPath, "utf-8");
|
|
169
|
+
expect(updatedText).toContain("top-level comment");
|
|
170
|
+
expect(updatedText).toContain("opencode block comment");
|
|
171
|
+
expect(updatedText).toContain("some-omo-setting");
|
|
172
|
+
|
|
173
|
+
const updated = parseJsonc(updatedText) as Record<string, unknown>;
|
|
174
|
+
const opencodeBlock = updated["[opencode]"] as Record<string, unknown>;
|
|
175
|
+
expect(opencodeBlock.disabled_hooks).toEqual([
|
|
176
|
+
"context-window-monitor",
|
|
177
|
+
"preemptive-compaction",
|
|
178
|
+
"anthropic-context-window-limit-recovery",
|
|
179
|
+
]);
|
|
180
|
+
expect(opencodeBlock.other_setting).toBe("value");
|
|
181
|
+
|
|
182
|
+
// Round-trip: detector should now report no OMO conflicts
|
|
183
|
+
// Need a project-level opencode.json with OMO plugin for detection
|
|
184
|
+
writeFileSync(
|
|
185
|
+
join(projectDir, "opencode.json"),
|
|
186
|
+
JSON.stringify({ plugin: ["oh-my-opencode"] }),
|
|
187
|
+
);
|
|
188
|
+
const result = detectConflicts(projectDir);
|
|
189
|
+
expect(result.conflicts.omoPreemptiveCompaction).toBe(false);
|
|
190
|
+
expect(result.conflicts.omoContextWindowMonitor).toBe(false);
|
|
191
|
+
expect(result.conflicts.omoAnthropicRecovery).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("creates [opencode] block when missing in unified omo.jsonc", () => {
|
|
195
|
+
const omoDir = join(homeDir, ".omo");
|
|
196
|
+
mkdirSync(omoDir, { recursive: true });
|
|
197
|
+
const configPath = join(omoDir, "omo.jsonc");
|
|
198
|
+
writeFileSync(
|
|
199
|
+
configPath,
|
|
200
|
+
`{
|
|
201
|
+
// just a top-level setting, no [opencode] block
|
|
202
|
+
"some-omo-setting": true
|
|
203
|
+
}
|
|
204
|
+
`,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
208
|
+
|
|
209
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
210
|
+
|
|
211
|
+
const updatedText = readFileSync(configPath, "utf-8");
|
|
212
|
+
expect(updatedText).toContain("top-level setting");
|
|
213
|
+
|
|
214
|
+
const updated = parseJsonc(updatedText) as Record<string, unknown>;
|
|
215
|
+
const opencodeBlock = updated["[opencode]"] as Record<string, unknown>;
|
|
216
|
+
expect(opencodeBlock.disabled_hooks).toEqual([
|
|
217
|
+
"context-window-monitor",
|
|
218
|
+
"preemptive-compaction",
|
|
219
|
+
"anthropic-context-window-limit-recovery",
|
|
220
|
+
]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("writes to project-level .omo/omo.jsonc", () => {
|
|
224
|
+
const omoDir = join(projectDir, ".omo");
|
|
225
|
+
mkdirSync(omoDir, { recursive: true });
|
|
226
|
+
const configPath = join(omoDir, "omo.jsonc");
|
|
227
|
+
writeFileSync(
|
|
228
|
+
configPath,
|
|
229
|
+
JSON.stringify({
|
|
230
|
+
"[opencode]": {},
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
235
|
+
|
|
236
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
237
|
+
|
|
238
|
+
const updated = parseJsonc(readFileSync(configPath, "utf-8")) as Record<
|
|
239
|
+
string,
|
|
240
|
+
unknown
|
|
241
|
+
>;
|
|
242
|
+
const opencodeBlock = updated["[opencode]"] as Record<string, unknown>;
|
|
243
|
+
expect(opencodeBlock.disabled_hooks).toEqual([
|
|
244
|
+
"context-window-monitor",
|
|
245
|
+
"preemptive-compaction",
|
|
246
|
+
"anthropic-context-window-limit-recovery",
|
|
247
|
+
]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("reads omo.json (fallback) when omo.jsonc does not exist", () => {
|
|
251
|
+
const omoDir = join(homeDir, ".omo");
|
|
252
|
+
mkdirSync(omoDir, { recursive: true });
|
|
253
|
+
const configPath = join(omoDir, "omo.json");
|
|
254
|
+
writeFileSync(
|
|
255
|
+
configPath,
|
|
256
|
+
JSON.stringify({
|
|
257
|
+
"[opencode]": {},
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
262
|
+
|
|
263
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
264
|
+
|
|
265
|
+
const updated = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
266
|
+
expect(updated["[opencode]"].disabled_hooks).toEqual([
|
|
267
|
+
"context-window-monitor",
|
|
268
|
+
"preemptive-compaction",
|
|
269
|
+
"anthropic-context-window-limit-recovery",
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("updates both legacy and unified config when both exist", () => {
|
|
274
|
+
// Legacy: project-level oh-my-opencode.json
|
|
275
|
+
const legacyPath = join(projectDir, "oh-my-opencode.json");
|
|
276
|
+
writeFileSync(legacyPath, JSON.stringify({ disabled_hooks: [] }));
|
|
277
|
+
|
|
278
|
+
// Unified: ~/.omo/omo.jsonc
|
|
279
|
+
const omoDir = join(homeDir, ".omo");
|
|
280
|
+
mkdirSync(omoDir, { recursive: true });
|
|
281
|
+
const unifiedPath = join(omoDir, "omo.jsonc");
|
|
282
|
+
writeFileSync(
|
|
283
|
+
unifiedPath,
|
|
284
|
+
JSON.stringify({
|
|
285
|
+
"[opencode]": {},
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
290
|
+
|
|
291
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
292
|
+
|
|
293
|
+
// Legacy: top-level disabled_hooks
|
|
294
|
+
const legacy = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
295
|
+
expect(legacy.disabled_hooks).toEqual([
|
|
296
|
+
"context-window-monitor",
|
|
297
|
+
"preemptive-compaction",
|
|
298
|
+
"anthropic-context-window-limit-recovery",
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
// Unified: inside [opencode] block
|
|
302
|
+
const unified = JSON.parse(readFileSync(unifiedPath, "utf-8"));
|
|
303
|
+
expect(unified["[opencode]"].disabled_hooks).toEqual([
|
|
304
|
+
"context-window-monitor",
|
|
305
|
+
"preemptive-compaction",
|
|
306
|
+
"anthropic-context-window-limit-recovery",
|
|
307
|
+
]);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("skips non-existent unified paths (no create)", () => {
|
|
311
|
+
// No .omo directory at all — fixer should find no targets
|
|
312
|
+
const actions = fixConflicts(projectDir, omoConflicts);
|
|
313
|
+
expect(actions).toEqual([]);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// --- Compaction-off mode parity (issue #266 S2) ---
|
|
318
|
+
// In compaction-off mode the fixer MUST NOT flip compaction.auto/prune to
|
|
319
|
+
// false — native compaction fields are left byte-for-byte as found.
|
|
320
|
+
// DCP and OMO hook fixes keep their existing policy in BOTH modes.
|
|
321
|
+
describe("compaction-off mode parity (issue #266)", () => {
|
|
322
|
+
it("does NOT flip compaction.auto to false when compaction-off", () => {
|
|
323
|
+
const configPath = join(projectDir, "opencode.jsonc");
|
|
324
|
+
const original = `{
|
|
325
|
+
// keep comment
|
|
326
|
+
"compaction": {
|
|
327
|
+
"auto": true,
|
|
328
|
+
"prune": true
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
`;
|
|
332
|
+
writeFileSync(configPath, original);
|
|
333
|
+
|
|
334
|
+
const actions = fixConflicts(
|
|
335
|
+
projectDir,
|
|
336
|
+
{
|
|
337
|
+
compactionAuto: true,
|
|
338
|
+
compactionPrune: true,
|
|
339
|
+
dcpPlugin: false,
|
|
340
|
+
...noOmoConflicts,
|
|
341
|
+
},
|
|
342
|
+
{ compactionEnabled: false },
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// No compaction repair action in compaction-off mode.
|
|
346
|
+
expect(actions).toEqual([]);
|
|
347
|
+
// File is byte-for-byte unchanged (native fields preserved).
|
|
348
|
+
expect(readFileSync(configPath, "utf-8")).toBe(original);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("still removes DCP plugin in compaction-off mode (DCP policy is mode-independent)", () => {
|
|
352
|
+
const configPath = join(projectDir, "opencode.jsonc");
|
|
353
|
+
writeFileSync(
|
|
354
|
+
configPath,
|
|
355
|
+
JSON.stringify({
|
|
356
|
+
plugin: ["@tarquinen/opencode-dcp@latest"],
|
|
357
|
+
compaction: { auto: true, prune: true },
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const actions = fixConflicts(
|
|
362
|
+
projectDir,
|
|
363
|
+
{ compactionAuto: true, compactionPrune: true, dcpPlugin: true, ...noOmoConflicts },
|
|
364
|
+
{ compactionEnabled: false },
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// DCP removed, compaction NOT flipped.
|
|
368
|
+
expect(actions).toEqual(["Removed opencode-dcp plugin"]);
|
|
369
|
+
const updated = parseJsonc(readFileSync(configPath, "utf-8")) as Record<
|
|
370
|
+
string,
|
|
371
|
+
unknown
|
|
372
|
+
>;
|
|
373
|
+
expect(updated.compaction).toEqual({ auto: true, prune: true });
|
|
374
|
+
expect(updated.plugin).toEqual([]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("still disables OMO hooks in compaction-off mode (OMO policy is mode-independent)", () => {
|
|
378
|
+
const configPath = join(projectDir, "opencode.json");
|
|
379
|
+
writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode"] }));
|
|
380
|
+
const omoDir = join(homeDir, ".omo");
|
|
381
|
+
mkdirSync(omoDir, { recursive: true });
|
|
382
|
+
const omoPath = join(omoDir, "omo.jsonc");
|
|
383
|
+
writeFileSync(omoPath, JSON.stringify({ "[opencode]": {} }));
|
|
384
|
+
|
|
385
|
+
const actions = fixConflicts(
|
|
386
|
+
projectDir,
|
|
387
|
+
{
|
|
388
|
+
compactionAuto: false,
|
|
389
|
+
compactionPrune: false,
|
|
390
|
+
dcpPlugin: false,
|
|
391
|
+
omoPreemptiveCompaction: true,
|
|
392
|
+
omoContextWindowMonitor: true,
|
|
393
|
+
omoAnthropicRecovery: true,
|
|
394
|
+
},
|
|
395
|
+
{ compactionEnabled: false },
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
expect(actions).toEqual(["Disabled conflicting oh-my-opencode hooks"]);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Mutation direction: with mode ON, the same compaction conflict IS
|
|
402
|
+
// repaired. Proves the off-gate isn't just always-skip.
|
|
403
|
+
it("mutation direction: same conflict IS repaired when mode forced on", () => {
|
|
404
|
+
const configPath = join(projectDir, "opencode.jsonc");
|
|
405
|
+
writeFileSync(configPath, JSON.stringify({ compaction: { auto: true, prune: true } }));
|
|
406
|
+
|
|
407
|
+
const offActions = fixConflicts(
|
|
408
|
+
projectDir,
|
|
409
|
+
{
|
|
410
|
+
compactionAuto: true,
|
|
411
|
+
compactionPrune: true,
|
|
412
|
+
dcpPlugin: false,
|
|
413
|
+
...noOmoConflicts,
|
|
414
|
+
},
|
|
415
|
+
{ compactionEnabled: false },
|
|
416
|
+
);
|
|
417
|
+
const onActions = fixConflicts(
|
|
418
|
+
projectDir,
|
|
419
|
+
{
|
|
420
|
+
compactionAuto: true,
|
|
421
|
+
compactionPrune: true,
|
|
422
|
+
dcpPlugin: false,
|
|
423
|
+
...noOmoConflicts,
|
|
424
|
+
},
|
|
425
|
+
{ compactionEnabled: true },
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
expect(offActions).toEqual([]);
|
|
429
|
+
expect(onActions).toEqual(["Disabled auto-compaction"]);
|
|
430
|
+
const updated = parseJsonc(readFileSync(configPath, "utf-8")) as Record<
|
|
431
|
+
string,
|
|
432
|
+
unknown
|
|
433
|
+
>;
|
|
434
|
+
expect(updated.compaction).toEqual({ auto: false, prune: false });
|
|
435
|
+
});
|
|
436
|
+
});
|
|
128
437
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { parse, stringify } from "comment-json";
|
|
4
5
|
|
|
@@ -25,6 +26,9 @@ const OMO_CONFIG_NAMES = [
|
|
|
25
26
|
"oh-my-opencode.json",
|
|
26
27
|
] as const;
|
|
27
28
|
|
|
29
|
+
/** Unified OMO config names (oh-my-openagent >= 4.19.0) */
|
|
30
|
+
const OMO_UNIFIED_NAMES = ["omo.jsonc", "omo.json"] as const;
|
|
31
|
+
|
|
28
32
|
function isRecord(value: unknown): value is JsonObject {
|
|
29
33
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
34
|
}
|
|
@@ -84,6 +88,7 @@ function collectOmoConfigPaths(directory: string): string[] {
|
|
|
84
88
|
const paths = new Set<string>();
|
|
85
89
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
86
90
|
|
|
91
|
+
// Legacy OMO config names in the OpenCode config dir and project dir
|
|
87
92
|
for (const fileName of OMO_CONFIG_NAMES) {
|
|
88
93
|
const userPath = join(configDir, fileName);
|
|
89
94
|
const projectPath = join(directory, fileName);
|
|
@@ -97,9 +102,48 @@ function collectOmoConfigPaths(directory: string): string[] {
|
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
// New unified omo.jsonc (oh-my-openagent >= 4.19.0)
|
|
106
|
+
// User-level: ~/.omo/omo.jsonc (fallback ~/.omo/omo.json)
|
|
107
|
+
const homeDir = process.env.HOME || homedir();
|
|
108
|
+
const omoHomeDir = join(homeDir, ".omo");
|
|
109
|
+
for (const name of OMO_UNIFIED_NAMES) {
|
|
110
|
+
const userPath = join(omoHomeDir, name);
|
|
111
|
+
if (existsSync(userPath)) {
|
|
112
|
+
paths.add(userPath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Project-level: .omo/omo.jsonc (fallback .omo/omo.json)
|
|
117
|
+
for (const name of OMO_UNIFIED_NAMES) {
|
|
118
|
+
const projectPath = join(directory, ".omo", name);
|
|
119
|
+
if (existsSync(projectPath)) {
|
|
120
|
+
paths.add(projectPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
return [...paths];
|
|
101
125
|
}
|
|
102
126
|
|
|
127
|
+
/** Check if a config path is a unified omo.json(c) path (not legacy). */
|
|
128
|
+
function isUnifiedOmoPath(configPath: string): boolean {
|
|
129
|
+
const basename = configPath.split("/").pop() ?? "";
|
|
130
|
+
return basename === "omo.jsonc" || basename === "omo.json";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get or create the `[opencode]` block inside a unified omo.json(c) config.
|
|
135
|
+
* Returns the block itself so callers can read/write `disabled_hooks` on it.
|
|
136
|
+
*/
|
|
137
|
+
function getOrCreateOmoV2Block(config: JsonObject): JsonObject {
|
|
138
|
+
const block = config["[opencode]"];
|
|
139
|
+
if (isRecord(block)) {
|
|
140
|
+
return block;
|
|
141
|
+
}
|
|
142
|
+
const newBlock: JsonObject = {};
|
|
143
|
+
config["[opencode]"] = newBlock;
|
|
144
|
+
return newBlock;
|
|
145
|
+
}
|
|
146
|
+
|
|
103
147
|
function filterDcpPluginEntries(entries: unknown[]): { plugins: unknown[]; removed: boolean } {
|
|
104
148
|
const plugins = entries.filter((entry) => {
|
|
105
149
|
const name = extractPluginName(entry);
|
|
@@ -108,13 +152,43 @@ function filterDcpPluginEntries(entries: unknown[]): { plugins: unknown[]; remov
|
|
|
108
152
|
return { plugins, removed: plugins.length !== entries.length };
|
|
109
153
|
}
|
|
110
154
|
|
|
111
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Options for {@link fixConflicts}.
|
|
157
|
+
*
|
|
158
|
+
* `compactionEnabled` is the boot-resolved MC compaction mode (the result of
|
|
159
|
+
* {@link isCompactionEnabled} on the resolved user-tier config). When `false`
|
|
160
|
+
* (compaction-off mode), the fixer MUST NOT flip `compaction.auto`/`prune` to
|
|
161
|
+
* `false` — native compaction fields are left byte-for-byte as found, because
|
|
162
|
+
* native compaction (or nothing) is the user's chosen window manager. DCP and
|
|
163
|
+
* OMO hook fixes keep their existing policy in BOTH modes.
|
|
164
|
+
*
|
|
165
|
+
* Default `true` (mode-on) preserves today's fix behavior for call sites that
|
|
166
|
+
* cannot supply the resolved mode; they fail toward mode-on, never silently
|
|
167
|
+
* skipping the fix.
|
|
168
|
+
*/
|
|
169
|
+
export interface FixConflictsOptions {
|
|
170
|
+
compactionEnabled?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function fixConflicts(
|
|
174
|
+
directory: string,
|
|
175
|
+
conflicts: ConflictResult["conflicts"],
|
|
176
|
+
options?: FixConflictsOptions,
|
|
177
|
+
): string[] {
|
|
178
|
+
const compactionEnabled = options?.compactionEnabled ?? true;
|
|
112
179
|
const actions: string[] = [];
|
|
113
180
|
let updatedCompaction = false;
|
|
114
181
|
let removedDcpPlugin = false;
|
|
115
182
|
let disabledOmoHooks = false;
|
|
116
183
|
|
|
117
|
-
|
|
184
|
+
// Native compaction fields are repaired ONLY when MC compaction is ON. In
|
|
185
|
+
// compaction-off mode the fixer must not rewrite compaction.auto/prune —
|
|
186
|
+
// native compaction is the intended manager and pre-existing values are
|
|
187
|
+
// left byte-for-byte as found.
|
|
188
|
+
const repairCompaction =
|
|
189
|
+
compactionEnabled && (conflicts.compactionAuto || conflicts.compactionPrune);
|
|
190
|
+
|
|
191
|
+
if (repairCompaction || conflicts.dcpPlugin) {
|
|
118
192
|
for (const configPath of collectOpenCodeConfigPaths(directory)) {
|
|
119
193
|
const config = readConfig(configPath);
|
|
120
194
|
if (!config) {
|
|
@@ -123,7 +197,7 @@ export function fixConflicts(directory: string, conflicts: ConflictResult["confl
|
|
|
123
197
|
|
|
124
198
|
let changed = false;
|
|
125
199
|
|
|
126
|
-
if (
|
|
200
|
+
if (repairCompaction) {
|
|
127
201
|
const compaction = isRecord(config.compaction) ? config.compaction : {};
|
|
128
202
|
|
|
129
203
|
if (compaction.auto !== false) {
|
|
@@ -180,7 +254,10 @@ export function fixConflicts(directory: string, conflicts: ConflictResult["confl
|
|
|
180
254
|
continue;
|
|
181
255
|
}
|
|
182
256
|
|
|
183
|
-
|
|
257
|
+
// Unified paths nest hooks under the [opencode] block; legacy paths use top-level
|
|
258
|
+
const target = isUnifiedOmoPath(configPath) ? getOrCreateOmoV2Block(config) : config;
|
|
259
|
+
|
|
260
|
+
const disabledHooks = new Set(asStringArray(target.disabled_hooks));
|
|
184
261
|
let changed = false;
|
|
185
262
|
|
|
186
263
|
for (const hook of hooksToDisable) {
|
|
@@ -192,7 +269,7 @@ export function fixConflicts(directory: string, conflicts: ConflictResult["confl
|
|
|
192
269
|
}
|
|
193
270
|
|
|
194
271
|
if (changed) {
|
|
195
|
-
|
|
272
|
+
target.disabled_hooks = [
|
|
196
273
|
...CONFLICTING_OMO_HOOKS.filter((hook) => disabledHooks.has(hook)),
|
|
197
274
|
...[...disabledHooks].filter(
|
|
198
275
|
(hook) =>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { escalationBands } from "./escalation-bands";
|
|
3
|
+
|
|
4
|
+
describe("escalationBands", () => {
|
|
5
|
+
test.each([
|
|
6
|
+
[65, 85],
|
|
7
|
+
[80, 85],
|
|
8
|
+
[88, 90],
|
|
9
|
+
[90, 92],
|
|
10
|
+
])("keeps the force band ordered for threshold %i", (threshold, expectedForce) => {
|
|
11
|
+
const bands = escalationBands(threshold);
|
|
12
|
+
expect(bands.forceMaterializationPercentage).toBe(expectedForce);
|
|
13
|
+
expect(threshold).toBeLessThan(bands.forceMaterializationPercentage);
|
|
14
|
+
expect(bands.forceMaterializationPercentage).toBeGreaterThanOrEqual(85);
|
|
15
|
+
expect(bands.forceMaterializationPercentage).toBeLessThan(95);
|
|
16
|
+
expect(bands.emergencyPercentage).toBe(95);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("preserves the exact pre-raise behavior through threshold 80", () => {
|
|
20
|
+
expect(escalationBands(65).forceMaterializationPercentage).toBe(85);
|
|
21
|
+
expect(escalationBands(80).forceMaterializationPercentage).toBe(85);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const MAX_EXECUTE_THRESHOLD = 90;
|
|
2
|
+
export const ABSOLUTE_EMERGENCY_PERCENTAGE = 95;
|
|
3
|
+
|
|
4
|
+
export interface EscalationBands {
|
|
5
|
+
forceMaterializationPercentage: number;
|
|
6
|
+
emergencyPercentage: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Keep force cleanup above normal execution while preserving the absolute 95% wall. */
|
|
10
|
+
export function escalationBands(effectiveThresholdPercentage: number): EscalationBands {
|
|
11
|
+
const threshold = Number.isFinite(effectiveThresholdPercentage)
|
|
12
|
+
? Math.min(effectiveThresholdPercentage, MAX_EXECUTE_THRESHOLD)
|
|
13
|
+
: 65;
|
|
14
|
+
return {
|
|
15
|
+
forceMaterializationPercentage: Math.max(85, threshold + 2),
|
|
16
|
+
emergencyPercentage: ABSOLUTE_EMERGENCY_PERCENTAGE,
|
|
17
|
+
};
|
|
18
|
+
}
|