@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 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
- queue.push({ id: nextNotificationId++, type, payload, sessionId });
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
- const now = Date.now();
14985
- if (sessionId !== undefined) {
14986
- const at = lastDrainAtBySession.get(sessionId) ?? 0;
14987
- return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS;
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 lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS;
15009
+ return false;
14990
15010
  }
14991
- var queue, nextNotificationId = 1, lastDrainAtBySession, lastDrainAtAny = 0, TUI_CONNECTED_WINDOW_MS = 3000;
15011
+ var queue, nextNotificationId = 1, sinks;
14992
15012
  var init_rpc_notifications = __esm(() => {
14993
15013
  queue = [];
14994
- lastDrainAtBySession = new Map;
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.1";
156484
+ var ANNOUNCEMENT_VERSION = "0.30.2";
156465
156485
  var ANNOUNCEMENT_FEATURES = [
156466
- "Local embeddings work on OpenCode Desktop again (#195): /ctx-embed no longer fails with 'Unsupported device: cpu' on the Desktop app."
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: texts,
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 embedBatchForProject(projectIdentity, texts, signal);
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 : info.modified instanceof Date ? info.modified.getTime() : undefined
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 sessionListAll = mod.SessionManager?.listAll;
183260
- const parseEntries = mod.parseSessionEntries;
183261
- if (typeof sessionListAll !== "function" || typeof parseEntries !== "function") {
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 { readFileSync: readFileSync9 } = await import("node:fs");
183462
+ const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
183463
+ const content = readFileSync9(filePath, "utf8");
183464
+ return mod.parseSessionEntries?.(content) ?? [];
183465
+ });
183265
183466
  return {
183266
- listSessions: () => sessionListAll.call(mod.SessionManager),
183267
- loadEntriesFromFile: (filePath) => parseEntries(readFileSync9(filePath, "utf-8"))
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.1",
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
  ".": {
@@ -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: texts,
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 embedBatchForProject(projectIdentity, texts, signal);
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.1",
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
  ".": {