@cortexkit/opencode-magic-context 0.42.4 → 0.42.6
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/README.md +2 -2
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +1 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-jyj54gbe.js → conflict-warning-hook-aktepwpz.js} +3 -3
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/context-authority.d.ts +28 -1
- package/dist/features/magic-context/context-authority.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/curate-category-rotation.d.ts +26 -0
- package/dist/features/magic-context/dreamer/curate-category-rotation.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/open-opencode-db.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts +7 -5
- package/dist/features/magic-context/dreamer/storage-task-schedule.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-gates.d.ts +1 -0
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts +3 -0
- package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +6 -0
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts +5 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +12 -0
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +12 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +1 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +0 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts +8 -0
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +4 -4
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-state-sync.d.ts +3 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts +24 -2
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/producer-window-guard.d.ts +31 -1
- package/dist/hooks/magic-context/producer-window-guard.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +6 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +8 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +6 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +30 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-refusal-recovery.d.ts +28 -0
- package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +7 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-bkg2fdj1.js → index-4wwr6cz0.js} +3 -0
- package/dist/{index-jkrqhc7y.js → index-a9ssj59b.js} +94 -15
- package/dist/{index-faay035c.js → index-djp6rwz6.js} +484 -57
- package/dist/{index-p474rw9p.js → index-ngjmj6xx.js} +1 -1
- package/dist/{index-d8mgqezq.js → index-s61hh7gv.js} +50 -17
- package/dist/{index-1ar7b3e9.js → index-znraa0kv.js} +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1667 -380
- package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/memory-id-translation.d.ts +23 -0
- package/dist/plugin/memory-id-translation.d.ts.map +1 -0
- package/dist/plugin/rust-tool-backends.d.ts +1 -1
- package/dist/plugin/rust-tool-backends.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-c4sa354a.js → recomp-orchestrator-0xx1364v.js} +4 -4
- package/dist/{send-session-notification-xnypkxpx.js → send-session-notification-jbxknvar.js} +2 -2
- package/dist/shared/auto-update-provenance.d.ts +9 -0
- package/dist/shared/auto-update-provenance.d.ts.map +1 -0
- package/dist/shared/conflict-detector.d.ts +5 -1
- package/dist/shared/conflict-detector.d.ts.map +1 -1
- package/dist/shared/opencode-db-path.d.ts +31 -2
- package/dist/shared/opencode-db-path.d.ts.map +1 -1
- package/dist/shared/prompt-surface-runtime.d.ts +3 -3
- package/dist/shared/prompt-surface-runtime.d.ts.map +1 -1
- package/dist/{storage-meta-session-x3j0q9qy.js → storage-meta-session-z818pn1e.js} +2 -2
- package/dist/{task-config-ys0arma5.js → task-config-cv8k9y8r.js} +1 -1
- package/dist/tools/ctx-expand/mode.d.ts +31 -0
- package/dist/tools/ctx-expand/mode.d.ts.map +1 -0
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/constants.d.ts +1 -1
- package/dist/tools/ctx-note/constants.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/types.d.ts +1 -1
- package/dist/tools/ctx-note/types.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/constants.d.ts +1 -1
- package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +1 -1
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/dist/tools/light-descriptions.d.ts +2 -2
- package/dist/tools/light-descriptions.d.ts.map +1 -1
- package/dist/v2/hidden-completion.d.ts +69 -13
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts +13 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts +31 -0
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -0
- package/dist/v2/hooks/store.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +99 -19
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/server.d.ts +19 -0
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +2311 -2724
- package/dist/v2/store-reader.d.ts +11 -5
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/tui/index.d.ts +10 -0
- package/dist/v2/tui/index.d.ts.map +1 -0
- package/dist/v2/tui/types.d.ts +80 -0
- package/dist/v2/tui/types.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/agents/magic-context-prompt.ts +1 -1
- package/src/config/index.ts +31 -1
- package/src/config/protected-tokens.test.ts +40 -0
- package/src/config/schema/magic-context.ts +8 -3
- package/src/features/magic-context/dreamer/task-registry.ts +10 -0
- package/src/shared/auto-update-provenance.ts +42 -0
- package/src/shared/conflict-detector.test.ts +53 -0
- package/src/shared/conflict-detector.ts +88 -19
- package/src/shared/models-dev-cache.test.ts +25 -0
- package/src/shared/opencode-db-path.test.ts +96 -0
- package/src/shared/opencode-db-path.ts +146 -15
- package/src/shared/prompt-surface-a1-golden.md +12 -16
- package/src/shared/prompt-surface-runtime.test.ts +20 -0
- package/src/shared/prompt-surface-runtime.ts +7 -5
- package/src/tui/entry.mjs +11 -1
- package/src/v2/tui/host-contract.test.ts +202 -0
- package/src/v2/tui/index.ts +270 -0
- package/src/v2/tui/types.ts +67 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Magic Context agent-facing surface (exported from source)
|
|
2
2
|
|
|
3
|
-
Generated by `packages/plugin/scripts/export-agent-surface.ts` at 2026-09-
|
|
3
|
+
Generated by `packages/plugin/scripts/export-agent-surface.ts` at 2026-09-17.
|
|
4
4
|
Token counts are Claude BPE estimates on the raw text.
|
|
5
5
|
|
|
6
6
|
## 1. System-prompt guidance section
|
|
7
7
|
|
|
8
|
-
### PRIMARY full (reduce=on, memory=on, dreamer=on, temporal=on) —
|
|
8
|
+
### PRIMARY full (reduce=on, memory=on, dreamer=on, temporal=on) — 8697 chars, ~1998 tokens
|
|
9
9
|
|
|
10
10
|
```markdown
|
|
11
11
|
## Magic Context
|
|
@@ -23,7 +23,7 @@ Because of this:
|
|
|
23
23
|
Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.
|
|
24
24
|
|
|
25
25
|
Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
|
|
26
|
-
Use `ctx_reduce` to mark spent tagged content as discardable and reclaim space. Marking
|
|
26
|
+
Use `ctx_reduce` to mark spent tagged content as discardable and reclaim space. Marking QUEUES content for release. It stays fully visible to you until it is actually released, which may be the next turn or many turns later. Mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The newest token-mass window stays protected until displaced. Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
|
|
27
27
|
Do not announce or narrate `ctx_reduce` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
|
|
28
28
|
Use `ctx_note` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).
|
|
29
29
|
Use `ctx_memory` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in `<project-memory>` when they drift. Memories persist across sessions and every new session starts with them.
|
|
@@ -71,7 +71,7 @@ Example: `ctx_note(action="write", content="Implement X because Y", surface_cond
|
|
|
71
71
|
Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
### PRIMARY memory-off (reduce=on, memory=off) —
|
|
74
|
+
### PRIMARY memory-off (reduce=on, memory=off) — 7662 chars, ~1739 tokens
|
|
75
75
|
|
|
76
76
|
```markdown
|
|
77
77
|
## Magic Context
|
|
@@ -89,7 +89,7 @@ Because of this:
|
|
|
89
89
|
Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.
|
|
90
90
|
|
|
91
91
|
Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
|
|
92
|
-
Use `ctx_reduce` to mark spent tagged content as discardable and reclaim space. Marking
|
|
92
|
+
Use `ctx_reduce` to mark spent tagged content as discardable and reclaim space. Marking QUEUES content for release. It stays fully visible to you until it is actually released, which may be the next turn or many turns later. Mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The newest token-mass window stays protected until displaced. Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
|
|
93
93
|
Do not announce or narrate `ctx_reduce` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
|
|
94
94
|
Use `ctx_note` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).
|
|
95
95
|
Use `ctx_search` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
|
|
@@ -185,7 +185,7 @@ Older tool calls may show `[dropped §N§]` sentinels; that is normal context ma
|
|
|
185
185
|
|
|
186
186
|
## 2. Tool surface (description + parameters as serialized to the provider)
|
|
187
187
|
|
|
188
|
-
### ctx_reduce — description ~
|
|
188
|
+
### ctx_reduce — description ~304 tokens, params ~31 tokens (total ~335)
|
|
189
189
|
|
|
190
190
|
**Description:**
|
|
191
191
|
|
|
@@ -193,7 +193,7 @@ Older tool calls may show `[dropped §N§]` sentinels; that is normal context ma
|
|
|
193
193
|
Mark spent tagged content as discardable to reclaim context space. This is NOT an immediate delete. Use §N§ identifiers visible in the conversation. The `drop` param accepts ranges: "3-5", "1,2,9", "1-5,8".
|
|
194
194
|
|
|
195
195
|
How it works:
|
|
196
|
-
- Marking QUEUES content for release. It stays fully visible to you until
|
|
196
|
+
- Marking QUEUES content for release. It stays fully visible to you until it is actually released, which may be the next turn or many turns later. Mark spent outputs as soon as you finish with them; don't hoard the call for the end of the turn.
|
|
197
197
|
- The newest tags are protected: marking one just queues it until it ages out of the recent window, so marking recent output is harmless.
|
|
198
198
|
- When content is finally released it becomes a short placeholder, and re-running the tool is the only way to get it back. So mark only what you are genuinely DONE with — the test is "have I extracted what I need from this?", not "is it safe / do I have time before it drops?".
|
|
199
199
|
|
|
@@ -254,7 +254,7 @@ Two recovery modes for finer detail:
|
|
|
254
254
|
}
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
### ctx_note — description ~
|
|
257
|
+
### ctx_note — description ~387 tokens, params ~359 tokens (total ~746)
|
|
258
258
|
|
|
259
259
|
**Description:**
|
|
260
260
|
|
|
@@ -266,7 +266,7 @@ Use a note when something matters LATER but not in the next few steps: "revisit
|
|
|
266
266
|
Actions:
|
|
267
267
|
- write: save a note (content). Add surface_condition to make it a smart note (below).
|
|
268
268
|
- read: list notes, newest first. Default: latest active session notes + ready smart notes; page older ones with limit/offset, or inspect other states with filter.
|
|
269
|
-
- update: change
|
|
269
|
+
- update: change one note (note_ids=[N]). dismiss: retire 1–50 notes (note_ids=[...]).
|
|
270
270
|
|
|
271
271
|
Smart notes: pass surface_condition and the note stays hidden until a background checker confirms the condition — using ONLY externally verifiable signals (GitHub state via gh, files on disk, git history, web pages). It cannot see this conversation, so the condition must be checkable from outside:
|
|
272
272
|
✓ "When PR #42 in cortexkit/magic-context is merged"
|
|
@@ -318,12 +318,8 @@ Example: ctx_note(action="write", content="Re-run the perf benchmark once the bo
|
|
|
318
318
|
"description": "Skip this many newest notes for read — page older ones (default: 0)",
|
|
319
319
|
"type": "number"
|
|
320
320
|
},
|
|
321
|
-
"note_id": {
|
|
322
|
-
"description": "Note ID (required for 'dismiss' and 'update' actions).",
|
|
323
|
-
"type": "number"
|
|
324
|
-
},
|
|
325
321
|
"note_ids": {
|
|
326
|
-
"description": "
|
|
322
|
+
"description": "Note ids: exactly one for 'update', one to fifty for 'dismiss'. Ignored by 'write' and 'read'.",
|
|
327
323
|
"minItems": 1,
|
|
328
324
|
"maxItems": 50,
|
|
329
325
|
"type": "array",
|
|
@@ -465,8 +461,8 @@ The hash handler persists the MD5 of `output.system.join("\\n")`. The values bel
|
|
|
465
461
|
|
|
466
462
|
| Variant | Guidance bytes | MD5 system-prompt hash |
|
|
467
463
|
|---|---:|---|
|
|
468
|
-
| PRIMARY full |
|
|
469
|
-
| PRIMARY memory-off |
|
|
464
|
+
| PRIMARY full | 8759 | `f513711009bd420fe07b2672ce30d279` |
|
|
465
|
+
| PRIMARY memory-off | 7718 | `6834a01be9891fa5461e996f281ba63d` |
|
|
470
466
|
| PRIMARY reduce-off | 6526 | `7e5d386d94abe4f101bfd6d891ec183b` |
|
|
471
467
|
| SUBAGENT minimal | 705 | `1c09c70b742f79274c501dd8f7eff1fc` |
|
|
472
468
|
|
|
@@ -148,6 +148,26 @@ describe("prompt-surface runtime", () => {
|
|
|
148
148
|
);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("honors model routes for tool text when a model key is supplied", () => {
|
|
152
|
+
const runtime = createPromptSurfaceRuntime({
|
|
153
|
+
userConfigDirectory: tempDir(),
|
|
154
|
+
warn: () => undefined,
|
|
155
|
+
});
|
|
156
|
+
const config = {
|
|
157
|
+
default: "full" as const,
|
|
158
|
+
models: { "provider/light": "light" as const },
|
|
159
|
+
};
|
|
160
|
+
const processScoped = runtime.resolveRegistration(config);
|
|
161
|
+
const perModel = runtime.resolveRegistration(config, "provider/light");
|
|
162
|
+
|
|
163
|
+
expect(processScoped.preset).toBe("full");
|
|
164
|
+
expect(processScoped.descriptionFor("ctx_search", "Full search")).toBe("Full search");
|
|
165
|
+
expect(perModel.preset).toBe("light");
|
|
166
|
+
expect(perModel.descriptionFor("ctx_search", "Full search")).toBe(
|
|
167
|
+
LIGHT_TOOL_DESCRIPTIONS.ctx_search,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
151
171
|
it("serves built-in light descriptions without a fallback notice", () => {
|
|
152
172
|
const warnings: string[] = [];
|
|
153
173
|
const runtime = createPromptSurfaceRuntime({
|
|
@@ -80,6 +80,7 @@ export interface PromptSurfaceRegistrationSelection {
|
|
|
80
80
|
export interface PromptSurfaceRuntime {
|
|
81
81
|
resolveRegistration: (
|
|
82
82
|
config: PromptSurfaceConfig | undefined,
|
|
83
|
+
modelKey?: string,
|
|
83
84
|
) => PromptSurfaceRegistrationSelection;
|
|
84
85
|
resolveGuidance: (
|
|
85
86
|
config: PromptSurfaceConfig | undefined,
|
|
@@ -184,11 +185,12 @@ export function createPromptSurfaceRuntime(
|
|
|
184
185
|
};
|
|
185
186
|
|
|
186
187
|
return {
|
|
187
|
-
resolveRegistration(config) {
|
|
188
|
-
// OpenCode and Pi expose one immutable provider tool map per host
|
|
189
|
-
// registration
|
|
190
|
-
//
|
|
191
|
-
|
|
188
|
+
resolveRegistration(config, modelKey) {
|
|
189
|
+
// OpenCode 1.x and Pi expose one immutable provider tool map per host
|
|
190
|
+
// registration, so callers omit modelKey and only the default preset
|
|
191
|
+
// can choose text. OpenCode 2 rewrites draft.tools per request and
|
|
192
|
+
// passes the draft model so the same resolver honors model routes.
|
|
193
|
+
const { preset } = resolvePromptSurface(config, modelKey);
|
|
192
194
|
|
|
193
195
|
const overrides = config?.tool_descriptions ?? {};
|
|
194
196
|
for (const [toolId, description] of Object.entries(overrides)) {
|
package/src/tui/entry.mjs
CHANGED
|
@@ -33,4 +33,14 @@ if (!mod) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
const v1 = mod.default;
|
|
37
|
+
|
|
38
|
+
async function setup(context) {
|
|
39
|
+
const v2 = await import("../v2/tui/index.ts");
|
|
40
|
+
return v2.setup(context);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// OpenCode 1.18.30's TUI loader reads only id/server/tui and ignores setup
|
|
44
|
+
// (packages/opencode/src/plugin/shared.ts:272-304). OpenCode 2.0.3 validates
|
|
45
|
+
// id/setup and ignores tui, so one object safely serves both package loaders.
|
|
46
|
+
export default { id: v1.id, tui: v1.tui, setup };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { Host } from "@opencode/plugin/host";
|
|
6
|
+
import { setupWithJsx } from "./index";
|
|
7
|
+
import type { V2SidebarState, V2TuiContext } from "./types";
|
|
8
|
+
|
|
9
|
+
const temporary: string[] = [];
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
for (const directory of temporary.splice(0))
|
|
12
|
+
rmSync(directory, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function v2Context() {
|
|
16
|
+
const claims: Array<{ render: (input: { sessionID: string }) => unknown }> = [];
|
|
17
|
+
const layers: Array<ReturnType<Parameters<V2TuiContext["keymap"]["layer"]>[0]>> = [];
|
|
18
|
+
const cleanups: Array<() => void> = [];
|
|
19
|
+
const state: V2SidebarState = { snapshots: {} };
|
|
20
|
+
const context: V2TuiContext = {
|
|
21
|
+
location: { directory: process.cwd() },
|
|
22
|
+
renderer: { requestRender() {} },
|
|
23
|
+
data: {
|
|
24
|
+
listen(handler) {
|
|
25
|
+
void handler;
|
|
26
|
+
const cleanup = () => {};
|
|
27
|
+
cleanups.push(cleanup);
|
|
28
|
+
return cleanup;
|
|
29
|
+
},
|
|
30
|
+
location: { default: () => ({ directory: process.cwd() }) },
|
|
31
|
+
},
|
|
32
|
+
keymap: { layer: (input) => layers.push(input()) },
|
|
33
|
+
storage: {
|
|
34
|
+
memory: <Value extends object>(_key: string, options: { initial: Value }) => [
|
|
35
|
+
options.initial,
|
|
36
|
+
(mutation: (draft: Value) => void) => mutation(options.initial),
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
ui: {
|
|
40
|
+
router: { current: () => ({ type: "session", sessionID: "ses-v2-tui" }) },
|
|
41
|
+
slot: (claim) => {
|
|
42
|
+
claims.push(claim);
|
|
43
|
+
return () => {};
|
|
44
|
+
},
|
|
45
|
+
toast: { show() {} },
|
|
46
|
+
dialog: {
|
|
47
|
+
async alert() {},
|
|
48
|
+
async confirm() {
|
|
49
|
+
return false;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return { context, claims, layers, state };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function v1Api(directory: string) {
|
|
58
|
+
let slot:
|
|
59
|
+
| { slots: { sidebar_content: (ctx: unknown, value: unknown) => unknown }; dispose(): void }
|
|
60
|
+
| undefined;
|
|
61
|
+
const disposals: Array<() => void | Promise<void>> = [];
|
|
62
|
+
const color = "#ffffff";
|
|
63
|
+
const api = {
|
|
64
|
+
state: {
|
|
65
|
+
path: { directory },
|
|
66
|
+
session: { messages: () => [] },
|
|
67
|
+
},
|
|
68
|
+
route: { current: { name: "session", params: { sessionID: "ses-v1-tui" } } },
|
|
69
|
+
slots: { register: (value: typeof slot) => (slot = value) },
|
|
70
|
+
keymap: { registerLayer() {} },
|
|
71
|
+
command: { register() {} },
|
|
72
|
+
event: { on: () => () => {} },
|
|
73
|
+
renderer: { requestRender() {} },
|
|
74
|
+
lifecycle: { onDispose: (cleanup: () => void | Promise<void>) => disposals.push(cleanup) },
|
|
75
|
+
theme: {
|
|
76
|
+
current: {
|
|
77
|
+
primary: color,
|
|
78
|
+
secondary: color,
|
|
79
|
+
accent: color,
|
|
80
|
+
error: color,
|
|
81
|
+
warning: color,
|
|
82
|
+
success: color,
|
|
83
|
+
info: color,
|
|
84
|
+
text: color,
|
|
85
|
+
textMuted: color,
|
|
86
|
+
background: "#000000",
|
|
87
|
+
backgroundPanel: "#000000",
|
|
88
|
+
backgroundElement: "#000000",
|
|
89
|
+
backgroundMenu: "#000000",
|
|
90
|
+
border: color,
|
|
91
|
+
borderActive: color,
|
|
92
|
+
borderSubtle: color,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
ui: {
|
|
96
|
+
toast() {},
|
|
97
|
+
dialog: { replace() {}, clear() {} },
|
|
98
|
+
DialogAlert: () => null,
|
|
99
|
+
DialogConfirm: () => null,
|
|
100
|
+
DialogPrompt: () => null,
|
|
101
|
+
DialogSelect: () => null,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
api,
|
|
106
|
+
slot: () => slot,
|
|
107
|
+
dispose: async () => Promise.all(disposals.map((cleanup) => cleanup())),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
test("GA 2.0.5 resolves ./tui and executes the union setup contract", async () => {
|
|
112
|
+
const packageRoot = resolve(import.meta.dir, "../../..");
|
|
113
|
+
const entrypoints = Host.resolve({
|
|
114
|
+
name: "@cortexkit/opencode-magic-context",
|
|
115
|
+
directory: packageRoot,
|
|
116
|
+
});
|
|
117
|
+
expect(entrypoints.tui).toContain("src/tui/entry.mjs");
|
|
118
|
+
const loaded = (await Host.load(entrypoints.tui!)) as {
|
|
119
|
+
default: {
|
|
120
|
+
id: string;
|
|
121
|
+
tui: unknown;
|
|
122
|
+
setup: (context: V2TuiContext) => Promise<() => void>;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
expect(typeof loaded.default.setup).toBe("function");
|
|
126
|
+
const fixture = v2Context();
|
|
127
|
+
const cleanup = await setupWithJsx(fixture.context, (type, props) => ({ type, props }));
|
|
128
|
+
expect(fixture.claims).toHaveLength(1);
|
|
129
|
+
expect(fixture.claims[0]!.render({ sessionID: "ses-v2-tui" })).toEqual({
|
|
130
|
+
type: "text",
|
|
131
|
+
props: { children: expect.stringContaining("Magic Context") },
|
|
132
|
+
});
|
|
133
|
+
expect(fixture.layers[0]!.commands.map((command) => command.slash.name)).toEqual([
|
|
134
|
+
"ctx-status",
|
|
135
|
+
"ctx-recomp",
|
|
136
|
+
]);
|
|
137
|
+
cleanup();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("GA 2.0.5 records its unbound keymap.layer gap without losing the sidebar", async () => {
|
|
141
|
+
const fixture = v2Context();
|
|
142
|
+
Object.assign(fixture.context.keymap, {
|
|
143
|
+
layer: () => {
|
|
144
|
+
throw new Error("Keymap.Provider is missing");
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const cleanup = await setupWithJsx(fixture.context, (type, props) => ({ type, props }));
|
|
148
|
+
expect(fixture.claims).toHaveLength(1);
|
|
149
|
+
expect(fixture.layers).toHaveLength(0);
|
|
150
|
+
cleanup();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("OpenCode 1.18.30 TUI loader projection executes unchanged sidebar registration", async () => {
|
|
154
|
+
// v1.18.30 packages/opencode/src/plugin/shared.ts:272-304 reads only id,
|
|
155
|
+
// server and tui, rejects a simultaneous server+tui pair, and returns the
|
|
156
|
+
// object without validating unrelated keys. `setup` is therefore invisible
|
|
157
|
+
// to the pinned v1 TUI loader while this test executes the selected tui().
|
|
158
|
+
const packageRoot = resolve(import.meta.dir, "../../..");
|
|
159
|
+
const entrypoints = Host.resolve({
|
|
160
|
+
name: "@cortexkit/opencode-magic-context",
|
|
161
|
+
directory: packageRoot,
|
|
162
|
+
});
|
|
163
|
+
const loaded = (await Host.load(entrypoints.tui!)) as {
|
|
164
|
+
default: {
|
|
165
|
+
id: string;
|
|
166
|
+
tui: (api: unknown, options?: unknown, meta?: unknown) => Promise<void>;
|
|
167
|
+
setup: unknown;
|
|
168
|
+
server?: unknown;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const plugin = loaded.default;
|
|
172
|
+
expect({ id: plugin.id, tui: plugin.tui }).toEqual({
|
|
173
|
+
id: "opencode-magic-context",
|
|
174
|
+
tui: expect.any(Function),
|
|
175
|
+
});
|
|
176
|
+
expect(plugin.server).toBeUndefined();
|
|
177
|
+
expect(typeof plugin.setup).toBe("function");
|
|
178
|
+
|
|
179
|
+
const directory = mkdtempSync(resolve(tmpdir(), "mc-v1-tui-union-"));
|
|
180
|
+
temporary.push(directory);
|
|
181
|
+
const fixture = v1Api(directory);
|
|
182
|
+
const previousCompactionOverride = process.env.OPENCODE_DISABLE_AUTOCOMPACT;
|
|
183
|
+
process.env.OPENCODE_DISABLE_AUTOCOMPACT = "1";
|
|
184
|
+
try {
|
|
185
|
+
await plugin.tui(fixture.api, undefined, {
|
|
186
|
+
state: "first",
|
|
187
|
+
id: plugin.id,
|
|
188
|
+
source: "file",
|
|
189
|
+
spec: "fixture",
|
|
190
|
+
target: entrypoints.tui,
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
if (previousCompactionOverride === undefined)
|
|
194
|
+
delete process.env.OPENCODE_DISABLE_AUTOCOMPACT;
|
|
195
|
+
else process.env.OPENCODE_DISABLE_AUTOCOMPACT = previousCompactionOverride;
|
|
196
|
+
}
|
|
197
|
+
const slot = fixture.slot();
|
|
198
|
+
expect(slot).toBeDefined();
|
|
199
|
+
expect(typeof slot!.slots.sidebar_content).toBe("function");
|
|
200
|
+
await fixture.dispose();
|
|
201
|
+
slot!.dispose();
|
|
202
|
+
}, 15_000);
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { jsx } from "@opentui/solid/jsx-runtime";
|
|
2
|
+
import type { SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
|
|
3
|
+
import {
|
|
4
|
+
closeRpc,
|
|
5
|
+
getCompartmentCount,
|
|
6
|
+
initRpcClient,
|
|
7
|
+
loadSidebarSnapshot,
|
|
8
|
+
loadStatusDetail,
|
|
9
|
+
requestRecomp,
|
|
10
|
+
} from "../../tui/data/context-db";
|
|
11
|
+
import {
|
|
12
|
+
type SocketNotification,
|
|
13
|
+
startNotificationSocket,
|
|
14
|
+
stopNotificationSocket,
|
|
15
|
+
} from "../../tui/data/notification-socket";
|
|
16
|
+
import type { V2SidebarState, V2TuiContext } from "./types";
|
|
17
|
+
|
|
18
|
+
const SIDEBAR_REFRESH_MS = 1_000;
|
|
19
|
+
const inflight = new Set<string>();
|
|
20
|
+
const refreshedAt = new Map<string, number>();
|
|
21
|
+
|
|
22
|
+
function compactTokens(value: number): string {
|
|
23
|
+
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
24
|
+
if (value >= 1_000) return `${Math.round(value / 1_000)}K`;
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sidebarText(snapshot: SidebarSnapshot | undefined): string {
|
|
29
|
+
if (!snapshot) return "Magic Context · loading…";
|
|
30
|
+
const pressure =
|
|
31
|
+
snapshot.contextLimit > 0
|
|
32
|
+
? `${snapshot.usagePercentage.toFixed(1)}% · ${compactTokens(snapshot.inputTokens)}/${compactTokens(snapshot.contextLimit)}`
|
|
33
|
+
: `${compactTokens(snapshot.inputTokens)} tokens`;
|
|
34
|
+
const historian = snapshot.historianRunning ? "running" : "idle";
|
|
35
|
+
return [
|
|
36
|
+
"Magic Context",
|
|
37
|
+
`Context ${pressure}`,
|
|
38
|
+
`Historian ${historian} · C:${snapshot.compartmentCount}`,
|
|
39
|
+
`Memories ${snapshot.memoryBlockCount}/${snapshot.memoryCount} · Q:${snapshot.pendingOpsCount}`,
|
|
40
|
+
...(snapshot.lastTransformError ? [`Warning: ${snapshot.lastTransformError}`] : []),
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function statusText(detail: StatusDetail): string {
|
|
45
|
+
const context =
|
|
46
|
+
detail.contextLimit > 0
|
|
47
|
+
? `${detail.usagePercentage.toFixed(1)}% (${compactTokens(detail.inputTokens)}/${compactTokens(detail.contextLimit)} tokens)`
|
|
48
|
+
: `${compactTokens(detail.inputTokens)} tokens`;
|
|
49
|
+
return [
|
|
50
|
+
`Context: ${context}`,
|
|
51
|
+
`Historian: ${detail.historianRunning ? "running" : "idle"}`,
|
|
52
|
+
`Compartments: ${detail.compartmentCount}`,
|
|
53
|
+
`Memories: ${detail.memoryBlockCount} injected / ${detail.memoryCount} stored`,
|
|
54
|
+
`Pending reductions: ${detail.pendingOpsCount}`,
|
|
55
|
+
`Harness: opencode2`,
|
|
56
|
+
...(detail.lastTransformError ? [`Warning: ${detail.lastTransformError}`] : []),
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function currentSessionID(context: V2TuiContext): string | null {
|
|
61
|
+
const route = context.ui.router.current();
|
|
62
|
+
return route.type === "session" && route.sessionID ? route.sessionID : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function eventSessionID(event: unknown): string | undefined {
|
|
66
|
+
if (typeof event !== "object" || event === null) return undefined;
|
|
67
|
+
const record = event as Record<string, unknown>;
|
|
68
|
+
const data =
|
|
69
|
+
typeof record.data === "object" && record.data !== null
|
|
70
|
+
? (record.data as Record<string, unknown>)
|
|
71
|
+
: record;
|
|
72
|
+
for (const key of ["sessionID", "sessionId", "id"]) {
|
|
73
|
+
if (typeof data[key] === "string") return data[key];
|
|
74
|
+
}
|
|
75
|
+
const info = data.info;
|
|
76
|
+
if (typeof info === "object" && info !== null) {
|
|
77
|
+
const nested = info as Record<string, unknown>;
|
|
78
|
+
if (typeof nested.sessionID === "string") return nested.sessionID;
|
|
79
|
+
if (typeof nested.id === "string") return nested.id;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type JsxFactory = (type: string, props: Record<string, unknown>) => unknown;
|
|
85
|
+
|
|
86
|
+
export async function setupWithJsx(context: V2TuiContext, jsx: JsxFactory): Promise<() => void> {
|
|
87
|
+
const directory = context.location?.directory ?? context.data.location.default().directory;
|
|
88
|
+
initRpcClient(directory);
|
|
89
|
+
const [sidebar, updateSidebar] = context.storage.memory<V2SidebarState>(
|
|
90
|
+
"magic-context.sidebar.v2",
|
|
91
|
+
{ initial: { snapshots: {} } },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const refresh = async (sessionID: string, force = false): Promise<void> => {
|
|
95
|
+
if (!sessionID || inflight.has(sessionID)) return;
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
if (!force && now - (refreshedAt.get(sessionID) ?? 0) < SIDEBAR_REFRESH_MS) return;
|
|
98
|
+
refreshedAt.set(sessionID, now);
|
|
99
|
+
inflight.add(sessionID);
|
|
100
|
+
try {
|
|
101
|
+
const snapshot = await loadSidebarSnapshot(sessionID, directory);
|
|
102
|
+
updateSidebar((draft) => {
|
|
103
|
+
draft.snapshots[sessionID] = snapshot;
|
|
104
|
+
});
|
|
105
|
+
context.renderer.requestRender();
|
|
106
|
+
} finally {
|
|
107
|
+
inflight.delete(sessionID);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const showStatus = async (diagnostics = false, target = currentSessionID(context)) => {
|
|
112
|
+
if (!target) {
|
|
113
|
+
context.ui.toast.show({ message: "No active session", variant: "warning" });
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const result = await loadStatusDetail(target, directory);
|
|
117
|
+
if (currentSessionID(context) !== target) return false;
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
context.ui.toast.show({
|
|
120
|
+
message: "Magic Context status is unavailable",
|
|
121
|
+
variant: "warning",
|
|
122
|
+
});
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
await context.ui.dialog.alert({
|
|
126
|
+
title: diagnostics ? "Magic Context diagnostics" : "Magic Context status",
|
|
127
|
+
message: statusText(result.detail),
|
|
128
|
+
});
|
|
129
|
+
return true;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const showRecomp = async (target = currentSessionID(context)) => {
|
|
133
|
+
if (!target) {
|
|
134
|
+
context.ui.toast.show({ message: "No active session", variant: "warning" });
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const count = await getCompartmentCount(target, directory);
|
|
138
|
+
if (currentSessionID(context) !== target) return false;
|
|
139
|
+
if (!count.ok) {
|
|
140
|
+
context.ui.toast.show({ message: "Unable to load recomp details", variant: "error" });
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const confirmed = await context.ui.dialog.confirm({
|
|
144
|
+
title: "Recomp confirmation",
|
|
145
|
+
message: [
|
|
146
|
+
count.count === 0
|
|
147
|
+
? "This session has no compartments yet; recomp will build them from raw history."
|
|
148
|
+
: `This session has ${count.count} compartments.`,
|
|
149
|
+
"Recomp rebuilds compressed history and can consume significant tokens.",
|
|
150
|
+
].join("\n\n"),
|
|
151
|
+
label: { confirm: "Run recomp", cancel: "Cancel" },
|
|
152
|
+
});
|
|
153
|
+
if (!confirmed) return true;
|
|
154
|
+
const requested = await requestRecomp(target);
|
|
155
|
+
context.ui.toast.show({
|
|
156
|
+
message: requested
|
|
157
|
+
? "Recomp requested; historian will start shortly"
|
|
158
|
+
: "Recomp request failed",
|
|
159
|
+
variant: requested ? "info" : "error",
|
|
160
|
+
});
|
|
161
|
+
if (requested) void refresh(target, true);
|
|
162
|
+
return requested;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const unregisterSlot = context.ui.slot({
|
|
166
|
+
append: "sidebar.content",
|
|
167
|
+
render: ({ sessionID }) => {
|
|
168
|
+
void refresh(sessionID);
|
|
169
|
+
return jsx("text", { children: sidebarText(sidebar.snapshots[sessionID]) });
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
context.keymap.layer(() => ({
|
|
175
|
+
mode: "global",
|
|
176
|
+
commands: [
|
|
177
|
+
{
|
|
178
|
+
id: "magic-context.status",
|
|
179
|
+
title: "Magic Context: Status",
|
|
180
|
+
group: "Magic Context",
|
|
181
|
+
palette: true,
|
|
182
|
+
slash: { name: "ctx-status", arguments: true },
|
|
183
|
+
run: async (input) => {
|
|
184
|
+
await showStatus(input?.trim().toLowerCase() === "diagnostics");
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "magic-context.recomp",
|
|
189
|
+
title: "Magic Context: Recomp",
|
|
190
|
+
group: "Magic Context",
|
|
191
|
+
palette: true,
|
|
192
|
+
slash: { name: "ctx-recomp" },
|
|
193
|
+
run: async () => {
|
|
194
|
+
await showRecomp();
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (!(error instanceof Error) || error.message !== "Keymap.Provider is missing")
|
|
201
|
+
throw error;
|
|
202
|
+
console.warn(
|
|
203
|
+
"[magic-context] OpenCode 2.0.5 keymap.layer is unavailable during plugin setup; /ctx-status and /ctx-recomp were not registered",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const stopListening = context.data.listen(({ details }) => {
|
|
208
|
+
const sessionID = eventSessionID(details);
|
|
209
|
+
if (sessionID) void refresh(sessionID, true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const handleNotification = async (notification: SocketNotification): Promise<boolean> => {
|
|
213
|
+
const target = notification.sessionId ?? currentSessionID(context);
|
|
214
|
+
if (notification.sessionId && notification.sessionId !== currentSessionID(context))
|
|
215
|
+
return false;
|
|
216
|
+
if (notification.type === "toast") {
|
|
217
|
+
const payload = notification.payload;
|
|
218
|
+
context.ui.toast.show({
|
|
219
|
+
title: typeof payload.title === "string" ? payload.title : undefined,
|
|
220
|
+
message: String(payload.message ?? ""),
|
|
221
|
+
variant:
|
|
222
|
+
payload.variant === "success" ||
|
|
223
|
+
payload.variant === "warning" ||
|
|
224
|
+
payload.variant === "error"
|
|
225
|
+
? payload.variant
|
|
226
|
+
: "info",
|
|
227
|
+
duration: typeof payload.duration === "number" ? payload.duration : undefined,
|
|
228
|
+
});
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (notification.type !== "action") return false;
|
|
232
|
+
if (notification.payload.action === "show-status-dialog") {
|
|
233
|
+
return showStatus(notification.payload.diagnostics === true, target);
|
|
234
|
+
}
|
|
235
|
+
if (notification.payload.action === "show-recomp-dialog") return showRecomp(target);
|
|
236
|
+
if (notification.payload.action === "refresh-sidebar" && target) {
|
|
237
|
+
await refresh(target, true);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (notification.payload.action === "show-result-dialog") {
|
|
241
|
+
await context.ui.dialog.alert({
|
|
242
|
+
title: String(notification.payload.title ?? "Magic Context"),
|
|
243
|
+
message: String(notification.payload.message ?? ""),
|
|
244
|
+
});
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
startNotificationSocket({
|
|
251
|
+
getSessionId: () => currentSessionID(context),
|
|
252
|
+
onNotification: handleNotification,
|
|
253
|
+
});
|
|
254
|
+
console.info("[magic-context] @cortexkit/opencode-magic-context v2 TUI setup");
|
|
255
|
+
|
|
256
|
+
return () => {
|
|
257
|
+
unregisterSlot();
|
|
258
|
+
stopListening();
|
|
259
|
+
stopNotificationSocket();
|
|
260
|
+
closeRpc();
|
|
261
|
+
inflight.clear();
|
|
262
|
+
refreshedAt.clear();
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function setup(context: V2TuiContext): Promise<() => void> {
|
|
267
|
+
return setupWithJsx(context, jsx);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default { id: "opencode-magic-context", setup };
|