@wolfx/pi-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/index.js +229 -28
- package/dist/subagent-entry.js +183 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -70,6 +70,9 @@ function getMagicContextTempDir(harness = getHarness()) {
|
|
|
70
70
|
return path2.join(os.tmpdir(), harness, "magic-context");
|
|
71
71
|
}
|
|
72
72
|
function getMagicContextLogPath(harness = getHarness()) {
|
|
73
|
+
const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
|
|
74
|
+
if (envPath)
|
|
75
|
+
return envPath;
|
|
73
76
|
return path2.join(getMagicContextTempDir(harness), "magic-context.log");
|
|
74
77
|
}
|
|
75
78
|
function getProjectMagicContextDir(directory) {
|
|
@@ -14945,12 +14948,30 @@ var require_src2 = __commonJS((exports, module) => {
|
|
|
14945
14948
|
// ../plugin/src/shared/rpc-notifications.ts
|
|
14946
14949
|
var exports_rpc_notifications = {};
|
|
14947
14950
|
__export(exports_rpc_notifications, {
|
|
14951
|
+
registerNotificationSink: () => registerNotificationSink,
|
|
14948
14952
|
pushNotification: () => pushNotification,
|
|
14949
14953
|
isTuiConnected: () => isTuiConnected,
|
|
14950
14954
|
drainNotifications: () => drainNotifications
|
|
14951
14955
|
});
|
|
14956
|
+
function registerNotificationSink(sink) {
|
|
14957
|
+
sinks.add(sink);
|
|
14958
|
+
return () => {
|
|
14959
|
+
sinks.delete(sink);
|
|
14960
|
+
};
|
|
14961
|
+
}
|
|
14962
|
+
function notificationMatchesSink(notification, sink) {
|
|
14963
|
+
return notification.sessionId === undefined || sink.sessionId === undefined || notification.sessionId === sink.sessionId;
|
|
14964
|
+
}
|
|
14952
14965
|
function pushNotification(type, payload, sessionId) {
|
|
14953
|
-
|
|
14966
|
+
const notification = { id: nextNotificationId++, type, payload, sessionId };
|
|
14967
|
+
queue.push(notification);
|
|
14968
|
+
for (const sink of sinks) {
|
|
14969
|
+
if (!notificationMatchesSink(notification, sink))
|
|
14970
|
+
continue;
|
|
14971
|
+
try {
|
|
14972
|
+
sink.send(notification);
|
|
14973
|
+
} catch {}
|
|
14974
|
+
}
|
|
14954
14975
|
if (queue.length > 100) {
|
|
14955
14976
|
const newestPerSession = new Map;
|
|
14956
14977
|
for (const n of queue) {
|
|
@@ -14970,10 +14991,6 @@ function pushNotification(type, payload, sessionId) {
|
|
|
14970
14991
|
}
|
|
14971
14992
|
}
|
|
14972
14993
|
function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
14973
|
-
const now = Date.now();
|
|
14974
|
-
lastDrainAtAny = now;
|
|
14975
|
-
if (sessionId !== undefined)
|
|
14976
|
-
lastDrainAtBySession.set(sessionId, now);
|
|
14977
14994
|
const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
|
|
14978
14995
|
if (lastReceivedId > 0) {
|
|
14979
14996
|
queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
|
|
@@ -14981,17 +14998,20 @@ function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
|
14981
14998
|
return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
|
|
14982
14999
|
}
|
|
14983
15000
|
function isTuiConnected(sessionId) {
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
return
|
|
15001
|
+
if (sinks.size === 0)
|
|
15002
|
+
return false;
|
|
15003
|
+
if (sessionId === undefined)
|
|
15004
|
+
return true;
|
|
15005
|
+
for (const sink of sinks) {
|
|
15006
|
+
if (sink.sessionId === undefined || sink.sessionId === sessionId)
|
|
15007
|
+
return true;
|
|
14988
15008
|
}
|
|
14989
|
-
return
|
|
15009
|
+
return false;
|
|
14990
15010
|
}
|
|
14991
|
-
var queue, nextNotificationId = 1,
|
|
15011
|
+
var queue, nextNotificationId = 1, sinks;
|
|
14992
15012
|
var init_rpc_notifications = __esm(() => {
|
|
14993
15013
|
queue = [];
|
|
14994
|
-
|
|
15014
|
+
sinks = new Set;
|
|
14995
15015
|
});
|
|
14996
15016
|
|
|
14997
15017
|
// ../plugin/src/shared/safe-notification-target.ts
|
|
@@ -156461,9 +156481,9 @@ function compareSemverCore(a, b) {
|
|
|
156461
156481
|
const [b0, b1, b2] = core(b);
|
|
156462
156482
|
return a0 - b0 || a1 - b1 || a2 - b2;
|
|
156463
156483
|
}
|
|
156464
|
-
var ANNOUNCEMENT_VERSION = "0.30.
|
|
156484
|
+
var ANNOUNCEMENT_VERSION = "0.30.2";
|
|
156465
156485
|
var ANNOUNCEMENT_FEATURES = [
|
|
156466
|
-
"
|
|
156486
|
+
"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."
|
|
156467
156487
|
];
|
|
156468
156488
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
|
|
156469
156489
|
var STATE_FILENAME = "last_announced_version";
|
|
@@ -157984,6 +158004,94 @@ init_logger();
|
|
|
157984
158004
|
|
|
157985
158005
|
// ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
|
|
157986
158006
|
import { createHash as createHash6 } from "node:crypto";
|
|
158007
|
+
init_logger();
|
|
158008
|
+
|
|
158009
|
+
// ../plugin/src/features/magic-context/recursive-text-splitter.ts
|
|
158010
|
+
var DEFAULT_SEPARATORS = [`
|
|
158011
|
+
|
|
158012
|
+
`, `
|
|
158013
|
+
`, " ", ""];
|
|
158014
|
+
function splitOnSeparator(text, separator) {
|
|
158015
|
+
const splits = separator ? text.split(separator) : text.split("");
|
|
158016
|
+
return splits.filter((s) => s !== "");
|
|
158017
|
+
}
|
|
158018
|
+
function mergeSplits(splits, separator, chunkSize, lengthFunction) {
|
|
158019
|
+
const docs = [];
|
|
158020
|
+
const currentDoc = [];
|
|
158021
|
+
let total = 0;
|
|
158022
|
+
const joinDocs = (docsToJoin) => {
|
|
158023
|
+
const joined = docsToJoin.join(separator).trim();
|
|
158024
|
+
return joined === "" ? null : joined;
|
|
158025
|
+
};
|
|
158026
|
+
for (const d of splits) {
|
|
158027
|
+
const len = lengthFunction(d);
|
|
158028
|
+
if (total + len + currentDoc.length * separator.length > chunkSize) {
|
|
158029
|
+
if (currentDoc.length > 0) {
|
|
158030
|
+
const doc2 = joinDocs(currentDoc);
|
|
158031
|
+
if (doc2 !== null)
|
|
158032
|
+
docs.push(doc2);
|
|
158033
|
+
while (total > 0 && currentDoc.length > 0) {
|
|
158034
|
+
total -= lengthFunction(currentDoc[0]);
|
|
158035
|
+
currentDoc.shift();
|
|
158036
|
+
}
|
|
158037
|
+
}
|
|
158038
|
+
}
|
|
158039
|
+
currentDoc.push(d);
|
|
158040
|
+
total += len;
|
|
158041
|
+
}
|
|
158042
|
+
const doc = joinDocs(currentDoc);
|
|
158043
|
+
if (doc !== null)
|
|
158044
|
+
docs.push(doc);
|
|
158045
|
+
return docs;
|
|
158046
|
+
}
|
|
158047
|
+
function splitTextRecursive(text, separators, chunkSize, lengthFunction) {
|
|
158048
|
+
const finalChunks = [];
|
|
158049
|
+
let separator = separators[separators.length - 1];
|
|
158050
|
+
let newSeparators;
|
|
158051
|
+
for (let i = 0;i < separators.length; i += 1) {
|
|
158052
|
+
const s = separators[i];
|
|
158053
|
+
if (s === "") {
|
|
158054
|
+
separator = s;
|
|
158055
|
+
break;
|
|
158056
|
+
}
|
|
158057
|
+
if (text.includes(s)) {
|
|
158058
|
+
separator = s;
|
|
158059
|
+
newSeparators = separators.slice(i + 1);
|
|
158060
|
+
break;
|
|
158061
|
+
}
|
|
158062
|
+
}
|
|
158063
|
+
const splits = splitOnSeparator(text, separator);
|
|
158064
|
+
let goodSplits = [];
|
|
158065
|
+
for (const s of splits) {
|
|
158066
|
+
if (lengthFunction(s) < chunkSize) {
|
|
158067
|
+
goodSplits.push(s);
|
|
158068
|
+
} else {
|
|
158069
|
+
if (goodSplits.length) {
|
|
158070
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
158071
|
+
goodSplits = [];
|
|
158072
|
+
}
|
|
158073
|
+
if (!newSeparators) {
|
|
158074
|
+
finalChunks.push(s);
|
|
158075
|
+
} else {
|
|
158076
|
+
finalChunks.push(...splitTextRecursive(s, newSeparators, chunkSize, lengthFunction));
|
|
158077
|
+
}
|
|
158078
|
+
}
|
|
158079
|
+
}
|
|
158080
|
+
if (goodSplits.length) {
|
|
158081
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
158082
|
+
}
|
|
158083
|
+
return finalChunks;
|
|
158084
|
+
}
|
|
158085
|
+
function recursiveCharacterSplit(text, options) {
|
|
158086
|
+
const chunkSize = options.chunkSize;
|
|
158087
|
+
const lengthFunction = options.lengthFunction ?? ((t) => t.length);
|
|
158088
|
+
const separators = options.separators ?? DEFAULT_SEPARATORS;
|
|
158089
|
+
if (text.length === 0)
|
|
158090
|
+
return [];
|
|
158091
|
+
return splitTextRecursive(text, separators, chunkSize, lengthFunction);
|
|
158092
|
+
}
|
|
158093
|
+
|
|
158094
|
+
// ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
|
|
157987
158095
|
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
|
|
157988
158096
|
var CHUNK_WINDOW_SAFETY_RATIO = 0.9;
|
|
157989
158097
|
var loadFtsRowsStatements = new WeakMap;
|
|
@@ -158262,6 +158370,19 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
158262
158370
|
const lineStart = range?.start ?? startOrdinal;
|
|
158263
158371
|
const lineEnd = range?.end ?? lineStart;
|
|
158264
158372
|
const lineTokens = estimateTokens(line);
|
|
158373
|
+
if (lineTokens > effectiveMax) {
|
|
158374
|
+
flush2();
|
|
158375
|
+
for (const slice of splitOversizedLine(line, effectiveMax)) {
|
|
158376
|
+
windows.push({
|
|
158377
|
+
windowIndex: windows.length + 1,
|
|
158378
|
+
startOrdinal: lineStart,
|
|
158379
|
+
endOrdinal: lineEnd,
|
|
158380
|
+
text: slice,
|
|
158381
|
+
chunkHash: hashChunkText(slice)
|
|
158382
|
+
});
|
|
158383
|
+
}
|
|
158384
|
+
continue;
|
|
158385
|
+
}
|
|
158265
158386
|
if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
|
|
158266
158387
|
flush2();
|
|
158267
158388
|
}
|
|
@@ -158275,6 +158396,56 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
158275
158396
|
flush2();
|
|
158276
158397
|
return windows;
|
|
158277
158398
|
}
|
|
158399
|
+
function splitOversizedLine(line, effectiveMax) {
|
|
158400
|
+
let slices = [];
|
|
158401
|
+
try {
|
|
158402
|
+
slices = recursiveCharacterSplit(line, {
|
|
158403
|
+
chunkSize: effectiveMax,
|
|
158404
|
+
lengthFunction: estimateTokens
|
|
158405
|
+
});
|
|
158406
|
+
} catch (error) {
|
|
158407
|
+
log("[magic-context] recursiveCharacterSplit failed; using char-budget fallback:", error);
|
|
158408
|
+
slices = [];
|
|
158409
|
+
}
|
|
158410
|
+
if (slices.length === 0) {
|
|
158411
|
+
slices = charBudgetSplit(line, effectiveMax);
|
|
158412
|
+
}
|
|
158413
|
+
const safe = [];
|
|
158414
|
+
const pushChecked = (slice) => {
|
|
158415
|
+
if (estimateTokens(slice) > effectiveMax && slice.length > 1) {
|
|
158416
|
+
safe.push(...charBudgetSplit(slice, effectiveMax));
|
|
158417
|
+
return;
|
|
158418
|
+
}
|
|
158419
|
+
safe.push(slice);
|
|
158420
|
+
};
|
|
158421
|
+
for (const slice of slices) {
|
|
158422
|
+
if (estimateTokens(slice) <= effectiveMax) {
|
|
158423
|
+
safe.push(slice);
|
|
158424
|
+
} else {
|
|
158425
|
+
for (const sub of charBudgetSplit(slice, effectiveMax))
|
|
158426
|
+
pushChecked(sub);
|
|
158427
|
+
}
|
|
158428
|
+
}
|
|
158429
|
+
return safe.filter((s) => s.length > 0);
|
|
158430
|
+
}
|
|
158431
|
+
function charBudgetSplit(text, effectiveMax) {
|
|
158432
|
+
const totalTokens = Math.max(1, estimateTokens(text));
|
|
158433
|
+
const charsPerToken = Math.max(1, Math.floor(text.length / totalTokens));
|
|
158434
|
+
const sliceChars = Math.max(1, effectiveMax * charsPerToken);
|
|
158435
|
+
const out = [];
|
|
158436
|
+
let pos = 0;
|
|
158437
|
+
while (pos < text.length) {
|
|
158438
|
+
let end = Math.min(text.length, pos + sliceChars);
|
|
158439
|
+
let slice = text.slice(pos, end);
|
|
158440
|
+
while (slice.length > 1 && estimateTokens(slice) > effectiveMax) {
|
|
158441
|
+
end = pos + Math.max(1, Math.floor((end - pos) / 2));
|
|
158442
|
+
slice = text.slice(pos, end);
|
|
158443
|
+
}
|
|
158444
|
+
out.push(slice);
|
|
158445
|
+
pos = end;
|
|
158446
|
+
}
|
|
158447
|
+
return out;
|
|
158448
|
+
}
|
|
158278
158449
|
function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
|
|
158279
158450
|
const scoped = typeof projectPath === "string" && projectPath.length > 0;
|
|
158280
158451
|
const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
|
|
@@ -158635,6 +158806,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
158635
158806
|
if (texts.length === 0) {
|
|
158636
158807
|
return [];
|
|
158637
158808
|
}
|
|
158809
|
+
const requestTexts = texts.map((t) => t.trim().length === 0 ? " " : t);
|
|
158638
158810
|
if (!await this.initialize()) {
|
|
158639
158811
|
return Array.from({ length: texts.length }, () => null);
|
|
158640
158812
|
}
|
|
@@ -158666,7 +158838,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
158666
158838
|
},
|
|
158667
158839
|
body: JSON.stringify({
|
|
158668
158840
|
model: this.model,
|
|
158669
|
-
input:
|
|
158841
|
+
input: requestTexts,
|
|
158670
158842
|
...inputTypeForRequest ? { input_type: inputTypeForRequest } : {},
|
|
158671
158843
|
...this.truncate ? { truncate: this.truncate } : {}
|
|
158672
158844
|
}),
|
|
@@ -159747,6 +159919,32 @@ async function embedBatchForProject(projectIdentity, texts, signal, purpose = "p
|
|
|
159747
159919
|
}
|
|
159748
159920
|
return { vectors, modelId, generation };
|
|
159749
159921
|
}
|
|
159922
|
+
async function embedTextsWindowBounded(projectIdentity, texts, signal) {
|
|
159923
|
+
if (texts.length <= MAX_WINDOWS_PER_EMBED_CALL) {
|
|
159924
|
+
return embedBatchForProject(projectIdentity, texts, signal);
|
|
159925
|
+
}
|
|
159926
|
+
const vectors = [];
|
|
159927
|
+
let modelId = null;
|
|
159928
|
+
let generation = null;
|
|
159929
|
+
for (let start = 0;start < texts.length; start += MAX_WINDOWS_PER_EMBED_CALL) {
|
|
159930
|
+
if (signal?.aborted)
|
|
159931
|
+
return null;
|
|
159932
|
+
const sub = texts.slice(start, start + MAX_WINDOWS_PER_EMBED_CALL);
|
|
159933
|
+
const result = await embedBatchForProject(projectIdentity, sub, signal);
|
|
159934
|
+
if (!result)
|
|
159935
|
+
return null;
|
|
159936
|
+
if (modelId === null) {
|
|
159937
|
+
modelId = result.modelId;
|
|
159938
|
+
generation = result.generation;
|
|
159939
|
+
} else if (result.modelId !== modelId || result.generation !== generation) {
|
|
159940
|
+
return null;
|
|
159941
|
+
}
|
|
159942
|
+
vectors.push(...result.vectors);
|
|
159943
|
+
}
|
|
159944
|
+
if (modelId === null || generation === null)
|
|
159945
|
+
return null;
|
|
159946
|
+
return { vectors, modelId, generation };
|
|
159947
|
+
}
|
|
159750
159948
|
function isUnembeddedMemoryRow(row) {
|
|
159751
159949
|
if (row === null || typeof row !== "object")
|
|
159752
159950
|
return false;
|
|
@@ -159880,7 +160078,7 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
|
|
|
159880
160078
|
let result = null;
|
|
159881
160079
|
const attemptStart = Date.now();
|
|
159882
160080
|
try {
|
|
159883
|
-
result = await
|
|
160081
|
+
result = await embedTextsWindowBounded(projectIdentity, texts, signal);
|
|
159884
160082
|
} catch (error) {
|
|
159885
160083
|
log("[magic-context] failed to proactively embed compartment chunks:", error);
|
|
159886
160084
|
}
|
|
@@ -183149,6 +183347,7 @@ async function loadDefaultPiSessionDeps() {
|
|
|
183149
183347
|
}
|
|
183150
183348
|
|
|
183151
183349
|
// src/dreamer/retrospective-raw-provider-pi.ts
|
|
183350
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
183152
183351
|
import { resolve as resolve2 } from "node:path";
|
|
183153
183352
|
var PI_CODING_AGENT_MODULE2 = "@earendil-works/pi-coding-agent";
|
|
183154
183353
|
|
|
@@ -183177,7 +183376,7 @@ class PiRetrospectiveRawProvider {
|
|
|
183177
183376
|
result.push({
|
|
183178
183377
|
sessionId: info.id,
|
|
183179
183378
|
path: info.path,
|
|
183180
|
-
updatedAt: typeof info.modified === "number" ? info.modified :
|
|
183379
|
+
updatedAt: typeof info.modified === "number" ? info.modified : undefined
|
|
183181
183380
|
});
|
|
183182
183381
|
}
|
|
183183
183382
|
return result.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
|
|
@@ -183256,15 +183455,17 @@ function extractPiTextContent(content) {
|
|
|
183256
183455
|
}
|
|
183257
183456
|
async function loadDefaultPiSessionDeps2() {
|
|
183258
183457
|
const mod = await import(PI_CODING_AGENT_MODULE2);
|
|
183259
|
-
const
|
|
183260
|
-
|
|
183261
|
-
|
|
183262
|
-
throw new Error("Pi session APIs unavailable: expected SessionManager.listAll and parseSessionEntries");
|
|
183458
|
+
const listSessions = mod.SessionManager?.listAll;
|
|
183459
|
+
if (typeof listSessions !== "function") {
|
|
183460
|
+
throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
|
|
183263
183461
|
}
|
|
183264
|
-
const
|
|
183462
|
+
const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
|
|
183463
|
+
const content = readFileSync9(filePath, "utf8");
|
|
183464
|
+
return mod.parseSessionEntries?.(content) ?? [];
|
|
183465
|
+
});
|
|
183265
183466
|
return {
|
|
183266
|
-
listSessions:
|
|
183267
|
-
loadEntriesFromFile
|
|
183467
|
+
listSessions: listSessions.bind(mod.SessionManager),
|
|
183468
|
+
loadEntriesFromFile
|
|
183268
183469
|
};
|
|
183269
183470
|
}
|
|
183270
183471
|
|
|
@@ -199413,7 +199614,7 @@ function formatThresholdPercent(value) {
|
|
|
199413
199614
|
// package.json
|
|
199414
199615
|
var package_default = {
|
|
199415
199616
|
name: "@wolfx/pi-magic-context",
|
|
199416
|
-
version: "0.30.
|
|
199617
|
+
version: "0.30.3",
|
|
199417
199618
|
type: "module",
|
|
199418
199619
|
description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
|
|
199419
199620
|
main: "dist/index.js",
|
|
@@ -199439,7 +199640,7 @@ var package_default = {
|
|
|
199439
199640
|
"README.md"
|
|
199440
199641
|
],
|
|
199441
199642
|
scripts: {
|
|
199442
|
-
build: "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external node:sqlite",
|
|
199643
|
+
build: "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
|
|
199443
199644
|
typecheck: "tsc --noEmit",
|
|
199444
199645
|
test: "bun test",
|
|
199445
199646
|
lint: "biome check src",
|
|
@@ -199468,8 +199669,8 @@ var package_default = {
|
|
|
199468
199669
|
typescript: "^5.8.0"
|
|
199469
199670
|
},
|
|
199470
199671
|
peerDependencies: {
|
|
199471
|
-
"@earendil-works/pi-coding-agent": "
|
|
199472
|
-
"@earendil-works/pi-tui": "
|
|
199672
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
199673
|
+
"@earendil-works/pi-tui": "^0.80.2"
|
|
199473
199674
|
},
|
|
199474
199675
|
exports: {
|
|
199475
199676
|
".": {
|
package/dist/subagent-entry.js
CHANGED
|
@@ -70,6 +70,9 @@ function getMagicContextTempDir(harness = getHarness()) {
|
|
|
70
70
|
return path.join(os.tmpdir(), harness, "magic-context");
|
|
71
71
|
}
|
|
72
72
|
function getMagicContextLogPath(harness = getHarness()) {
|
|
73
|
+
const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
|
|
74
|
+
if (envPath)
|
|
75
|
+
return envPath;
|
|
73
76
|
return path.join(getMagicContextTempDir(harness), "magic-context.log");
|
|
74
77
|
}
|
|
75
78
|
function getProjectMagicContextDir(directory) {
|
|
@@ -160280,6 +160283,94 @@ init_logger();
|
|
|
160280
160283
|
|
|
160281
160284
|
// ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
|
|
160282
160285
|
import { createHash as createHash3 } from "node:crypto";
|
|
160286
|
+
init_logger();
|
|
160287
|
+
|
|
160288
|
+
// ../plugin/src/features/magic-context/recursive-text-splitter.ts
|
|
160289
|
+
var DEFAULT_SEPARATORS = [`
|
|
160290
|
+
|
|
160291
|
+
`, `
|
|
160292
|
+
`, " ", ""];
|
|
160293
|
+
function splitOnSeparator(text, separator) {
|
|
160294
|
+
const splits = separator ? text.split(separator) : text.split("");
|
|
160295
|
+
return splits.filter((s) => s !== "");
|
|
160296
|
+
}
|
|
160297
|
+
function mergeSplits(splits, separator, chunkSize, lengthFunction) {
|
|
160298
|
+
const docs = [];
|
|
160299
|
+
const currentDoc = [];
|
|
160300
|
+
let total = 0;
|
|
160301
|
+
const joinDocs = (docsToJoin) => {
|
|
160302
|
+
const joined = docsToJoin.join(separator).trim();
|
|
160303
|
+
return joined === "" ? null : joined;
|
|
160304
|
+
};
|
|
160305
|
+
for (const d of splits) {
|
|
160306
|
+
const len = lengthFunction(d);
|
|
160307
|
+
if (total + len + currentDoc.length * separator.length > chunkSize) {
|
|
160308
|
+
if (currentDoc.length > 0) {
|
|
160309
|
+
const doc3 = joinDocs(currentDoc);
|
|
160310
|
+
if (doc3 !== null)
|
|
160311
|
+
docs.push(doc3);
|
|
160312
|
+
while (total > 0 && currentDoc.length > 0) {
|
|
160313
|
+
total -= lengthFunction(currentDoc[0]);
|
|
160314
|
+
currentDoc.shift();
|
|
160315
|
+
}
|
|
160316
|
+
}
|
|
160317
|
+
}
|
|
160318
|
+
currentDoc.push(d);
|
|
160319
|
+
total += len;
|
|
160320
|
+
}
|
|
160321
|
+
const doc2 = joinDocs(currentDoc);
|
|
160322
|
+
if (doc2 !== null)
|
|
160323
|
+
docs.push(doc2);
|
|
160324
|
+
return docs;
|
|
160325
|
+
}
|
|
160326
|
+
function splitTextRecursive(text, separators, chunkSize, lengthFunction) {
|
|
160327
|
+
const finalChunks = [];
|
|
160328
|
+
let separator = separators[separators.length - 1];
|
|
160329
|
+
let newSeparators;
|
|
160330
|
+
for (let i = 0;i < separators.length; i += 1) {
|
|
160331
|
+
const s = separators[i];
|
|
160332
|
+
if (s === "") {
|
|
160333
|
+
separator = s;
|
|
160334
|
+
break;
|
|
160335
|
+
}
|
|
160336
|
+
if (text.includes(s)) {
|
|
160337
|
+
separator = s;
|
|
160338
|
+
newSeparators = separators.slice(i + 1);
|
|
160339
|
+
break;
|
|
160340
|
+
}
|
|
160341
|
+
}
|
|
160342
|
+
const splits = splitOnSeparator(text, separator);
|
|
160343
|
+
let goodSplits = [];
|
|
160344
|
+
for (const s of splits) {
|
|
160345
|
+
if (lengthFunction(s) < chunkSize) {
|
|
160346
|
+
goodSplits.push(s);
|
|
160347
|
+
} else {
|
|
160348
|
+
if (goodSplits.length) {
|
|
160349
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
160350
|
+
goodSplits = [];
|
|
160351
|
+
}
|
|
160352
|
+
if (!newSeparators) {
|
|
160353
|
+
finalChunks.push(s);
|
|
160354
|
+
} else {
|
|
160355
|
+
finalChunks.push(...splitTextRecursive(s, newSeparators, chunkSize, lengthFunction));
|
|
160356
|
+
}
|
|
160357
|
+
}
|
|
160358
|
+
}
|
|
160359
|
+
if (goodSplits.length) {
|
|
160360
|
+
finalChunks.push(...mergeSplits(goodSplits, separator, chunkSize, lengthFunction));
|
|
160361
|
+
}
|
|
160362
|
+
return finalChunks;
|
|
160363
|
+
}
|
|
160364
|
+
function recursiveCharacterSplit(text, options) {
|
|
160365
|
+
const chunkSize = options.chunkSize;
|
|
160366
|
+
const lengthFunction = options.lengthFunction ?? ((t) => t.length);
|
|
160367
|
+
const separators = options.separators ?? DEFAULT_SEPARATORS;
|
|
160368
|
+
if (text.length === 0)
|
|
160369
|
+
return [];
|
|
160370
|
+
return splitTextRecursive(text, separators, chunkSize, lengthFunction);
|
|
160371
|
+
}
|
|
160372
|
+
|
|
160373
|
+
// ../plugin/src/features/magic-context/compartment-chunk-embedding.ts
|
|
160283
160374
|
var DEFAULT_COMPARTMENT_CHUNK_MAX_INPUT_TOKENS = 512;
|
|
160284
160375
|
var CHUNK_WINDOW_SAFETY_RATIO = 0.9;
|
|
160285
160376
|
var loadFtsRowsStatements = new WeakMap;
|
|
@@ -160558,6 +160649,19 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
160558
160649
|
const lineStart = range?.start ?? startOrdinal;
|
|
160559
160650
|
const lineEnd = range?.end ?? lineStart;
|
|
160560
160651
|
const lineTokens = estimateTokens(line);
|
|
160652
|
+
if (lineTokens > effectiveMax) {
|
|
160653
|
+
flush2();
|
|
160654
|
+
for (const slice of splitOversizedLine(line, effectiveMax)) {
|
|
160655
|
+
windows.push({
|
|
160656
|
+
windowIndex: windows.length + 1,
|
|
160657
|
+
startOrdinal: lineStart,
|
|
160658
|
+
endOrdinal: lineEnd,
|
|
160659
|
+
text: slice,
|
|
160660
|
+
chunkHash: hashChunkText(slice)
|
|
160661
|
+
});
|
|
160662
|
+
}
|
|
160663
|
+
continue;
|
|
160664
|
+
}
|
|
160561
160665
|
if (currentLines.length > 0 && currentTokens + lineTokens > effectiveMax) {
|
|
160562
160666
|
flush2();
|
|
160563
160667
|
}
|
|
@@ -160571,6 +160675,56 @@ function chunkCanonicalText(canonicalText, startOrdinal, endOrdinal, maxInputTok
|
|
|
160571
160675
|
flush2();
|
|
160572
160676
|
return windows;
|
|
160573
160677
|
}
|
|
160678
|
+
function splitOversizedLine(line, effectiveMax) {
|
|
160679
|
+
let slices = [];
|
|
160680
|
+
try {
|
|
160681
|
+
slices = recursiveCharacterSplit(line, {
|
|
160682
|
+
chunkSize: effectiveMax,
|
|
160683
|
+
lengthFunction: estimateTokens
|
|
160684
|
+
});
|
|
160685
|
+
} catch (error51) {
|
|
160686
|
+
log("[magic-context] recursiveCharacterSplit failed; using char-budget fallback:", error51);
|
|
160687
|
+
slices = [];
|
|
160688
|
+
}
|
|
160689
|
+
if (slices.length === 0) {
|
|
160690
|
+
slices = charBudgetSplit(line, effectiveMax);
|
|
160691
|
+
}
|
|
160692
|
+
const safe = [];
|
|
160693
|
+
const pushChecked = (slice) => {
|
|
160694
|
+
if (estimateTokens(slice) > effectiveMax && slice.length > 1) {
|
|
160695
|
+
safe.push(...charBudgetSplit(slice, effectiveMax));
|
|
160696
|
+
return;
|
|
160697
|
+
}
|
|
160698
|
+
safe.push(slice);
|
|
160699
|
+
};
|
|
160700
|
+
for (const slice of slices) {
|
|
160701
|
+
if (estimateTokens(slice) <= effectiveMax) {
|
|
160702
|
+
safe.push(slice);
|
|
160703
|
+
} else {
|
|
160704
|
+
for (const sub of charBudgetSplit(slice, effectiveMax))
|
|
160705
|
+
pushChecked(sub);
|
|
160706
|
+
}
|
|
160707
|
+
}
|
|
160708
|
+
return safe.filter((s) => s.length > 0);
|
|
160709
|
+
}
|
|
160710
|
+
function charBudgetSplit(text, effectiveMax) {
|
|
160711
|
+
const totalTokens = Math.max(1, estimateTokens(text));
|
|
160712
|
+
const charsPerToken = Math.max(1, Math.floor(text.length / totalTokens));
|
|
160713
|
+
const sliceChars = Math.max(1, effectiveMax * charsPerToken);
|
|
160714
|
+
const out = [];
|
|
160715
|
+
let pos = 0;
|
|
160716
|
+
while (pos < text.length) {
|
|
160717
|
+
let end = Math.min(text.length, pos + sliceChars);
|
|
160718
|
+
let slice = text.slice(pos, end);
|
|
160719
|
+
while (slice.length > 1 && estimateTokens(slice) > effectiveMax) {
|
|
160720
|
+
end = pos + Math.max(1, Math.floor((end - pos) / 2));
|
|
160721
|
+
slice = text.slice(pos, end);
|
|
160722
|
+
}
|
|
160723
|
+
out.push(slice);
|
|
160724
|
+
pos = end;
|
|
160725
|
+
}
|
|
160726
|
+
return out;
|
|
160727
|
+
}
|
|
160574
160728
|
function getExistingChunkHashes(db, compartmentId, modelId, projectPath) {
|
|
160575
160729
|
const scoped = typeof projectPath === "string" && projectPath.length > 0;
|
|
160576
160730
|
const rows = scoped ? getExistingHashStatement(db, true).all(compartmentId, modelId, projectPath) : getExistingHashStatement(db, false).all(compartmentId, modelId);
|
|
@@ -160931,6 +161085,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
160931
161085
|
if (texts.length === 0) {
|
|
160932
161086
|
return [];
|
|
160933
161087
|
}
|
|
161088
|
+
const requestTexts = texts.map((t) => t.trim().length === 0 ? " " : t);
|
|
160934
161089
|
if (!await this.initialize()) {
|
|
160935
161090
|
return Array.from({ length: texts.length }, () => null);
|
|
160936
161091
|
}
|
|
@@ -160962,7 +161117,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
160962
161117
|
},
|
|
160963
161118
|
body: JSON.stringify({
|
|
160964
161119
|
model: this.model,
|
|
160965
|
-
input:
|
|
161120
|
+
input: requestTexts,
|
|
160966
161121
|
...inputTypeForRequest ? { input_type: inputTypeForRequest } : {},
|
|
160967
161122
|
...this.truncate ? { truncate: this.truncate } : {}
|
|
160968
161123
|
}),
|
|
@@ -162043,6 +162198,32 @@ async function embedBatchForProject(projectIdentity, texts, signal, purpose = "p
|
|
|
162043
162198
|
}
|
|
162044
162199
|
return { vectors, modelId, generation };
|
|
162045
162200
|
}
|
|
162201
|
+
async function embedTextsWindowBounded(projectIdentity, texts, signal) {
|
|
162202
|
+
if (texts.length <= MAX_WINDOWS_PER_EMBED_CALL) {
|
|
162203
|
+
return embedBatchForProject(projectIdentity, texts, signal);
|
|
162204
|
+
}
|
|
162205
|
+
const vectors = [];
|
|
162206
|
+
let modelId = null;
|
|
162207
|
+
let generation = null;
|
|
162208
|
+
for (let start = 0;start < texts.length; start += MAX_WINDOWS_PER_EMBED_CALL) {
|
|
162209
|
+
if (signal?.aborted)
|
|
162210
|
+
return null;
|
|
162211
|
+
const sub = texts.slice(start, start + MAX_WINDOWS_PER_EMBED_CALL);
|
|
162212
|
+
const result = await embedBatchForProject(projectIdentity, sub, signal);
|
|
162213
|
+
if (!result)
|
|
162214
|
+
return null;
|
|
162215
|
+
if (modelId === null) {
|
|
162216
|
+
modelId = result.modelId;
|
|
162217
|
+
generation = result.generation;
|
|
162218
|
+
} else if (result.modelId !== modelId || result.generation !== generation) {
|
|
162219
|
+
return null;
|
|
162220
|
+
}
|
|
162221
|
+
vectors.push(...result.vectors);
|
|
162222
|
+
}
|
|
162223
|
+
if (modelId === null || generation === null)
|
|
162224
|
+
return null;
|
|
162225
|
+
return { vectors, modelId, generation };
|
|
162226
|
+
}
|
|
162046
162227
|
function isUnembeddedMemoryRow(row) {
|
|
162047
162228
|
if (row === null || typeof row !== "object")
|
|
162048
162229
|
return false;
|
|
@@ -162176,7 +162357,7 @@ async function embedCandidateChunkBatch(db, projectIdentity, modelId, candidates
|
|
|
162176
162357
|
let result = null;
|
|
162177
162358
|
const attemptStart = Date.now();
|
|
162178
162359
|
try {
|
|
162179
|
-
result = await
|
|
162360
|
+
result = await embedTextsWindowBounded(projectIdentity, texts, signal);
|
|
162180
162361
|
} catch (error51) {
|
|
162181
162362
|
log("[magic-context] failed to proactively embed compartment chunks:", error51);
|
|
162182
162363
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wolfx/pi-magic-context",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi coding agent extension for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external node:sqlite",
|
|
29
|
+
"build": "bun build src/index.ts src/subagent-entry.ts --outdir dist --target node --format esm --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external @huggingface/transformers --external node:sqlite",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"test": "bun test",
|
|
32
32
|
"lint": "biome check src",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"typescript": "^5.8.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@earendil-works/pi-coding-agent": "
|
|
59
|
-
"@earendil-works/pi-tui": "
|
|
58
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
59
|
+
"@earendil-works/pi-tui": "^0.80.2"
|
|
60
60
|
},
|
|
61
61
|
"exports": {
|
|
62
62
|
".": {
|