@cortexkit/opencode-magic-context 0.21.8 → 0.22.1
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 +124 -323
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/agents/permissions.d.ts +29 -14
- package/dist/agents/permissions.d.ts.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/migrate-experimental.d.ts +29 -0
- package/dist/config/migrate-experimental.d.ts.map +1 -0
- package/dist/config/schema/agent-overrides.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +95 -104
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-embedding.d.ts +34 -0
- package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-events.d.ts +50 -0
- package/dist/features/magic-context/compartment-events.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-storage.d.ts +22 -0
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/queue.d.ts +13 -2
- package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/runner.d.ts +11 -0
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
- package/dist/features/magic-context/memory/constants.d.ts +4 -0
- package/dist/features/magic-context/memory/constants.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.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +6 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-probe.d.ts +5 -0
- package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/index.d.ts +1 -1
- package/dist/features/magic-context/memory/index.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts +133 -0
- package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -0
- package/dist/features/magic-context/memory/project-identity.d.ts +38 -7
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +15 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/types.d.ts +3 -1
- package/dist/features/magic-context/memory/types.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 +7 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-docs-hash.d.ts +6 -0
- package/dist/features/magic-context/project-docs-hash.d.ts.map +1 -0
- package/dist/features/magic-context/project-identity.d.ts +2 -0
- package/dist/features/magic-context/project-identity.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts +51 -7
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +73 -0
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts +11 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts.map +1 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts +22 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts +25 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +44 -0
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -0
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-project-state.d.ts +19 -0
- package/dist/features/magic-context/storage-project-state.d.ts.map +1 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts +9 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +21 -1
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts +24 -0
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts.map +1 -0
- package/dist/features/magic-context/storage.d.ts +12 -3
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/subagent-token-capture.d.ts +1 -1
- package/dist/features/magic-context/subagent-token-capture.d.ts.map +1 -1
- package/dist/features/magic-context/tagger.d.ts +15 -1
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/types.d.ts +21 -0
- package/dist/features/magic-context/types.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.map +1 -1
- package/dist/features/magic-context/v22-deferred-backfill.d.ts +46 -0
- package/dist/features/magic-context/v22-deferred-backfill.d.ts.map +1 -0
- package/dist/features/magic-context/work-metrics.d.ts +66 -0
- package/dist/features/magic-context/work-metrics.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +9 -0
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +13 -1
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-parser.d.ts +25 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +27 -16
- package/dist/hooks/magic-context/compartment-prompt.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 +6 -2
- 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 +9 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +67 -4
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/decay-curve.d.ts +78 -0
- package/dist/hooks/magic-context/decay-curve.d.ts.map +1 -0
- package/dist/hooks/magic-context/decay-render.d.ts +67 -0
- package/dist/hooks/magic-context/decay-render.d.ts.map +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +17 -0
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +2 -0
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts +3 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +9 -21
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +126 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/key-files-block.d.ts.map +1 -1
- package/dist/hooks/magic-context/live-session-state.d.ts +9 -0
- package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts +35 -0
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +9 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +104 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts +61 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts +8 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/send-session-notification.d.ts +1 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +5 -6
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts +6 -0
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +0 -7
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +18 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +9 -7
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/upgrade-reminder.d.ts +73 -0
- package/dist/hooks/magic-context/upgrade-reminder.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9435 -4001
- package/dist/plugin/conflict-warning-hook.d.ts +13 -0
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/event.d.ts +10 -0
- package/dist/plugin/event.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +17 -1
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +1 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +27 -5
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +30 -2
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +9 -0
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/shared/sqlite-helpers.d.ts +7 -7
- package/dist/shared/sqlite.d.ts +23 -14
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +10 -1
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/tools/ctx-expand/tools.d.ts +5 -1
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +16 -1
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/package.json +5 -7
- package/src/shared/announcement.test.ts +23 -7
- package/src/shared/announcement.ts +30 -8
- package/src/shared/conflict-detector.test.ts +15 -2
- package/src/shared/conflict-fixer.test.ts +5 -1
- package/src/shared/models-dev-cache.test.ts +72 -4
- package/src/shared/models-dev-cache.ts +47 -8
- package/src/shared/opencode-compaction-detector.test.ts +10 -2
- package/src/shared/rpc-client.test.ts +54 -3
- package/src/shared/rpc-client.ts +19 -9
- package/src/shared/rpc-notifications.test.ts +54 -1
- package/src/shared/rpc-notifications.ts +82 -13
- package/src/shared/rpc-server.ts +33 -4
- package/src/shared/rpc-types.ts +30 -2
- package/src/shared/rpc-utils.ts +10 -0
- package/src/shared/sqlite-helpers.ts +9 -9
- package/src/shared/sqlite.ts +99 -80
- package/src/shared/tag-transcript.test.ts +280 -0
- package/src/shared/tag-transcript.ts +162 -33
- package/src/tui/data/context-db.ts +75 -11
- package/src/tui/index.tsx +223 -32
- package/src/tui/slots/sidebar-content.tsx +366 -34
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +0 -87
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +0 -1
- package/dist/shared/native-binding.d.ts +0 -87
- package/dist/shared/native-binding.d.ts.map +0 -1
- package/src/shared/native-binding.ts +0 -311
package/src/shared/rpc-server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import {
|
|
2
3
|
mkdirSync,
|
|
3
4
|
readdirSync,
|
|
@@ -20,6 +21,11 @@ export class MagicContextRpcServer {
|
|
|
20
21
|
private portFilePath: string;
|
|
21
22
|
private portDir: string;
|
|
22
23
|
private startedAt = Date.now();
|
|
24
|
+
// Unguessable per-process bearer token, published in the (user-private) port
|
|
25
|
+
// file and required on every non-health RPC call. Defends side-effecting
|
|
26
|
+
// endpoints (recomp/upgrade/dismiss) against any local process or
|
|
27
|
+
// browser-origin script that merely discovers/guesses the port.
|
|
28
|
+
private readonly token = randomBytes(32).toString("hex");
|
|
23
29
|
|
|
24
30
|
constructor(storageDir: string, directory: string) {
|
|
25
31
|
this.portFilePath = rpcPortFilePath(storageDir, directory);
|
|
@@ -57,7 +63,15 @@ export class MagicContextRpcServer {
|
|
|
57
63
|
try {
|
|
58
64
|
this.warnIfOtherLiveInstance();
|
|
59
65
|
const dir = dirname(this.portFilePath);
|
|
60
|
-
|
|
66
|
+
// The port file holds the per-process bearer token that
|
|
67
|
+
// gates side-effecting RPC endpoints (recomp/upgrade/
|
|
68
|
+
// dismiss). Under the default umask 0o022 a plain write
|
|
69
|
+
// lands at 0o644 in a 0o755 dir — world-readable, so any
|
|
70
|
+
// local UID could read the token and drive those endpoints,
|
|
71
|
+
// defeating the auth defense. Restrict both: dir 0o700,
|
|
72
|
+
// file 0o600. renameSync preserves the tmp file's mode, so
|
|
73
|
+
// the 0o600 on the write covers the final file.
|
|
74
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
61
75
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
62
76
|
writeFileSync(
|
|
63
77
|
tmpPath,
|
|
@@ -65,8 +79,9 @@ export class MagicContextRpcServer {
|
|
|
65
79
|
port: this.port,
|
|
66
80
|
pid: process.pid,
|
|
67
81
|
started_at: this.startedAt,
|
|
82
|
+
token: this.token,
|
|
68
83
|
}),
|
|
69
|
-
"utf-8",
|
|
84
|
+
{ encoding: "utf-8", mode: 0o600 },
|
|
70
85
|
);
|
|
71
86
|
renameSync(tmpPath, this.portFilePath);
|
|
72
87
|
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
@@ -114,8 +129,10 @@ export class MagicContextRpcServer {
|
|
|
114
129
|
private dispatch(req: IncomingMessage, res: ServerResponse): void {
|
|
115
130
|
const url = req.url ?? "";
|
|
116
131
|
|
|
117
|
-
// CORS
|
|
118
|
-
|
|
132
|
+
// No wildcard CORS: the only legitimate client is the in-process TUI
|
|
133
|
+
// client, which is not a browser origin. Omitting
|
|
134
|
+
// Access-Control-Allow-Origin makes browsers refuse to read responses,
|
|
135
|
+
// closing the CSRF-style read path a malicious local page could use.
|
|
119
136
|
|
|
120
137
|
if (req.method === "GET" && url === "/health") {
|
|
121
138
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -129,6 +146,18 @@ export class MagicContextRpcServer {
|
|
|
129
146
|
return;
|
|
130
147
|
}
|
|
131
148
|
|
|
149
|
+
// Require the per-process bearer token on every side-effecting call.
|
|
150
|
+
// The legitimate TUI client reads it from the same port file it used to
|
|
151
|
+
// discover the port; a process that only guessed the port cannot.
|
|
152
|
+
const auth = req.headers.authorization;
|
|
153
|
+
const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
|
|
154
|
+
if (presented !== this.token) {
|
|
155
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
156
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
157
|
+
req.resume();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
const method = url.slice(5); // strip "/rpc/"
|
|
133
162
|
const handler = this.handlers.get(method);
|
|
134
163
|
if (!handler) {
|
package/src/shared/rpc-types.ts
CHANGED
|
@@ -24,10 +24,23 @@ export interface SidebarSnapshot {
|
|
|
24
24
|
compartmentTokens: number;
|
|
25
25
|
factTokens: number;
|
|
26
26
|
memoryTokens: number;
|
|
27
|
+
/**
|
|
28
|
+
* Token estimate of the injected <project-docs> block (root ARCHITECTURE.md
|
|
29
|
+
* + STRUCTURE.md) that lives in m[0] in v2. Part of the message stream, not
|
|
30
|
+
* conversation. Display layer shows this as "Docs".
|
|
31
|
+
*/
|
|
32
|
+
docsTokens: number;
|
|
33
|
+
/**
|
|
34
|
+
* Token estimate of the injected <user-profile> block (promoted user
|
|
35
|
+
* memories) that lives in m[0] in v2. Part of the message stream, not
|
|
36
|
+
* conversation. Display layer shows this as "Profile".
|
|
37
|
+
*/
|
|
38
|
+
profileTokens: number;
|
|
27
39
|
/**
|
|
28
40
|
* Token estimate of real user/assistant discussion (text + reasoning +
|
|
29
|
-
* image parts) inside messages, excluding injected <session-history
|
|
30
|
-
* blocks. Display layer shows this as
|
|
41
|
+
* image parts) inside messages, excluding injected <session-history>,
|
|
42
|
+
* <project-docs>, and <user-profile> blocks. Display layer shows this as
|
|
43
|
+
* "Conversation".
|
|
31
44
|
*/
|
|
32
45
|
conversationTokens: number;
|
|
33
46
|
/**
|
|
@@ -57,6 +70,21 @@ export interface SidebarSnapshot {
|
|
|
57
70
|
executeThreshold: number;
|
|
58
71
|
newWorkTokens?: number | null;
|
|
59
72
|
totalInputTokens?: number | null;
|
|
73
|
+
/**
|
|
74
|
+
* Live recomp / session-upgrade progress for this session, or null when no
|
|
75
|
+
* recomp is running (and no recent terminal state is being shown). Drives the
|
|
76
|
+
* sidebar "Recomp"/"Upgrade" progress bar and the /ctx-status dialog. Mirrors
|
|
77
|
+
* the runtime `RecompProgress` shape from compartment-runner-types.ts.
|
|
78
|
+
*/
|
|
79
|
+
recompProgress?: {
|
|
80
|
+
phase: "recomp" | "migration" | "done" | "failed";
|
|
81
|
+
processedMessages: number;
|
|
82
|
+
totalMessages: number;
|
|
83
|
+
passCount: number;
|
|
84
|
+
compartmentsCreated: number;
|
|
85
|
+
message?: string;
|
|
86
|
+
note?: string;
|
|
87
|
+
} | null;
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
export interface StatusDetail extends SidebarSnapshot {
|
package/src/shared/rpc-utils.ts
CHANGED
|
@@ -5,6 +5,15 @@ export interface RpcPortFileRecord {
|
|
|
5
5
|
port: number;
|
|
6
6
|
pid: number;
|
|
7
7
|
started_at: number;
|
|
8
|
+
/**
|
|
9
|
+
* Per-process bearer token. The server requires it on all non-health RPC
|
|
10
|
+
* calls so a random local process or browser-origin script that merely
|
|
11
|
+
* discovers/guesses the port cannot drive side-effecting endpoints
|
|
12
|
+
* (recomp/upgrade/dismiss). Optional in the type for forward/backward
|
|
13
|
+
* compatibility with port files written by older builds (treated as "no
|
|
14
|
+
* auth required" only when the server itself didn't set one).
|
|
15
|
+
*/
|
|
16
|
+
token?: string;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
/**
|
|
@@ -56,6 +65,7 @@ export function parseRpcPortFile(content: string, fallbackPid = 0): RpcPortFileR
|
|
|
56
65
|
port,
|
|
57
66
|
pid,
|
|
58
67
|
started_at: Number.isFinite(startedAt) ? startedAt : 0,
|
|
68
|
+
token: typeof parsed.token === "string" ? parsed.token : undefined,
|
|
59
69
|
};
|
|
60
70
|
} catch {
|
|
61
71
|
return null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-runtime helpers that smooth over the small bun:sqlite ↔
|
|
3
|
-
* API differences without leaking either
|
|
2
|
+
* Cross-runtime helpers that smooth over the small bun:sqlite ↔ node:sqlite
|
|
3
|
+
* API differences without leaking either backend into call sites.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Database } from "./sqlite";
|
|
@@ -8,17 +8,17 @@ import type { Database } from "./sqlite";
|
|
|
8
8
|
/**
|
|
9
9
|
* Close a database, ignoring errors.
|
|
10
10
|
*
|
|
11
|
-
* bun:sqlite supports `db.close(throwOnError = false)`.
|
|
12
|
-
*
|
|
13
|
-
* mirrors the bun "swallow errors" semantics for both
|
|
14
|
-
* test teardown and `finally` blocks where the caller
|
|
15
|
-
* the close succeeded.
|
|
11
|
+
* bun:sqlite supports `db.close(throwOnError = false)`. node:sqlite has only
|
|
12
|
+
* `db.close()` and throws ("database is not open") on an already-closed
|
|
13
|
+
* handle. This helper mirrors the bun "swallow errors" semantics for both
|
|
14
|
+
* runtimes — useful in test teardown and `finally` blocks where the caller
|
|
15
|
+
* doesn't care whether the close succeeded.
|
|
16
16
|
*/
|
|
17
17
|
export function closeQuietly(db: Database | null | undefined): void {
|
|
18
18
|
if (!db) return;
|
|
19
19
|
// Just attempt close and swallow errors. bun:sqlite has no `open` property,
|
|
20
|
-
// and
|
|
21
|
-
//
|
|
20
|
+
// and node:sqlite throws on an already-closed handle — both are handled by
|
|
21
|
+
// the bare try/catch.
|
|
22
22
|
try {
|
|
23
23
|
db.close();
|
|
24
24
|
} catch {
|
package/src/shared/sqlite.ts
CHANGED
|
@@ -3,33 +3,42 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The same shipped plugin artifact must run under two different runtimes:
|
|
5
5
|
* - Bun (current OpenCode releases) → uses `bun:sqlite` (built-in, fast)
|
|
6
|
-
* - Node
|
|
6
|
+
* - Node / Electron (Pi plugin, OpenCode Desktop) → uses `node:sqlite`
|
|
7
|
+
* (`DatabaseSync`, built into Node 22.5+ / Electron 41+, stable-enough and
|
|
8
|
+
* flag-free since Node 22.13/23.4).
|
|
7
9
|
*
|
|
8
|
-
* Bun
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Bun has no `node:sqlite`, and Node/Electron have no `bun:sqlite`. Static
|
|
11
|
+
* imports of either would crash at parse time in the wrong runtime, so we use
|
|
12
|
+
* dynamic imports gated by runtime detection.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Why `node:sqlite` instead of `better-sqlite3`: better-sqlite3 is a native
|
|
15
|
+
* module requiring per-ABI prebuilds, and Electron's ABI never matches the npm
|
|
16
|
+
* Node prebuild — which forced a runtime download of an Electron-matched
|
|
17
|
+
* `.node` binary (a supply-chain + maintenance liability). `node:sqlite` is
|
|
18
|
+
* built into the runtime, so there is NOTHING to download or rebuild. Both Pi
|
|
19
|
+
* (plain Node 24) and OpenCode Desktop (Electron 41 → Node 24.14.1) ship it.
|
|
16
20
|
*
|
|
17
|
-
*
|
|
18
|
-
* - new Database(path, { readonly?: boolean })
|
|
21
|
+
* API surface we use (common across both backends, modulo the shims below):
|
|
22
|
+
* - new Database(path, { readonly?: boolean }) ← we map readonly→readOnly
|
|
19
23
|
* - db.prepare(sql).run/get/all
|
|
20
24
|
* - db.exec(multistatement)
|
|
21
|
-
* - db.transaction(fn) → wrapped function
|
|
25
|
+
* - db.transaction(fn) → wrapped function ← shimmed for node:sqlite
|
|
22
26
|
* - db.close()
|
|
23
27
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* The two backend differences we bridge for node:sqlite:
|
|
29
|
+
* 1. node:sqlite has no `db.transaction(fn)` helper — we add a savepoint-aware
|
|
30
|
+
* shim (below) that matches better-sqlite3/bun semantics.
|
|
31
|
+
* 2. node:sqlite's constructor option is `readOnly` (camel-case), not
|
|
32
|
+
* better-sqlite3/bun's `readonly` — we translate it so call sites are
|
|
33
|
+
* unchanged.
|
|
34
|
+
* Everything else (named params with bare keys, ATTACH under defensive mode,
|
|
35
|
+
* `run()` → {changes,lastInsertRowid}) is identical and was verified directly.
|
|
27
36
|
*/
|
|
28
37
|
|
|
29
|
-
// Type import only —
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
38
|
+
// Type import only — runtime is loaded dynamically below. @types/better-sqlite3
|
|
39
|
+
// has the richest definitions and is a structural superset of the API surface
|
|
40
|
+
// we use, so calls typed against BetterSqlite3 work under bun:sqlite and
|
|
41
|
+
// node:sqlite at runtime (both expose prepare/run/get/all/exec/close).
|
|
33
42
|
import type BetterSqlite3 from "better-sqlite3";
|
|
34
43
|
|
|
35
44
|
// Detect Bun via process.versions.bun. Both globalThis.Bun and
|
|
@@ -41,80 +50,90 @@ const isBun = typeof process !== "undefined" && typeof process.versions?.bun ===
|
|
|
41
50
|
|
|
42
51
|
// IMPORTANT: bundler-evading dynamic imports.
|
|
43
52
|
//
|
|
44
|
-
// We can't write `await import("
|
|
53
|
+
// We can't write `await import("node:sqlite")` directly because esbuild/bun
|
|
45
54
|
// would try to resolve both modules at build time, and one of them won't exist
|
|
46
|
-
// in the build runtime (bun:sqlite is missing in Node,
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// no referrer module and Node throws "A dynamic import callback was not
|
|
55
|
+
// in the build runtime (bun:sqlite is missing in Node, node:sqlite is missing
|
|
56
|
+
// in Bun). Earlier versions used `new Function("p", "return import(p)")(...)`
|
|
57
|
+
// to defeat static analysis, but that breaks Pi's vm-based extension loader: a
|
|
58
|
+
// Function constructed at runtime has no module record, so `import()` inside it
|
|
59
|
+
// has no referrer module and Node throws "A dynamic import callback was not
|
|
52
60
|
// specified".
|
|
53
61
|
//
|
|
54
62
|
// The /* @vite-ignore */ + variable indirection pattern hides the specifier
|
|
55
63
|
// from static analyzers while keeping a real referrer module for the
|
|
56
64
|
// dynamic import — Pi's loader, esbuild, and bun build all accept it.
|
|
57
65
|
const bunSpec = "bun:" + "sqlite";
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
// Under Electron, the npm-installed better-sqlite3 binary has the wrong ABI
|
|
61
|
-
// (it's a Node prebuild but Electron embeds a different NODE_MODULE_VERSION).
|
|
62
|
-
// resolveBetterSqliteNativeBinding() detects this and downloads + caches the
|
|
63
|
-
// matching Electron prebuild, then returns its absolute path so we can pass
|
|
64
|
-
// it to better-sqlite3 via the `nativeBinding` constructor option (a
|
|
65
|
-
// documented public API). Returns null outside Electron OR when the on-disk
|
|
66
|
-
// binary already matches the runtime ABI — in those cases the default
|
|
67
|
-
// bindings() lookup just works.
|
|
68
|
-
const electronNativeBinding = isBun
|
|
69
|
-
? null
|
|
70
|
-
: await (async () => {
|
|
71
|
-
const mod = await import("./native-binding");
|
|
72
|
-
return mod.resolveBetterSqliteNativeBinding();
|
|
73
|
-
})();
|
|
66
|
+
const nodeSpec = "node:" + "sqlite";
|
|
74
67
|
|
|
75
68
|
const sqliteModule = isBun
|
|
76
69
|
? await import(/* @vite-ignore */ bunSpec)
|
|
77
|
-
: await import(/* @vite-ignore */
|
|
70
|
+
: await import(/* @vite-ignore */ nodeSpec);
|
|
78
71
|
|
|
79
|
-
// Different export shapes between the two
|
|
80
|
-
// - bun:sqlite
|
|
81
|
-
//
|
|
82
|
-
|
|
72
|
+
// Different export shapes between the two backends:
|
|
73
|
+
// - bun:sqlite → named export `Database` (has its own .transaction, accepts
|
|
74
|
+
// `{ readonly }`) — usable as-is.
|
|
75
|
+
// - node:sqlite → named export `DatabaseSync` (no .transaction, option is
|
|
76
|
+
// `readOnly`) — wrapped below.
|
|
77
|
+
const DatabaseImpl: typeof BetterSqlite3 = isBun
|
|
78
|
+
? (sqliteModule.Database as typeof BetterSqlite3)
|
|
79
|
+
: buildNodeSqliteDatabaseClass(sqliteModule.DatabaseSync);
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Wrap node:sqlite's `DatabaseSync` so it presents the better-sqlite3/bun
|
|
83
|
+
* surface the rest of the codebase calls:
|
|
84
|
+
* - translate the `{ readonly }` constructor option → node:sqlite's `readOnly`
|
|
85
|
+
* - add a `transaction(fn)` helper that matches better-sqlite3 semantics,
|
|
86
|
+
* using `db.isTransaction` to pick BEGIN (top-level) vs SAVEPOINT (nested),
|
|
87
|
+
* so it composes correctly with manual `BEGIN IMMEDIATE` blocks too.
|
|
88
|
+
*/
|
|
89
|
+
// biome-ignore lint/suspicious/noExplicitAny: node:sqlite has no shipped types here; the public export is cast to the better-sqlite3 shape.
|
|
90
|
+
function buildNodeSqliteDatabaseClass(DatabaseSync: any): typeof BetterSqlite3 {
|
|
91
|
+
// Single constant savepoint name is correct for arbitrary nesting depth:
|
|
92
|
+
// SQLite savepoints with the same name stack LIFO — RELEASE / ROLLBACK TO
|
|
93
|
+
// always target the most recent. node:sqlite is synchronous + single-process
|
|
94
|
+
// per connection, so there is no concurrent-savepoint hazard.
|
|
95
|
+
const SAVEPOINT = "mc_tx_sp";
|
|
96
|
+
|
|
97
|
+
class NodeSqliteDatabase extends DatabaseSync {
|
|
98
|
+
constructor(filename?: string | Buffer, options?: BetterSqlite3.Options) {
|
|
99
|
+
const translated: Record<string, unknown> = { ...options };
|
|
100
|
+
if (options && "readonly" in options) {
|
|
101
|
+
translated.readOnly = (options as { readonly?: boolean }).readonly;
|
|
102
|
+
delete translated.readonly;
|
|
103
|
+
}
|
|
104
|
+
super(typeof filename === "string" ? filename : ":memory:", translated);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// biome-ignore lint/suspicious/noExplicitAny: mirrors better-sqlite3's generic transaction(fn) signature.
|
|
108
|
+
transaction<F extends (...args: any[]) => any>(fn: F): F {
|
|
109
|
+
// biome-ignore lint/suspicious/noExplicitAny: faithful pass-through of this/args to fn.
|
|
110
|
+
const self = this as any;
|
|
111
|
+
const wrapped = function (this: unknown, ...args: unknown[]): unknown {
|
|
112
|
+
const nested = self.isTransaction === true;
|
|
113
|
+
self.exec(nested ? `SAVEPOINT ${SAVEPOINT}` : "BEGIN");
|
|
114
|
+
try {
|
|
115
|
+
const result = fn.apply(this, args);
|
|
116
|
+
self.exec(nested ? `RELEASE ${SAVEPOINT}` : "COMMIT");
|
|
117
|
+
return result;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (nested) {
|
|
120
|
+
// ROLLBACK TO unwinds the savepoint's changes but leaves
|
|
121
|
+
// it on the stack; RELEASE then pops it (better-sqlite3
|
|
122
|
+
// does both).
|
|
123
|
+
self.exec(`ROLLBACK TO ${SAVEPOINT}`);
|
|
124
|
+
self.exec(`RELEASE ${SAVEPOINT}`);
|
|
125
|
+
} else {
|
|
126
|
+
self.exec("ROLLBACK");
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
return wrapped as unknown as F;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return NodeSqliteDatabase as unknown as typeof BetterSqlite3;
|
|
136
|
+
}
|
|
118
137
|
|
|
119
138
|
export const Database: typeof BetterSqlite3 = DatabaseImpl;
|
|
120
139
|
|