@wolfx/opencode-magic-context 0.30.1 → 0.30.3
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/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/recursive-text-splitter.d.ts +36 -0
- package/dist/features/magic-context/recursive-text-splitter.d.ts.map +1 -0
- package/dist/index.js +368 -117
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +1 -1
- package/dist/shared/data-path.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +8 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +28 -10
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +22 -3
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +4 -14
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +39 -0
- package/dist/tui/data/notification-socket.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/shared/announcement.ts +2 -2
- package/src/shared/data-path.test.ts +28 -0
- package/src/shared/data-path.ts +5 -0
- package/src/shared/rpc-client.ts +14 -0
- package/src/shared/rpc-notifications.test.ts +68 -11
- package/src/shared/rpc-notifications.ts +75 -36
- package/src/shared/rpc-server.ts +249 -150
- package/src/tui/data/context-db.ts +10 -64
- package/src/tui/data/notification-socket.ts +229 -0
- package/src/tui/index.tsx +68 -118
package/dist/index.js
CHANGED
|
@@ -64,6 +64,9 @@ function getMagicContextTempDir(harness = getHarness()) {
|
|
|
64
64
|
return path.join(os.tmpdir(), harness, "magic-context");
|
|
65
65
|
}
|
|
66
66
|
function getMagicContextLogPath(harness = getHarness()) {
|
|
67
|
+
const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
|
|
68
|
+
if (envPath)
|
|
69
|
+
return envPath;
|
|
67
70
|
return path.join(getMagicContextTempDir(harness), "magic-context.log");
|
|
68
71
|
}
|
|
69
72
|
function getProjectMagicContextDir(directory) {
|
|
@@ -157659,12 +157662,30 @@ var init_safe_notification_target = __esm(() => {
|
|
|
157659
157662
|
// src/shared/rpc-notifications.ts
|
|
157660
157663
|
var exports_rpc_notifications = {};
|
|
157661
157664
|
__export(exports_rpc_notifications, {
|
|
157665
|
+
registerNotificationSink: () => registerNotificationSink,
|
|
157662
157666
|
pushNotification: () => pushNotification,
|
|
157663
157667
|
isTuiConnected: () => isTuiConnected,
|
|
157664
157668
|
drainNotifications: () => drainNotifications
|
|
157665
157669
|
});
|
|
157670
|
+
function registerNotificationSink(sink) {
|
|
157671
|
+
sinks.add(sink);
|
|
157672
|
+
return () => {
|
|
157673
|
+
sinks.delete(sink);
|
|
157674
|
+
};
|
|
157675
|
+
}
|
|
157676
|
+
function notificationMatchesSink(notification, sink) {
|
|
157677
|
+
return notification.sessionId === undefined || sink.sessionId === undefined || notification.sessionId === sink.sessionId;
|
|
157678
|
+
}
|
|
157666
157679
|
function pushNotification(type, payload, sessionId) {
|
|
157667
|
-
|
|
157680
|
+
const notification = { id: nextNotificationId++, type, payload, sessionId };
|
|
157681
|
+
queue.push(notification);
|
|
157682
|
+
for (const sink of sinks) {
|
|
157683
|
+
if (!notificationMatchesSink(notification, sink))
|
|
157684
|
+
continue;
|
|
157685
|
+
try {
|
|
157686
|
+
sink.send(notification);
|
|
157687
|
+
} catch {}
|
|
157688
|
+
}
|
|
157668
157689
|
if (queue.length > 100) {
|
|
157669
157690
|
const newestPerSession = new Map;
|
|
157670
157691
|
for (const n of queue) {
|
|
@@ -157684,10 +157705,6 @@ function pushNotification(type, payload, sessionId) {
|
|
|
157684
157705
|
}
|
|
157685
157706
|
}
|
|
157686
157707
|
function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
157687
|
-
const now = Date.now();
|
|
157688
|
-
lastDrainAtAny = now;
|
|
157689
|
-
if (sessionId !== undefined)
|
|
157690
|
-
lastDrainAtBySession.set(sessionId, now);
|
|
157691
157708
|
const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
|
|
157692
157709
|
if (lastReceivedId > 0) {
|
|
157693
157710
|
queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
|
|
@@ -157695,17 +157712,20 @@ function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
|
157695
157712
|
return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
|
|
157696
157713
|
}
|
|
157697
157714
|
function isTuiConnected(sessionId) {
|
|
157698
|
-
|
|
157699
|
-
|
|
157700
|
-
|
|
157701
|
-
return
|
|
157715
|
+
if (sinks.size === 0)
|
|
157716
|
+
return false;
|
|
157717
|
+
if (sessionId === undefined)
|
|
157718
|
+
return true;
|
|
157719
|
+
for (const sink of sinks) {
|
|
157720
|
+
if (sink.sessionId === undefined || sink.sessionId === sessionId)
|
|
157721
|
+
return true;
|
|
157702
157722
|
}
|
|
157703
|
-
return
|
|
157723
|
+
return false;
|
|
157704
157724
|
}
|
|
157705
|
-
var queue, nextNotificationId = 1,
|
|
157725
|
+
var queue, nextNotificationId = 1, sinks;
|
|
157706
157726
|
var init_rpc_notifications = __esm(() => {
|
|
157707
157727
|
queue = [];
|
|
157708
|
-
|
|
157728
|
+
sinks = new Set;
|
|
157709
157729
|
});
|
|
157710
157730
|
|
|
157711
157731
|
// src/plugin/conflict-warning-hook.ts
|
|
@@ -158381,6 +158401,94 @@ function parseFields(json2) {
|
|
|
158381
158401
|
}
|
|
158382
158402
|
var init_compartment_events = () => {};
|
|
158383
158403
|
|
|
158404
|
+
// src/features/magic-context/recursive-text-splitter.ts
|
|
158405
|
+
function splitOnSeparator(text, separator) {
|
|
158406
|
+
const splits = separator ? text.split(separator) : text.split("");
|
|
158407
|
+
return splits.filter((s) => s !== "");
|
|
158408
|
+
}
|
|
158409
|
+
function mergeSplits(splits, separator, chunkSize, lengthFunction) {
|
|
158410
|
+
const docs = [];
|
|
158411
|
+
const currentDoc = [];
|
|
158412
|
+
let total = 0;
|
|
158413
|
+
const joinDocs = (docsToJoin) => {
|
|
158414
|
+
const joined = docsToJoin.join(separator).trim();
|
|
158415
|
+
return joined === "" ? null : joined;
|
|
158416
|
+
};
|
|
158417
|
+
for (const d of splits) {
|
|
158418
|
+
const len = lengthFunction(d);
|
|
158419
|
+
if (total + len + currentDoc.length * separator.length > chunkSize) {
|
|
158420
|
+
if (currentDoc.length > 0) {
|
|
158421
|
+
const doc3 = joinDocs(currentDoc);
|
|
158422
|
+
if (doc3 !== null)
|
|
158423
|
+
docs.push(doc3);
|
|
158424
|
+
while (total > 0 && currentDoc.length > 0) {
|
|
158425
|
+
total -= lengthFunction(currentDoc[0]);
|
|
158426
|
+
currentDoc.shift();
|
|
158427
|
+
}
|
|
158428
|
+
}
|
|
158429
|
+
}
|
|
158430
|
+
currentDoc.push(d);
|
|
158431
|
+
total += len;
|
|
158432
|
+
}
|
|
158433
|
+
const doc2 = joinDocs(currentDoc);
|
|
158434
|
+
if (doc2 !== null)
|
|
158435
|
+
docs.push(doc2);
|
|
158436
|
+
return docs;
|
|
158437
|
+
}
|
|
158438
|
+
function splitTextRecursive(text, separators, chunkSize, lengthFunction) {
|
|
158439
|
+
const finalChunks = [];
|
|
158440
|
+
let separator = separators[separators.length - 1];
|
|
158441
|
+
let newSeparators;
|
|
158442
|
+
for (let i = 0;i < separators.length; i += 1) {
|
|
158443
|
+
const s = separators[i];
|
|
158444
|
+
if (s === "") {
|
|
158445
|
+
separator = s;
|
|
158446
|
+
break;
|
|
158447
|
+
}
|
|
158448
|
+
if (text.includes(s)) {
|
|
158449
|
+
separator = s;
|
|
158450
|
+
newSeparators = separators.slice(i + 1);
|
|
158451
|
+
break;
|
|
158452
|
+
}
|
|
158453
|
+
}
|
|
158454
|
+
const splits = splitOnSeparator(text, separator);
|
|
158455
|
+
let goodSplits = [];
|
|
158456
|
+
for (const s of splits) {
|
|
158457
|
+
if (lengthFunction(s) < chunkSize) {
|
|
158458
|
+
goodSplits.push(s);
|
|
158459
|
+
} else {
|
|
158460
|
+
if (goodSplits.length) {
|
|
158461
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
158462
|
+
goodSplits = [];
|
|
158463
|
+
}
|
|
158464
|
+
if (!newSeparators) {
|
|
158465
|
+
finalChunks.push(s);
|
|
158466
|
+
} else {
|
|
158467
|
+
finalChunks.push(...splitTextRecursive(s, newSeparators, chunkSize, lengthFunction));
|
|
158468
|
+
}
|
|
158469
|
+
}
|
|
158470
|
+
}
|
|
158471
|
+
if (goodSplits.length) {
|
|
158472
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
158473
|
+
}
|
|
158474
|
+
return finalChunks;
|
|
158475
|
+
}
|
|
158476
|
+
function recursiveCharacterSplit(text, options) {
|
|
158477
|
+
const chunkSize = options.chunkSize;
|
|
158478
|
+
const lengthFunction = options.lengthFunction ?? ((t) => t.length);
|
|
158479
|
+
const separators = options.separators ?? DEFAULT_SEPARATORS;
|
|
158480
|
+
if (text.length === 0)
|
|
158481
|
+
return [];
|
|
158482
|
+
return splitTextRecursive(text, separators, chunkSize, lengthFunction);
|
|
158483
|
+
}
|
|
158484
|
+
var DEFAULT_SEPARATORS;
|
|
158485
|
+
var init_recursive_text_splitter = __esm(() => {
|
|
158486
|
+
DEFAULT_SEPARATORS = [`
|
|
158487
|
+
|
|
158488
|
+
`, `
|
|
158489
|
+
`, " ", ""];
|
|
158490
|
+
});
|
|
158491
|
+
|
|
158384
158492
|
// src/features/magic-context/compartment-chunk-embedding.ts
|
|
158385
158493
|
import { createHash as createHash6 } from "node:crypto";
|
|
158386
158494
|
function getLoadFtsRowsStatement(db) {
|
|
@@ -158648,6 +158756,19 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
158648
158756
|
const lineStart = range?.start ?? startOrdinal;
|
|
158649
158757
|
const lineEnd = range?.end ?? lineStart;
|
|
158650
158758
|
const lineTokens = estimateTokens(line);
|
|
158759
|
+
if (lineTokens > effectiveMax) {
|
|
158760
|
+
flush2();
|
|
158761
|
+
for (const slice of splitOversizedLine(line, effectiveMax)) {
|
|
158762
|
+
windows.push({
|
|
158763
|
+
windowIndex: windows.length + 1,
|
|
158764
|
+
startOrdinal: lineStart,
|
|
158765
|
+
endOrdinal: lineEnd,
|
|
158766
|
+
text: slice,
|
|
158767
|
+
chunkHash: hashChunkText(slice)
|
|
158768
|
+
});
|
|
158769
|
+
}
|
|
158770
|
+
continue;
|
|
158771
|
+
}
|
|
158651
158772
|
if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
|
|
158652
158773
|
flush2();
|
|
158653
158774
|
}
|
|
@@ -158661,6 +158782,56 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
158661
158782
|
flush2();
|
|
158662
158783
|
return windows;
|
|
158663
158784
|
}
|
|
158785
|
+
function splitOversizedLine(line, effectiveMax) {
|
|
158786
|
+
let slices = [];
|
|
158787
|
+
try {
|
|
158788
|
+
slices = recursiveCharacterSplit(line, {
|
|
158789
|
+
chunkSize: effectiveMax,
|
|
158790
|
+
lengthFunction: estimateTokens
|
|
158791
|
+
});
|
|
158792
|
+
} catch (error51) {
|
|
158793
|
+
log("[magic-context] recursiveCharacterSplit failed; using char-budget fallback:", error51);
|
|
158794
|
+
slices = [];
|
|
158795
|
+
}
|
|
158796
|
+
if (slices.length === 0) {
|
|
158797
|
+
slices = charBudgetSplit(line, effectiveMax);
|
|
158798
|
+
}
|
|
158799
|
+
const safe = [];
|
|
158800
|
+
const pushChecked = (slice) => {
|
|
158801
|
+
if (estimateTokens(slice) > effectiveMax && slice.length > 1) {
|
|
158802
|
+
safe.push(...charBudgetSplit(slice, effectiveMax));
|
|
158803
|
+
return;
|
|
158804
|
+
}
|
|
158805
|
+
safe.push(slice);
|
|
158806
|
+
};
|
|
158807
|
+
for (const slice of slices) {
|
|
158808
|
+
if (estimateTokens(slice) <= effectiveMax) {
|
|
158809
|
+
safe.push(slice);
|
|
158810
|
+
} else {
|
|
158811
|
+
for (const sub of charBudgetSplit(slice, effectiveMax))
|
|
158812
|
+
pushChecked(sub);
|
|
158813
|
+
}
|
|
158814
|
+
}
|
|
158815
|
+
return safe.filter((s) => s.length > 0);
|
|
158816
|
+
}
|
|
158817
|
+
function charBudgetSplit(text, effectiveMax) {
|
|
158818
|
+
const totalTokens = Math.max(1, estimateTokens(text));
|
|
158819
|
+
const charsPerToken = Math.max(1, Math.floor(text.length / totalTokens));
|
|
158820
|
+
const sliceChars = Math.max(1, effectiveMax * charsPerToken);
|
|
158821
|
+
const out = [];
|
|
158822
|
+
let pos = 0;
|
|
158823
|
+
while (pos < text.length) {
|
|
158824
|
+
let end = Math.min(text.length, pos + sliceChars);
|
|
158825
|
+
let slice = text.slice(pos, end);
|
|
158826
|
+
while (slice.length > 1 && estimateTokens(slice) > effectiveMax) {
|
|
158827
|
+
end = pos + Math.max(1, Math.floor((end - pos) / 2));
|
|
158828
|
+
slice = text.slice(pos, end);
|
|
158829
|
+
}
|
|
158830
|
+
out.push(slice);
|
|
158831
|
+
pos = end;
|
|
158832
|
+
}
|
|
158833
|
+
return out;
|
|
158834
|
+
}
|
|
158664
158835
|
function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
|
|
158665
158836
|
const scoped = typeof projectPath === "string" && projectPath.length > 0;
|
|
158666
158837
|
const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
|
|
@@ -158828,6 +158999,8 @@ function countSessionCompartmentEmbedCoverage(db, projectPath, sessionId, modelI
|
|
|
158828
158999
|
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512, CHUNK_WINDOW_SAFETY_RATIO = 0.9, loadFtsRowsStatements, existingHashStatements, existingHashByProjectStatements, deleteByCompartmentStatements, insertEmbeddingStatements, distinctModelStatements, clearProjectStatements, clearProjectModelStatements, searchRowsStatements, searchRowsByModelStatements, backfillCandidateStatements, sessionBackfillCandidateStatements;
|
|
158829
159000
|
var init_compartment_chunk_embedding = __esm(() => {
|
|
158830
159001
|
init_read_session_formatting();
|
|
159002
|
+
init_logger();
|
|
159003
|
+
init_recursive_text_splitter();
|
|
158831
159004
|
loadFtsRowsStatements = new WeakMap;
|
|
158832
159005
|
existingHashStatements = new WeakMap;
|
|
158833
159006
|
existingHashByProjectStatements = new WeakMap;
|
|
@@ -159036,6 +159209,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
159036
159209
|
if (texts.length === 0) {
|
|
159037
159210
|
return [];
|
|
159038
159211
|
}
|
|
159212
|
+
const requestTexts = texts.map((t) => t.trim().length === 0 ? " " : t);
|
|
159039
159213
|
if (!await this.initialize()) {
|
|
159040
159214
|
return Array.from({ length: texts.length }, () => null);
|
|
159041
159215
|
}
|
|
@@ -159067,7 +159241,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
159067
159241
|
},
|
|
159068
159242
|
body: JSON.stringify({
|
|
159069
159243
|
model: this.model,
|
|
159070
|
-
input:
|
|
159244
|
+
input: requestTexts,
|
|
159071
159245
|
...inputTypeForRequest ? { input_type: inputTypeForRequest } : {},
|
|
159072
159246
|
...this.truncate ? { truncate: this.truncate } : {}
|
|
159073
159247
|
}),
|
|
@@ -160149,6 +160323,32 @@ async function embedBatchForProject(projectIdentity, texts, signal, purpose = "p
|
|
|
160149
160323
|
}
|
|
160150
160324
|
return { vectors, modelId, generation };
|
|
160151
160325
|
}
|
|
160326
|
+
async function embedTextsWindowBounded(projectIdentity, texts, signal) {
|
|
160327
|
+
if (texts.length <= MAX_WINDOWS_PER_EMBED_CALL) {
|
|
160328
|
+
return embedBatchForProject(projectIdentity, texts, signal);
|
|
160329
|
+
}
|
|
160330
|
+
const vectors = [];
|
|
160331
|
+
let modelId = null;
|
|
160332
|
+
let generation = null;
|
|
160333
|
+
for (let start = 0;start < texts.length; start += MAX_WINDOWS_PER_EMBED_CALL) {
|
|
160334
|
+
if (signal?.aborted)
|
|
160335
|
+
return null;
|
|
160336
|
+
const sub = texts.slice(start, start + MAX_WINDOWS_PER_EMBED_CALL);
|
|
160337
|
+
const result = await embedBatchForProject(projectIdentity, sub, signal);
|
|
160338
|
+
if (!result)
|
|
160339
|
+
return null;
|
|
160340
|
+
if (modelId === null) {
|
|
160341
|
+
modelId = result.modelId;
|
|
160342
|
+
generation = result.generation;
|
|
160343
|
+
} else if (result.modelId !== modelId || result.generation !== generation) {
|
|
160344
|
+
return null;
|
|
160345
|
+
}
|
|
160346
|
+
vectors.push(...result.vectors);
|
|
160347
|
+
}
|
|
160348
|
+
if (modelId === null || generation === null)
|
|
160349
|
+
return null;
|
|
160350
|
+
return { vectors, modelId, generation };
|
|
160351
|
+
}
|
|
160152
160352
|
function isUnembeddedMemoryRow(row) {
|
|
160153
160353
|
if (row === null || typeof row !== "object")
|
|
160154
160354
|
return false;
|
|
@@ -160282,7 +160482,7 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
|
|
|
160282
160482
|
let result = null;
|
|
160283
160483
|
const attemptStart = Date.now();
|
|
160284
160484
|
try {
|
|
160285
|
-
result = await
|
|
160485
|
+
result = await embedTextsWindowBounded(projectIdentity, texts, signal);
|
|
160286
160486
|
} catch (error51) {
|
|
160287
160487
|
log("[magic-context] failed to proactively embed compartment chunks:", error51);
|
|
160288
160488
|
}
|
|
@@ -176489,11 +176689,11 @@ function shouldShowAnnouncement() {
|
|
|
176489
176689
|
}
|
|
176490
176690
|
return ordering > 0;
|
|
176491
176691
|
}
|
|
176492
|
-
var ANNOUNCEMENT_VERSION = "0.30.
|
|
176692
|
+
var ANNOUNCEMENT_VERSION = "0.30.2", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
|
|
176493
176693
|
var init_announcement = __esm(() => {
|
|
176494
176694
|
init_data_path();
|
|
176495
176695
|
ANNOUNCEMENT_FEATURES = [
|
|
176496
|
-
"
|
|
176696
|
+
"Fixed high idle CPU from the TUI sidebar (#200): it now uses a single persistent connection to the plugin instead of polling, so an idle session no longer burns CPU."
|
|
176497
176697
|
];
|
|
176498
176698
|
});
|
|
176499
176699
|
|
|
@@ -193710,7 +193910,6 @@ function calibrateBuckets(input) {
|
|
|
193710
193910
|
// src/plugin/rpc-handlers.ts
|
|
193711
193911
|
init_announcement();
|
|
193712
193912
|
init_logger();
|
|
193713
|
-
init_rpc_notifications();
|
|
193714
193913
|
var workMetricsCarryBySession = new Map;
|
|
193715
193914
|
function resolveSidebarWorkMetrics(db, sessionId, persistedNewWork, persistedTotalInput) {
|
|
193716
193915
|
if (!openCodeDbExists()) {
|
|
@@ -194222,12 +194421,6 @@ function registerRpcHandlers(rpcServer, args) {
|
|
|
194222
194421
|
const resolved = typeof config2.toast_duration_ms === "number" && Number.isFinite(config2.toast_duration_ms) ? config2.toast_duration_ms : 5000;
|
|
194223
194422
|
return { toastDurationMs: resolved };
|
|
194224
194423
|
});
|
|
194225
|
-
rpcServer.handle("pending-notifications", async (params) => {
|
|
194226
|
-
const lastReceivedId = Number(params.lastReceivedId ?? 0);
|
|
194227
|
-
const sessionId = typeof params.sessionId === "string" && params.sessionId.length > 0 ? params.sessionId : undefined;
|
|
194228
|
-
const notifications = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, sessionId);
|
|
194229
|
-
return { messages: notifications };
|
|
194230
|
-
});
|
|
194231
194424
|
rpcServer.handle("get-announcement", async () => {
|
|
194232
194425
|
if (!shouldShowAnnouncement()) {
|
|
194233
194426
|
return { show: false };
|
|
@@ -195597,6 +195790,7 @@ init_models_dev_cache();
|
|
|
195597
195790
|
|
|
195598
195791
|
// src/shared/rpc-server.ts
|
|
195599
195792
|
init_logger();
|
|
195793
|
+
init_rpc_notifications();
|
|
195600
195794
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
195601
195795
|
import {
|
|
195602
195796
|
chmodSync as chmodSync2,
|
|
@@ -195608,7 +195802,6 @@ import {
|
|
|
195608
195802
|
unlinkSync as unlinkSync4,
|
|
195609
195803
|
writeFileSync as writeFileSync8
|
|
195610
195804
|
} from "node:fs";
|
|
195611
|
-
import { createServer } from "node:http";
|
|
195612
195805
|
import { dirname as dirname5 } from "node:path";
|
|
195613
195806
|
|
|
195614
195807
|
// src/shared/rpc-utils.ts
|
|
@@ -195666,6 +195859,9 @@ function isValidPort(port) {
|
|
|
195666
195859
|
}
|
|
195667
195860
|
|
|
195668
195861
|
// src/shared/rpc-server.ts
|
|
195862
|
+
var MAX_BODY_BYTES = 1048576;
|
|
195863
|
+
var WS_AUTH_TIMEOUT_MS = 5000;
|
|
195864
|
+
var WS_CLOSE_UNAUTHORIZED = 4401;
|
|
195669
195865
|
function tokensMatch(presented, expected) {
|
|
195670
195866
|
const a = Buffer.from(presented, "utf8");
|
|
195671
195867
|
const b = Buffer.from(expected, "utf8");
|
|
@@ -195681,6 +195877,7 @@ class MagicContextRpcServer {
|
|
|
195681
195877
|
portFilePath;
|
|
195682
195878
|
portDir;
|
|
195683
195879
|
startedAt = Date.now();
|
|
195880
|
+
sockets = new Set;
|
|
195684
195881
|
token = randomBytes(32).toString("hex");
|
|
195685
195882
|
constructor(storageDir, directory) {
|
|
195686
195883
|
this.portFilePath = rpcPortFilePath(storageDir, directory);
|
|
@@ -195690,49 +195887,77 @@ class MagicContextRpcServer {
|
|
|
195690
195887
|
this.handlers.set(method, handler);
|
|
195691
195888
|
}
|
|
195692
195889
|
async start() {
|
|
195693
|
-
|
|
195694
|
-
|
|
195695
|
-
|
|
195696
|
-
|
|
195697
|
-
|
|
195698
|
-
|
|
195699
|
-
|
|
195700
|
-
|
|
195701
|
-
|
|
195702
|
-
|
|
195703
|
-
|
|
195704
|
-
|
|
195705
|
-
|
|
195706
|
-
|
|
195707
|
-
|
|
195708
|
-
|
|
195709
|
-
|
|
195710
|
-
|
|
195711
|
-
|
|
195712
|
-
|
|
195713
|
-
|
|
195714
|
-
|
|
195715
|
-
try {
|
|
195716
|
-
rmSync2(tmpPath, { force: true });
|
|
195717
|
-
} catch {}
|
|
195718
|
-
writeFileSync8(tmpPath, JSON.stringify({
|
|
195719
|
-
port: this.port,
|
|
195720
|
-
pid: process.pid,
|
|
195721
|
-
started_at: this.startedAt,
|
|
195722
|
-
token: this.token
|
|
195723
|
-
}), { encoding: "utf-8", mode: 384 });
|
|
195724
|
-
renameSync3(tmpPath, this.portFilePath);
|
|
195725
|
-
try {
|
|
195726
|
-
chmodSync2(this.portFilePath, 384);
|
|
195727
|
-
} catch {}
|
|
195728
|
-
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
195729
|
-
} catch (err) {
|
|
195730
|
-
log(`[rpc] failed to write port file: ${err}`);
|
|
195890
|
+
const self2 = this;
|
|
195891
|
+
const server = Bun.serve({
|
|
195892
|
+
port: 0,
|
|
195893
|
+
hostname: "127.0.0.1",
|
|
195894
|
+
fetch(req, srv) {
|
|
195895
|
+
return self2.handleFetch(req, srv);
|
|
195896
|
+
},
|
|
195897
|
+
websocket: {
|
|
195898
|
+
open(ws) {
|
|
195899
|
+
ws.data.authTimer = setTimeout(() => {
|
|
195900
|
+
if (!ws.data.authed)
|
|
195901
|
+
ws.close(WS_CLOSE_UNAUTHORIZED, "auth timeout");
|
|
195902
|
+
}, WS_AUTH_TIMEOUT_MS);
|
|
195903
|
+
},
|
|
195904
|
+
message(ws, raw) {
|
|
195905
|
+
self2.handleWsMessage(ws, raw);
|
|
195906
|
+
},
|
|
195907
|
+
close(ws) {
|
|
195908
|
+
if (ws.data.authTimer)
|
|
195909
|
+
clearTimeout(ws.data.authTimer);
|
|
195910
|
+
ws.data.unregister?.();
|
|
195911
|
+
self2.sockets.delete(ws);
|
|
195731
195912
|
}
|
|
195732
|
-
|
|
195733
|
-
});
|
|
195734
|
-
server.unref();
|
|
195913
|
+
}
|
|
195735
195914
|
});
|
|
195915
|
+
this.server = server;
|
|
195916
|
+
this.port = server.port ?? 0;
|
|
195917
|
+
try {
|
|
195918
|
+
this.warnIfOtherLiveInstance();
|
|
195919
|
+
const dir = dirname5(this.portFilePath);
|
|
195920
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
195921
|
+
try {
|
|
195922
|
+
chmodSync2(dir, 448);
|
|
195923
|
+
} catch {}
|
|
195924
|
+
const tmpPath = `${this.portFilePath}.tmp`;
|
|
195925
|
+
try {
|
|
195926
|
+
rmSync2(tmpPath, { force: true });
|
|
195927
|
+
} catch {}
|
|
195928
|
+
writeFileSync8(tmpPath, JSON.stringify({
|
|
195929
|
+
port: this.port,
|
|
195930
|
+
pid: process.pid,
|
|
195931
|
+
started_at: this.startedAt,
|
|
195932
|
+
token: this.token
|
|
195933
|
+
}), { encoding: "utf-8", mode: 384 });
|
|
195934
|
+
renameSync3(tmpPath, this.portFilePath);
|
|
195935
|
+
try {
|
|
195936
|
+
chmodSync2(this.portFilePath, 384);
|
|
195937
|
+
} catch {}
|
|
195938
|
+
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
195939
|
+
} catch (err) {
|
|
195940
|
+
log(`[rpc] failed to write port file: ${err}`);
|
|
195941
|
+
}
|
|
195942
|
+
return this.port;
|
|
195943
|
+
}
|
|
195944
|
+
stop() {
|
|
195945
|
+
for (const ws of this.sockets) {
|
|
195946
|
+
try {
|
|
195947
|
+
if (ws.data.authTimer)
|
|
195948
|
+
clearTimeout(ws.data.authTimer);
|
|
195949
|
+
ws.data.unregister?.();
|
|
195950
|
+
ws.close();
|
|
195951
|
+
} catch {}
|
|
195952
|
+
}
|
|
195953
|
+
this.sockets.clear();
|
|
195954
|
+
if (this.server) {
|
|
195955
|
+
this.server.stop(true);
|
|
195956
|
+
this.server = null;
|
|
195957
|
+
}
|
|
195958
|
+
try {
|
|
195959
|
+
unlinkSync4(this.portFilePath);
|
|
195960
|
+
} catch {}
|
|
195736
195961
|
}
|
|
195737
195962
|
warnIfOtherLiveInstance() {
|
|
195738
195963
|
try {
|
|
@@ -195747,73 +195972,99 @@ class MagicContextRpcServer {
|
|
|
195747
195972
|
}
|
|
195748
195973
|
} catch {}
|
|
195749
195974
|
}
|
|
195750
|
-
|
|
195751
|
-
|
|
195752
|
-
|
|
195753
|
-
|
|
195975
|
+
async handleFetch(req, srv) {
|
|
195976
|
+
const url2 = new URL(req.url);
|
|
195977
|
+
if (url2.pathname === "/ws") {
|
|
195978
|
+
const ok = srv.upgrade(req, { data: { authed: false } });
|
|
195979
|
+
if (ok)
|
|
195980
|
+
return;
|
|
195981
|
+
return new Response("upgrade failed", { status: 400 });
|
|
195754
195982
|
}
|
|
195755
|
-
|
|
195756
|
-
|
|
195757
|
-
} catch {}
|
|
195758
|
-
}
|
|
195759
|
-
dispatch(req, res) {
|
|
195760
|
-
const url2 = req.url ?? "";
|
|
195761
|
-
if (req.method === "GET" && url2 === "/health") {
|
|
195762
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
195763
|
-
res.end(JSON.stringify({ ok: true, pid: process.pid }));
|
|
195764
|
-
return;
|
|
195983
|
+
if (req.method === "GET" && url2.pathname === "/health") {
|
|
195984
|
+
return json2({ ok: true, pid: process.pid });
|
|
195765
195985
|
}
|
|
195766
|
-
if (req.method !== "POST" || !url2.startsWith("/rpc/")) {
|
|
195767
|
-
|
|
195768
|
-
res.end("Not Found");
|
|
195769
|
-
return;
|
|
195986
|
+
if (req.method !== "POST" || !url2.pathname.startsWith("/rpc/")) {
|
|
195987
|
+
return new Response("Not Found", { status: 404 });
|
|
195770
195988
|
}
|
|
195771
|
-
const auth = req.headers.authorization;
|
|
195989
|
+
const auth = req.headers.get("authorization");
|
|
195772
195990
|
const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
|
|
195773
195991
|
if (!tokensMatch(presented, this.token)) {
|
|
195774
|
-
|
|
195775
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
195776
|
-
req.resume();
|
|
195777
|
-
return;
|
|
195992
|
+
return json2({ error: "Unauthorized" }, 401);
|
|
195778
195993
|
}
|
|
195779
|
-
const method = url2.slice(5);
|
|
195994
|
+
const method = url2.pathname.slice(5);
|
|
195780
195995
|
const handler = this.handlers.get(method);
|
|
195781
195996
|
if (!handler) {
|
|
195782
|
-
|
|
195783
|
-
res.end(JSON.stringify({ error: `Unknown method: ${method}` }));
|
|
195784
|
-
return;
|
|
195997
|
+
return json2({ error: `Unknown method: ${method}` }, 404);
|
|
195785
195998
|
}
|
|
195786
|
-
|
|
195787
|
-
|
|
195788
|
-
|
|
195789
|
-
|
|
195790
|
-
|
|
195791
|
-
|
|
195792
|
-
req.destroy();
|
|
195793
|
-
}
|
|
195794
|
-
});
|
|
195795
|
-
req.on("end", () => {
|
|
195796
|
-
let params = {};
|
|
195999
|
+
const bodyText = await req.text();
|
|
196000
|
+
if (bodyText.length > MAX_BODY_BYTES) {
|
|
196001
|
+
return new Response("Request too large", { status: 413 });
|
|
196002
|
+
}
|
|
196003
|
+
let params = {};
|
|
196004
|
+
if (bodyText.length > 0) {
|
|
195797
196005
|
try {
|
|
195798
|
-
|
|
195799
|
-
params = JSON.parse(body);
|
|
195800
|
-
}
|
|
196006
|
+
params = JSON.parse(bodyText);
|
|
195801
196007
|
} catch {
|
|
195802
|
-
|
|
195803
|
-
|
|
196008
|
+
return json2({ error: "Invalid JSON" }, 400);
|
|
196009
|
+
}
|
|
196010
|
+
}
|
|
196011
|
+
try {
|
|
196012
|
+
const result = await handler(params);
|
|
196013
|
+
return json2(result);
|
|
196014
|
+
} catch (err) {
|
|
196015
|
+
log(`[rpc] handler error: ${method} => ${err}`);
|
|
196016
|
+
return json2({ error: String(err) }, 500);
|
|
196017
|
+
}
|
|
196018
|
+
}
|
|
196019
|
+
handleWsMessage(ws, raw) {
|
|
196020
|
+
let msg;
|
|
196021
|
+
try {
|
|
196022
|
+
msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
196023
|
+
} catch {
|
|
196024
|
+
return;
|
|
196025
|
+
}
|
|
196026
|
+
if (msg.type === "hello") {
|
|
196027
|
+
if (!tokensMatch(typeof msg.token === "string" ? msg.token : "", this.token)) {
|
|
196028
|
+
ws.send(JSON.stringify({ type: "error", error: "unauthorized" }));
|
|
196029
|
+
ws.close(WS_CLOSE_UNAUTHORIZED, "bad token");
|
|
195804
196030
|
return;
|
|
195805
196031
|
}
|
|
195806
|
-
|
|
195807
|
-
|
|
195808
|
-
|
|
195809
|
-
}
|
|
195810
|
-
|
|
195811
|
-
|
|
195812
|
-
|
|
195813
|
-
|
|
195814
|
-
|
|
196032
|
+
if (ws.data.authTimer) {
|
|
196033
|
+
clearTimeout(ws.data.authTimer);
|
|
196034
|
+
ws.data.authTimer = undefined;
|
|
196035
|
+
}
|
|
196036
|
+
ws.data.authed = true;
|
|
196037
|
+
ws.data.sessionId = typeof msg.sessionId === "string" && msg.sessionId.length > 0 ? msg.sessionId : undefined;
|
|
196038
|
+
const sink = {
|
|
196039
|
+
sessionId: ws.data.sessionId,
|
|
196040
|
+
send: (notification) => {
|
|
196041
|
+
ws.send(JSON.stringify({ type: "notification", notification }));
|
|
196042
|
+
}
|
|
196043
|
+
};
|
|
196044
|
+
ws.data.unregister = registerNotificationSink(sink);
|
|
196045
|
+
this.sockets.add(ws);
|
|
196046
|
+
const lastReceivedId = Number(msg.lastReceivedId ?? 0);
|
|
196047
|
+
const backlog = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, ws.data.sessionId);
|
|
196048
|
+
for (const notification of backlog) {
|
|
196049
|
+
ws.send(JSON.stringify({ type: "notification", notification }));
|
|
196050
|
+
}
|
|
196051
|
+
ws.send(JSON.stringify({ type: "hello-ack" }));
|
|
196052
|
+
return;
|
|
196053
|
+
}
|
|
196054
|
+
if (msg.type === "ack") {
|
|
196055
|
+
const lastReceivedId = Number(msg.lastReceivedId ?? 0);
|
|
196056
|
+
if (Number.isFinite(lastReceivedId) && lastReceivedId > 0) {
|
|
196057
|
+
drainNotifications(lastReceivedId, ws.data.sessionId);
|
|
196058
|
+
}
|
|
196059
|
+
}
|
|
195815
196060
|
}
|
|
195816
196061
|
}
|
|
196062
|
+
function json2(body, status = 200) {
|
|
196063
|
+
return new Response(JSON.stringify(body), {
|
|
196064
|
+
status,
|
|
196065
|
+
headers: { "Content-Type": "application/json" }
|
|
196066
|
+
});
|
|
196067
|
+
}
|
|
195817
196068
|
|
|
195818
196069
|
// src/index.ts
|
|
195819
196070
|
var server = async (ctx) => {
|
|
@@ -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;AAIzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAc3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,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;AAoBlF,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,CA4ON"}
|
|
@@ -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.30.
|
|
21
|
+
export declare const ANNOUNCEMENT_VERSION = "0.30.2";
|
|
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":"data-path.d.ts","sourceRoot":"","sources":["../../src/shared/data-path.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAEvD,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,SAAwB,GAAG,MAAM,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,SAAwB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"data-path.d.ts","sourceRoot":"","sources":["../../src/shared/data-path.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAEvD,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,SAAwB,GAAG,MAAM,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,SAAwB,GAAG,MAAM,CAOhF;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,SAAwB,GAAG,MAAM,CAErF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAkBxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kCAAkC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,uCAAuC,IAAI,MAAM,CAEhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
|