@cortexkit/opencode-magic-context 0.29.0 → 0.30.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/features/magic-context/dreamer/retrospective-raw-provider.d.ts +0 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts +10 -0
- package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +0 -3
- 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/memory/embedding-local.d.ts +0 -3
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +2 -21
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts +30 -0
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -0
- package/dist/hooks/magic-context/channel2-delivery.d.ts +9 -5
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +1 -4
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +0 -2
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3118 -5026
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +1 -1
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/exit-abort-registry.d.ts +25 -0
- package/dist/shared/exit-abort-registry.d.ts.map +1 -0
- package/dist/shared/harness-provider-map.d.ts +30 -0
- package/dist/shared/harness-provider-map.d.ts.map +1 -0
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tui/badge-contrast.d.ts +37 -22
- package/dist/tui/badge-contrast.d.ts.map +1 -1
- package/package.json +78 -78
- package/src/shared/announcement.ts +4 -3
- package/src/shared/exit-abort-registry.test.ts +50 -0
- package/src/shared/exit-abort-registry.ts +46 -0
- package/src/shared/harness-provider-map.test.ts +63 -0
- package/src/shared/harness-provider-map.ts +56 -0
- package/src/tui/badge-contrast.test.ts +39 -1
- package/src/tui/badge-contrast.ts +63 -25
- package/src/tui/slots/sidebar-content.tsx +2 -2
- package/dist/hooks/is-anthropic-provider.d.ts +0 -2
- package/dist/hooks/is-anthropic-provider.d.ts.map +0 -1
- package/dist/shared/live-server-client.d.ts +0 -50
- package/dist/shared/live-server-client.d.ts.map +0 -1
- package/src/shared/live-server-client.ts +0 -152
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAO7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;
|
|
1
|
+
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAO7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;qBA2B28K,CAAC;;;;;;;;;;;;qBAAjhE,CAAC;mBAAyB,CAAC;iBAAuB,CAAC;iBAAuB,CAAC;0BAAc,CAAC;uBAAiB,CAAC;;;;;;0BAA20mC,CAAC;;;;;;EADn3tC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAc3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAqBlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAe,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0FtF,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,EAK9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,eAAe,CAuVjB;AAED,wBAAgB,iBAAiB,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,GAC/B,YAAY,CAyKd;AA4BD;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GACF,IAAI,CAyPN"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Bump only when there are user-visible changes worth a startup dialog.
|
|
19
19
|
* Does NOT need to match the published package version.
|
|
20
20
|
*/
|
|
21
|
-
export declare const ANNOUNCEMENT_VERSION = "0.
|
|
21
|
+
export declare const ANNOUNCEMENT_VERSION = "0.30.0";
|
|
22
22
|
/**
|
|
23
23
|
* Short, user-facing bullet strings. Keep each line ~80 chars or shorter so the
|
|
24
24
|
* TUI dialog renders cleanly without horizontal scroll on a typical terminal.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAIvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAyBtF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CA0BhD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-global registry of AbortControllers to abort on process exit, backed
|
|
3
|
+
* by a SINGLE `process.once("exit")` listener no matter how many controllers
|
|
4
|
+
* register.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the plugin factory runs once per plugin instance, and
|
|
7
|
+
* OpenCode Desktop loads many instances in one Node process (one per open
|
|
8
|
+
* project). Registering a `process.once("exit")` per instance added one listener
|
|
9
|
+
* each, so past Node's default 10-listener cap it logged a
|
|
10
|
+
* `MaxListenersExceededWarning` ("11 exit listeners added to [process]"). One
|
|
11
|
+
* module-global listener that fans out to every registered controller keeps the
|
|
12
|
+
* count at one.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Abort `controller` when the process exits. The underlying `process.once("exit")`
|
|
16
|
+
* listener is installed on the first call only; subsequent calls just add to the
|
|
17
|
+
* fan-out set.
|
|
18
|
+
*/
|
|
19
|
+
export declare function registerExitAbort(controller: AbortController): void;
|
|
20
|
+
/**
|
|
21
|
+
* Stop tracking `controller` (e.g. when its plugin instance is disposed) so the
|
|
22
|
+
* set doesn't grow without bound as Desktop opens and closes projects.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unregisterExitAbort(controller: AbortController): void;
|
|
25
|
+
//# sourceMappingURL=exit-abort-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-abort-registry.d.ts","sourceRoot":"","sources":["../../src/shared/exit-abort-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAKnE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAErE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-id translation between the canonical (OpenCode) form stored in the
|
|
3
|
+
* shared magic-context config and Pi's harness-native provider ids.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode and Pi now share ONE config, but a few auth-plugin providers were
|
|
6
|
+
* named differently on each side. The model id AFTER the slash is identical;
|
|
7
|
+
* only the provider prefix differs:
|
|
8
|
+
*
|
|
9
|
+
* canonical (OpenCode) Pi
|
|
10
|
+
* -------------------- -------------------
|
|
11
|
+
* openai/<model> openai-codex/<model>
|
|
12
|
+
* google/<model> google-antigravity/<model>
|
|
13
|
+
* anthropic/<model> anthropic/<model> (same; every other provider too)
|
|
14
|
+
*
|
|
15
|
+
* Canonical = OpenCode: the config always stores the OpenCode form. Pi
|
|
16
|
+
* translates at its edges:
|
|
17
|
+
* - read: canonical -> Pi when spawning a configured model (subagent-runner).
|
|
18
|
+
* - write: Pi -> canonical in the Pi setup wizard, so a config written from
|
|
19
|
+
* the Pi side stays readable by OpenCode.
|
|
20
|
+
*
|
|
21
|
+
* OpenCode needs no translation (canonical IS the OpenCode form).
|
|
22
|
+
*/
|
|
23
|
+
/** Pi-native `provider/model` -> canonical (OpenCode). Identity when unmapped.
|
|
24
|
+
* Used by the Pi setup wizard so configs it writes stay OpenCode-readable. */
|
|
25
|
+
export declare function piModelRefToCanonical(ref: string): string;
|
|
26
|
+
/** Canonical (OpenCode) `provider/model` -> Pi-native, for spawning a model on
|
|
27
|
+
* Pi. Idempotent: normalizes any Pi-form prefix back to canonical first, so it
|
|
28
|
+
* is safe on a config that already holds Pi-form ids (hand-edited or pre-fix). */
|
|
29
|
+
export declare function resolveModelRefForPi(ref: string): string;
|
|
30
|
+
//# sourceMappingURL=harness-provider-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-provider-map.d.ts","sourceRoot":"","sources":["../../src/shared/harness-provider-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuBH;+EAC+E;AAC/E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;mFAEmF;AACnF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAYhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAYhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;AAuTD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
|
|
@@ -1,31 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pick
|
|
3
|
-
* background
|
|
2
|
+
* Pick the text color for the sidebar header badge (a bold label drawn on a
|
|
3
|
+
* `theme.accent` background).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* always opaque, so deriving the text color from it is transparency-proof.
|
|
5
|
+
* Primary rule (matches AFT's sidebar by construction): paint the theme's own
|
|
6
|
+
* `background` color as the label, the inverse-of-panel look. Because it is a
|
|
7
|
+
* fixed theme token rather than an accent-derived computation, MC's badge and
|
|
8
|
+
* AFT's badge agree on EVERY accent automatically, so the same theme can never
|
|
9
|
+
* make one badge black and the other white (issue #198).
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
11
|
+
* Fallback rule (the reason a luminance pick exists at all): `theme.background`
|
|
12
|
+
* can be unusable as a label color in two degenerate cases, where it would
|
|
13
|
+
* render the label invisible on the accent:
|
|
14
|
+
* 1. Transparent background. Themes that respect terminal transparency set
|
|
15
|
+
* `background: "none"`, which resolves to `RGBA(0,0,0,0)`; drawing it as
|
|
16
|
+
* text renders fully transparent and the label disappears (issue #186).
|
|
17
|
+
* 2. Background ~= accent. If the theme's background and accent are nearly the
|
|
18
|
+
* same color, background-on-accent text has no contrast.
|
|
19
|
+
* In either case we fall back to a black/white pick that is guaranteed visible
|
|
20
|
+
* on the always-opaque accent.
|
|
21
21
|
*
|
|
22
|
-
* `RGBA` channels from @opentui/core are normalized 0..1 floats
|
|
23
|
-
* minimal `{ r, g, b }` shape so this stays a pure, trivially
|
|
24
|
-
* independent of the native color class
|
|
22
|
+
* `RGBA` channels from @opentui/core are normalized 0..1 floats (alpha included).
|
|
23
|
+
* We accept the minimal `{ r, g, b, a? }` shape so this stays a pure, trivially
|
|
24
|
+
* testable function independent of the native color class, and we return the
|
|
25
|
+
* passed-in `background` object unchanged on the primary path so it stays the
|
|
26
|
+
* exact same theme token AFT uses.
|
|
25
27
|
*/
|
|
26
|
-
|
|
28
|
+
type Color = {
|
|
27
29
|
r: number;
|
|
28
30
|
g: number;
|
|
29
31
|
b: number;
|
|
30
|
-
|
|
32
|
+
a?: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Pure black/white pick by accent luminance. Used as the badge fallback and kept
|
|
36
|
+
* exported for callers that only have the accent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readableTextColorOn(bg: Color): string;
|
|
39
|
+
/**
|
|
40
|
+
* Badge label color on the accent: the theme background (AFT parity) when it is
|
|
41
|
+
* usable, else a guaranteed-visible black/white fallback. Returns the passed-in
|
|
42
|
+
* `background` reference unchanged on the primary path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function badgeTextColor<T extends Color>(accent: T, background: T): T | string;
|
|
45
|
+
export {};
|
|
31
46
|
//# sourceMappingURL=badge-contrast.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,KAAK,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAoC7D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,KAAK,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAMpF"}
|
package/package.json
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
2
|
+
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
+
"version": "0.30.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"author": "ualtinok",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"opencode",
|
|
12
|
+
"plugin",
|
|
13
|
+
"context",
|
|
14
|
+
"memory",
|
|
15
|
+
"ai",
|
|
16
|
+
"llm",
|
|
17
|
+
"prompt-caching",
|
|
18
|
+
"session-history"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/cortexkit/magic-context",
|
|
23
|
+
"directory": "packages/plugin"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src/tui",
|
|
28
|
+
"src/shared",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @huggingface/transformers --external onnxruntime-web --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
|
|
33
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
|
|
34
|
+
"test": "bun test",
|
|
35
|
+
"lint": "biome check .",
|
|
36
|
+
"lint:fix": "biome check --write .",
|
|
37
|
+
"format": "biome format --write .",
|
|
38
|
+
"format:check": "biome format .",
|
|
39
|
+
"clean": "rm -rf dist",
|
|
40
|
+
"prepublishOnly": "bun run build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@huggingface/transformers": "^4.1.0",
|
|
44
|
+
"@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
|
|
45
|
+
"@opencode-ai/plugin": "^1.17.11",
|
|
46
|
+
"@opencode-ai/sdk": "^1.17.11",
|
|
47
|
+
"@opentui/core": "^0.4.2",
|
|
48
|
+
"@opentui/solid": "^0.4.2",
|
|
49
|
+
"ai-tokenizer": "^1.0.6",
|
|
50
|
+
"comment-json": "^5.0.0",
|
|
51
|
+
"quickjs-emscripten": "^0.32.0",
|
|
52
|
+
"solid-js": "1.9.12",
|
|
53
|
+
"zod": "^4.1.8"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@biomejs/biome": "^2.5.1",
|
|
57
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
58
|
+
"@types/bun": "^1.3.10",
|
|
59
|
+
"@types/node": "^22.20.0",
|
|
60
|
+
"bun-types": "^1.3.10",
|
|
61
|
+
"typescript": "^5.8.0"
|
|
62
|
+
},
|
|
63
|
+
"exports": {
|
|
64
|
+
".": {
|
|
65
|
+
"types": "./dist/index.d.ts",
|
|
66
|
+
"import": "./dist/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./tui": {
|
|
69
|
+
"types": "./src/tui/index.tsx",
|
|
70
|
+
"import": "./src/tui/index.tsx"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"oc-plugin": [
|
|
74
|
+
"server",
|
|
75
|
+
"tui"
|
|
76
|
+
],
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"@opencode-ai/plugin": ">=1.15.0"
|
|
79
|
+
}
|
|
80
80
|
}
|
|
@@ -24,15 +24,16 @@ import { getMagicContextStorageDir } from "./data-path";
|
|
|
24
24
|
* Bump only when there are user-visible changes worth a startup dialog.
|
|
25
25
|
* Does NOT need to match the published package version.
|
|
26
26
|
*/
|
|
27
|
-
export const ANNOUNCEMENT_VERSION = "0.
|
|
27
|
+
export const ANNOUNCEMENT_VERSION = "0.30.0";
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Short, user-facing bullet strings. Keep each line ~80 chars or shorter so the
|
|
31
31
|
* TUI dialog renders cleanly without horizontal scroll on a typical terminal.
|
|
32
32
|
*/
|
|
33
33
|
export const ANNOUNCEMENT_FEATURES: ReadonlyArray<string> = [
|
|
34
|
-
"
|
|
35
|
-
"
|
|
34
|
+
"Setup and doctor now recognize an OpenCode Desktop install that has no CLI on PATH, instead of reporting OpenCode as not installed (#196). Desktop-only users continue setup with manual model entry.",
|
|
35
|
+
"TUI sidebar badge color follows your theme again (#198): it uses the theme background like sibling badges, with a fallback so the label can't disappear on transparent themes.",
|
|
36
|
+
"doctor clears a stale plugin cache more reliably (#199): it compares against the plugin's latest published version and preserves the cache when offline.",
|
|
36
37
|
];
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { registerExitAbort, unregisterExitAbort } from "./exit-abort-registry";
|
|
3
|
+
|
|
4
|
+
// Captured before any registration so we can isolate the ONE listener the
|
|
5
|
+
// registry installs process-wide (it intentionally never removes it, mirroring
|
|
6
|
+
// production, so the suite must not strip it either).
|
|
7
|
+
const baseline = process.listenerCount("exit");
|
|
8
|
+
|
|
9
|
+
/** The registry's single 'exit' listener (the first one added past baseline). */
|
|
10
|
+
function registryListener(): () => void {
|
|
11
|
+
return process.listeners("exit").slice(baseline)[0] as () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("exit-abort-registry", () => {
|
|
15
|
+
it("adds exactly ONE process exit listener no matter how many controllers register", () => {
|
|
16
|
+
registerExitAbort(new AbortController());
|
|
17
|
+
registerExitAbort(new AbortController());
|
|
18
|
+
registerExitAbort(new AbortController());
|
|
19
|
+
expect(process.listenerCount("exit") - baseline).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("aborts every registered controller when the exit listener fires", () => {
|
|
23
|
+
const a = new AbortController();
|
|
24
|
+
const b = new AbortController();
|
|
25
|
+
registerExitAbort(a);
|
|
26
|
+
registerExitAbort(b);
|
|
27
|
+
|
|
28
|
+
// Invoke the registry's listener directly (emitting 'exit' would end the
|
|
29
|
+
// test process).
|
|
30
|
+
registryListener()();
|
|
31
|
+
|
|
32
|
+
expect(a.signal.aborted).toBe(true);
|
|
33
|
+
expect(b.signal.aborted).toBe(true);
|
|
34
|
+
// Still exactly one listener after firing.
|
|
35
|
+
expect(process.listenerCount("exit") - baseline).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not abort a controller that was unregistered before exit", () => {
|
|
39
|
+
const keep = new AbortController();
|
|
40
|
+
const drop = new AbortController();
|
|
41
|
+
registerExitAbort(keep);
|
|
42
|
+
registerExitAbort(drop);
|
|
43
|
+
unregisterExitAbort(drop);
|
|
44
|
+
|
|
45
|
+
registryListener()();
|
|
46
|
+
|
|
47
|
+
expect(keep.signal.aborted).toBe(true);
|
|
48
|
+
expect(drop.signal.aborted).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-global registry of AbortControllers to abort on process exit, backed
|
|
3
|
+
* by a SINGLE `process.once("exit")` listener no matter how many controllers
|
|
4
|
+
* register.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the plugin factory runs once per plugin instance, and
|
|
7
|
+
* OpenCode Desktop loads many instances in one Node process (one per open
|
|
8
|
+
* project). Registering a `process.once("exit")` per instance added one listener
|
|
9
|
+
* each, so past Node's default 10-listener cap it logged a
|
|
10
|
+
* `MaxListenersExceededWarning` ("11 exit listeners added to [process]"). One
|
|
11
|
+
* module-global listener that fans out to every registered controller keeps the
|
|
12
|
+
* count at one.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const controllers = new Set<AbortController>();
|
|
16
|
+
let listenerRegistered = false;
|
|
17
|
+
|
|
18
|
+
function abortAll(): void {
|
|
19
|
+
for (const controller of controllers) {
|
|
20
|
+
try {
|
|
21
|
+
controller.abort();
|
|
22
|
+
} catch {
|
|
23
|
+
// best-effort: the process is exiting anyway
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Abort `controller` when the process exits. The underlying `process.once("exit")`
|
|
30
|
+
* listener is installed on the first call only; subsequent calls just add to the
|
|
31
|
+
* fan-out set.
|
|
32
|
+
*/
|
|
33
|
+
export function registerExitAbort(controller: AbortController): void {
|
|
34
|
+
controllers.add(controller);
|
|
35
|
+
if (listenerRegistered) return;
|
|
36
|
+
listenerRegistered = true;
|
|
37
|
+
process.once("exit", abortAll);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Stop tracking `controller` (e.g. when its plugin instance is disposed) so the
|
|
42
|
+
* set doesn't grow without bound as Desktop opens and closes projects.
|
|
43
|
+
*/
|
|
44
|
+
export function unregisterExitAbort(controller: AbortController): void {
|
|
45
|
+
controllers.delete(controller);
|
|
46
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { piModelRefToCanonical, resolveModelRefForPi } from "./harness-provider-map";
|
|
3
|
+
|
|
4
|
+
describe("harness-provider-map", () => {
|
|
5
|
+
describe("resolveModelRefForPi (canonical -> Pi, used when spawning)", () => {
|
|
6
|
+
it("maps the diverging auth-plugin providers, preserving the model id", () => {
|
|
7
|
+
expect(resolveModelRefForPi("openai/gpt-5.5")).toBe("openai-codex/gpt-5.5");
|
|
8
|
+
expect(resolveModelRefForPi("google/antigravity-gemini-3.5-flash")).toBe(
|
|
9
|
+
"google-antigravity/antigravity-gemini-3.5-flash",
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("leaves anthropic and every other provider unchanged", () => {
|
|
14
|
+
expect(resolveModelRefForPi("anthropic/claude-opus-4-8")).toBe(
|
|
15
|
+
"anthropic/claude-opus-4-8",
|
|
16
|
+
);
|
|
17
|
+
expect(resolveModelRefForPi("cerebras/gpt-oss-120b")).toBe("cerebras/gpt-oss-120b");
|
|
18
|
+
expect(resolveModelRefForPi("openrouter/openai/gpt-5.5")).toBe(
|
|
19
|
+
"openrouter/openai/gpt-5.5",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("is idempotent: a config already in Pi form still resolves to Pi form", () => {
|
|
24
|
+
expect(resolveModelRefForPi("openai-codex/gpt-5.5")).toBe("openai-codex/gpt-5.5");
|
|
25
|
+
expect(resolveModelRefForPi("google-antigravity/antigravity-gemini-3.1-pro")).toBe(
|
|
26
|
+
"google-antigravity/antigravity-gemini-3.1-pro",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves model ids that themselves contain slashes", () => {
|
|
31
|
+
expect(resolveModelRefForPi("openai/some/nested/id")).toBe(
|
|
32
|
+
"openai-codex/some/nested/id",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("passes through malformed refs (no slash, empty provider) unchanged", () => {
|
|
37
|
+
expect(resolveModelRefForPi("gpt-5.5")).toBe("gpt-5.5");
|
|
38
|
+
expect(resolveModelRefForPi("/gpt-5.5")).toBe("/gpt-5.5");
|
|
39
|
+
expect(resolveModelRefForPi("")).toBe("");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("piModelRefToCanonical (Pi -> canonical, used by Pi setup write)", () => {
|
|
44
|
+
it("normalizes Pi-native provider ids to the OpenCode form", () => {
|
|
45
|
+
expect(piModelRefToCanonical("openai-codex/gpt-5.5")).toBe("openai/gpt-5.5");
|
|
46
|
+
expect(piModelRefToCanonical("google-antigravity/antigravity-gemini-3.5-flash")).toBe(
|
|
47
|
+
"google/antigravity-gemini-3.5-flash",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("leaves already-canonical and unmapped providers unchanged", () => {
|
|
52
|
+
expect(piModelRefToCanonical("anthropic/claude-opus-4-8")).toBe(
|
|
53
|
+
"anthropic/claude-opus-4-8",
|
|
54
|
+
);
|
|
55
|
+
expect(piModelRefToCanonical("openai/gpt-5.5")).toBe("openai/gpt-5.5");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("round-trips with resolveModelRefForPi", () => {
|
|
59
|
+
const piForm = "openai-codex/gpt-5.5";
|
|
60
|
+
expect(resolveModelRefForPi(piModelRefToCanonical(piForm))).toBe(piForm);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-id translation between the canonical (OpenCode) form stored in the
|
|
3
|
+
* shared magic-context config and Pi's harness-native provider ids.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode and Pi now share ONE config, but a few auth-plugin providers were
|
|
6
|
+
* named differently on each side. The model id AFTER the slash is identical;
|
|
7
|
+
* only the provider prefix differs:
|
|
8
|
+
*
|
|
9
|
+
* canonical (OpenCode) Pi
|
|
10
|
+
* -------------------- -------------------
|
|
11
|
+
* openai/<model> openai-codex/<model>
|
|
12
|
+
* google/<model> google-antigravity/<model>
|
|
13
|
+
* anthropic/<model> anthropic/<model> (same; every other provider too)
|
|
14
|
+
*
|
|
15
|
+
* Canonical = OpenCode: the config always stores the OpenCode form. Pi
|
|
16
|
+
* translates at its edges:
|
|
17
|
+
* - read: canonical -> Pi when spawning a configured model (subagent-runner).
|
|
18
|
+
* - write: Pi -> canonical in the Pi setup wizard, so a config written from
|
|
19
|
+
* the Pi side stays readable by OpenCode.
|
|
20
|
+
*
|
|
21
|
+
* OpenCode needs no translation (canonical IS the OpenCode form).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const CANONICAL_TO_PI_PROVIDER: Record<string, string> = {
|
|
25
|
+
openai: "openai-codex",
|
|
26
|
+
google: "google-antigravity",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PI_TO_CANONICAL_PROVIDER: Record<string, string> = {
|
|
30
|
+
"openai-codex": "openai",
|
|
31
|
+
"google-antigravity": "google",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Remap only the provider prefix (text before the first "/"), preserving the
|
|
35
|
+
* model id verbatim. No "/", empty provider, or unmapped provider -> unchanged. */
|
|
36
|
+
function remapProviderPrefix(ref: string, map: Record<string, string>): string {
|
|
37
|
+
if (typeof ref !== "string") return ref;
|
|
38
|
+
const slash = ref.indexOf("/");
|
|
39
|
+
if (slash <= 0) return ref;
|
|
40
|
+
const provider = ref.slice(0, slash);
|
|
41
|
+
const mapped = map[provider];
|
|
42
|
+
return mapped ? `${mapped}${ref.slice(slash)}` : ref;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Pi-native `provider/model` -> canonical (OpenCode). Identity when unmapped.
|
|
46
|
+
* Used by the Pi setup wizard so configs it writes stay OpenCode-readable. */
|
|
47
|
+
export function piModelRefToCanonical(ref: string): string {
|
|
48
|
+
return remapProviderPrefix(ref, PI_TO_CANONICAL_PROVIDER);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Canonical (OpenCode) `provider/model` -> Pi-native, for spawning a model on
|
|
52
|
+
* Pi. Idempotent: normalizes any Pi-form prefix back to canonical first, so it
|
|
53
|
+
* is safe on a config that already holds Pi-form ids (hand-edited or pre-fix). */
|
|
54
|
+
export function resolveModelRefForPi(ref: string): string {
|
|
55
|
+
return remapProviderPrefix(piModelRefToCanonical(ref), CANONICAL_TO_PI_PROVIDER);
|
|
56
|
+
}
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { readableTextColorOn } from "./badge-contrast";
|
|
2
|
+
import { badgeTextColor, readableTextColorOn } from "./badge-contrast";
|
|
3
|
+
|
|
4
|
+
describe("badgeTextColor (AFT parity with #186 safety net)", () => {
|
|
5
|
+
// A purple/lavender accent on a dark theme: the badge label should be the
|
|
6
|
+
// theme background (the inverse-of-panel look), the same fixed token the AFT
|
|
7
|
+
// sidebar uses, so two sibling badges never disagree on the same accent.
|
|
8
|
+
const accent = { r: 0.6, g: 0.5, b: 0.9, a: 1 };
|
|
9
|
+
|
|
10
|
+
test("opaque distinct background is used verbatim as the label", () => {
|
|
11
|
+
const background = { r: 0.05, g: 0.05, b: 0.07, a: 1 }; // near-black dark theme
|
|
12
|
+
// Returns the SAME background token rather than a computed color.
|
|
13
|
+
expect(badgeTextColor(accent, background)).toBe(background);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("light theme: background is used verbatim too (near-white label inverse)", () => {
|
|
17
|
+
const background = { r: 0.97, g: 0.97, b: 0.95, a: 1 };
|
|
18
|
+
expect(badgeTextColor(accent, background)).toBe(background);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("transparent background (alpha 0) falls back to a visible pick (#186)", () => {
|
|
22
|
+
// background:"none" resolves to RGBA(0,0,0,0); using it as text would
|
|
23
|
+
// render the label invisible, so fall back to a contrast pick on accent.
|
|
24
|
+
const transparent = { r: 0, g: 0, b: 0, a: 0 };
|
|
25
|
+
const result = badgeTextColor(accent, transparent);
|
|
26
|
+
expect(result).not.toBe(transparent);
|
|
27
|
+
expect(result).toBe(readableTextColorOn(accent));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("background ~= accent falls back to a visible pick", () => {
|
|
31
|
+
const sameAsAccent = { r: 0.6, g: 0.5, b: 0.9, a: 1 };
|
|
32
|
+
const result = badgeTextColor(accent, sameAsAccent);
|
|
33
|
+
expect(result).toBe(readableTextColorOn(accent));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("missing alpha is treated as opaque", () => {
|
|
37
|
+
const background = { r: 0.05, g: 0.05, b: 0.07 };
|
|
38
|
+
expect(badgeTextColor(accent, background)).toBe(background);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
3
41
|
|
|
4
42
|
describe("readableTextColorOn", () => {
|
|
5
43
|
test("dark accent gets white text", () => {
|