@cortexkit/pi-magic-context 0.28.0 → 0.29.0

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
@@ -15906,6 +15906,17 @@ function getRawSessionMessageCountFromDb(db, sessionId) {
15906
15906
  return typeof row?.count === "number" ? row.count : 0;
15907
15907
  }
15908
15908
 
15909
+ // ../plugin/src/shared/commit-detection.ts
15910
+ var HASH_HEX = "[0-9a-f]{7,12}";
15911
+ var COMMIT_HASH_TEST_PATTERN = new RegExp(`\\b${HASH_HEX}\\b`, "i");
15912
+ var COMMIT_VERB_PATTERN = /\b(?:commit(?:ted|ting|s)?|cherry-?pick(?:ed|ing|s)?|merge[ds]?|merging|rebas(?:e|ed|es|ing))\b/i;
15913
+ function textMentionsRecentCommit(text) {
15914
+ return COMMIT_HASH_TEST_PATTERN.test(text) && COMMIT_VERB_PATTERN.test(text);
15915
+ }
15916
+ function createCommitHashExtractPattern() {
15917
+ return new RegExp(`\`?\\b(${HASH_HEX})\\b\`?`, "gi");
15918
+ }
15919
+
15909
15920
  // ../../node_modules/.bun/ai-tokenizer@1.0.6/node_modules/ai-tokenizer/dist/index.js
15910
15921
  function _typeof(o) {
15911
15922
  "@babel/helpers - typeof";
@@ -148345,8 +148356,6 @@ var decoder = {
148345
148356
  };
148346
148357
 
148347
148358
  // ../plugin/src/hooks/magic-context/read-session-formatting.ts
148348
- var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
148349
- var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
148350
148359
  var MAX_COMMITS_PER_BLOCK = 5;
148351
148360
  function hasMeaningfulUserText(parts) {
148352
148361
  for (const part of parts) {
@@ -148450,7 +148459,7 @@ function formatBlock(block) {
148450
148459
  function extractCommitHashes(text) {
148451
148460
  const hashes = [];
148452
148461
  const seen = new Set;
148453
- for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
148462
+ for (const match of text.matchAll(createCommitHashExtractPattern())) {
148454
148463
  const hash = match[1]?.toLowerCase();
148455
148464
  if (!hash || seen.has(hash))
148456
148465
  continue;
@@ -148463,10 +148472,10 @@ function extractCommitHashes(text) {
148463
148472
  }
148464
148473
  function compactTextForSummary(text, role) {
148465
148474
  const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
148466
- if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
148475
+ if (commitHashes.length === 0 || !COMMIT_VERB_PATTERN.test(text)) {
148467
148476
  return { text, commitHashes };
148468
148477
  }
148469
- const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
148478
+ const withoutHashes = text.replace(createCommitHashExtractPattern(), "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
148470
148479
  return {
148471
148480
  text: withoutHashes.length > 0 ? withoutHashes : text,
148472
148481
  commitHashes
@@ -148688,6 +148697,36 @@ function isRecord(value) {
148688
148697
  return typeof value === "object" && value !== null && !Array.isArray(value);
148689
148698
  }
148690
148699
 
148700
+ // ../plugin/src/hooks/magic-context/edit-marker.ts
148701
+ var TRUNCATION_SENTINEL = "...[truncated]";
148702
+ var EDIT_REGION_HINT_LEN = 40;
148703
+ var PATH_KEYS = new Set(["filePath", "file_path", "path"]);
148704
+ var DIFF_KEYS = new Set(["oldString", "newString", "content", "old_string", "new_string"]);
148705
+ function safeSlice(str, maxLen) {
148706
+ if (str.length <= maxLen)
148707
+ return str;
148708
+ const lastCharCode = str.charCodeAt(maxLen - 1);
148709
+ if (lastCharCode >= 55296 && lastCharCode <= 56319) {
148710
+ return str.slice(0, maxLen - 1);
148711
+ }
148712
+ return str.slice(0, maxLen);
148713
+ }
148714
+ function isEditTool(name2) {
148715
+ return name2 === "edit" || name2 === "write";
148716
+ }
148717
+ function applyEditMarkerToInput(input) {
148718
+ for (const key of Object.keys(input)) {
148719
+ if (PATH_KEYS.has(key))
148720
+ continue;
148721
+ const value = input[key];
148722
+ if (typeof value !== "string" || !DIFF_KEYS.has(key))
148723
+ continue;
148724
+ if (value.endsWith(TRUNCATION_SENTINEL))
148725
+ continue;
148726
+ input[key] = value.length > EDIT_REGION_HINT_LEN ? `${safeSlice(value, EDIT_REGION_HINT_LEN)}${TRUNCATION_SENTINEL}` : value;
148727
+ }
148728
+ }
148729
+
148691
148730
  // ../plugin/src/hooks/magic-context/tool-drop-target.ts
148692
148731
  var IGNORE_PART_TYPES = new Set([
148693
148732
  "thinking",
@@ -155191,7 +155230,7 @@ function toTagEntry(row) {
155191
155230
  messageId: row.message_id,
155192
155231
  type,
155193
155232
  status,
155194
- dropMode: row.drop_mode === "truncated" ? "truncated" : "full",
155233
+ dropMode: row.drop_mode === "truncated" ? "truncated" : row.drop_mode === "edit_marker" ? "edit_marker" : "full",
155195
155234
  toolName: row.tool_name ?? null,
155196
155235
  inputByteSize: row.input_byte_size ?? 0,
155197
155236
  byteSize: row.byte_size,
@@ -156347,10 +156386,10 @@ function compareSemverCore(a, b) {
156347
156386
 
156348
156387
  // ../plugin/src/shared/announcement.ts
156349
156388
  init_data_path();
156350
- var ANNOUNCEMENT_VERSION = "0.28.0";
156389
+ var ANNOUNCEMENT_VERSION = "0.29.0";
156351
156390
  var ANNOUNCEMENT_FEATURES = [
156352
- `New 'language' option: set a top-level 2-letter language code (e.g. "tr" or "es") and Magic Context writes its summaries, memories, and guidance in that language, while keeping all structure (tags, categories, code, paths) in English. User-level only, off by default.`,
156353
- "The ctx_reduce reminder now reflects how much tool output is actually reclaimable, instead of escalating to 'urgent' just because you're near compaction. It also no longer suggests dropping the agent's task list or tiny status outputs."
156391
+ `New 'smart_drops' option (opt-in, off by default): on long, edit-heavy sessions it reclaims more context by dropping tool output a later call made obsolete (superseded edits, old todowrite/ctx_reduce, spent status calls) instead of only the oldest. When off, the prompt is byte-identical to before. Enable with "smart_drops": true.`,
156392
+ "When memory is disabled, the ctx_memory tool and its guidance are no longer shown (they had nothing to write to). ctx_search stays, searching conversation history and git commits."
156354
156393
  ];
156355
156394
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
156356
156395
  var STATE_FILENAME = "last_announced_version";
@@ -172455,6 +172494,7 @@ var MagicContextConfigSchema = exports_external.object({
172455
172494
  }).describe("Embedding provider configuration"),
172456
172495
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
172457
172496
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
172497
+ smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
172458
172498
  caveman_text_compression: exports_external.object({
172459
172499
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
172460
172500
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
@@ -176274,6 +176314,7 @@ var MAINTAIN_DOCS_SYSTEM_PROMPT = `You are a documentation maintainer for the ma
176274
176314
 
176275
176315
  ## Rules
176276
176316
  - **NEVER touch protected regions.** Any content between \`<!-- mc:protected START ... -->\` and \`<!-- mc:protected END -->\` is hand-authored and cache-critical. Reproduce it BYTE-FOR-BYTE — do not edit, reword, reorder, summarize, trim, or drop a single line, and keep the marker comments. Only a human edits that region.
176317
+ - **Preserve an existing doc's structure, voice, and density.** When a doc already exists, it is the source of truth for shape: keep its headings, ordering, level of detail, and writing style. Make the SMALLEST edits that bring it back in sync with the code. NEVER reshape hand-written prose into a generic template, collapse a dense section into bullet stubs, or drop hard-won detail (specific invariants, edge cases, mechanism descriptions) because it does not fit a standard layout. A doc denser and more specific than a template is BETTER, not worse: leave it that way.
176277
176318
  - **Be prescriptive** ("Use X pattern", not "X pattern is used"). **Current state only** — no temporal language, no history.
176278
176319
  - **Verify before writing** — read the actual files, never guess. All file paths in the docs must point to files that exist.`;
176279
176320
  var REVIEW_USER_MEMORIES_SYSTEM_PROMPT = `You are a user-profile reviewer for the magic-context system. You run during a scheduled dream window to decide which recurring behavioral observations about the human user are real, persistent patterns worth keeping in their global user profile.
@@ -176389,7 +176430,7 @@ Return only XML in this exact shape:
176389
176430
  function buildMaintainDocsPrompt(projectPath, lastDreamAt, existingDocs) {
176390
176431
  const hasAny = existingDocs.architecture || existingDocs.structure;
176391
176432
  const gitSinceClause = lastDreamAt ? `Run \`git log --oneline --since="${new Date(Number(lastDreamAt)).toISOString()}"\` to see what changed since the last dream.` : "No previous dream timestamp — treat this as a full analysis.";
176392
- const modeIntro = hasAny ? `Some docs already exist. Update only the sections affected by recent changes. Do NOT rewrite unchanged sections.` : `No docs exist yet. Create both ARCHITECTURE.md and STRUCTURE.md from scratch using the templates below.`;
176433
+ const modeIntro = hasAny ? `Some docs already exist and are the source of truth for shape. Make SURGICAL \`edit\` changes to only the sections affected by recent code changes; preserve every other section, the existing structure, and the existing density verbatim. Do NOT regenerate a whole file, do NOT reshape prose into a template, and do NOT use the templates below (they are for creation only). If nothing material changed, change nothing.` : `No docs exist yet. Create both ARCHITECTURE.md and STRUCTURE.md from scratch using the templates below as a STARTING shape, then go deeper than the template wherever the code warrants it.`;
176393
176434
  return `## Task: Maintain Codebase Documentation
176394
176435
 
176395
176436
  **Project:** ${projectPath}
@@ -176404,15 +176445,16 @@ ${modeIntro}
176404
176445
  ### Process
176405
176446
 
176406
176447
  1. **Check what changed.** ${gitSinceClause}
176407
- 2. **Read existing docs** (if they exist) to understand current state.
176448
+ 2. **Read existing docs** (if they exist) IN FULL to understand their current structure, depth, and voice; you will preserve all of it except what code changes force you to touch.
176408
176449
  3. **Explore the codebase** to verify and update:
176409
176450
  - Directory structure: \`find . -type d -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -60\`
176410
176451
  - Entry points: \`ls src/index.* src/main.* 2>/dev/null\`
176411
176452
  - Key imports: \`grep -r "^import\\|^export" src/ --include="*.ts" | head -80\`
176412
- 4. **Write or update** using the Write tool. Always write to project root, NOT to .planning/.
176453
+ 4. **Apply the change.** If the doc EXISTS: use \`edit\` for the specific sections that drifted, never rewrite the whole file with \`write\`. If the doc is MISSING: create it with \`write\`. Always at project root, NOT \`.planning/\`.
176413
176454
 
176414
176455
  ### Rules
176415
176456
  - **NEVER touch protected regions**: any content between \`<!-- mc:protected START ... -->\` and \`<!-- mc:protected END -->\` is hand-authored and cache-critical. Reproduce it BYTE-FOR-BYTE in your rewrite — do not edit, reword, reorder, summarize, trim, or drop a single line of it, and keep the marker comments themselves. Only a human edits that region.
176457
+ - **Preserve existing structure and density**: when a doc exists, keep its headings, ordering, level of detail, and voice. Make the smallest edits that re-sync it with the code. NEVER flatten dense hand-written prose into the generic template, collapse a detailed section into bullet stubs, or drop specific invariants/edge-cases/mechanism detail because it does not match a standard layout. Denser and more specific than the template is BETTER.
176416
176458
  - **Be prescriptive**: "Use X pattern" not "X pattern is used"
176417
176459
  - **Always include file paths** in backticks
176418
176460
  - **Write current state only**: no temporal language, no history
@@ -177817,10 +177859,23 @@ function getAsyncModule() {
177817
177859
  asyncModulePromise ??= newQuickJSAsyncWASMModuleFromVariant(import_quickjs_singlefile_cjs_release_asyncify.default);
177818
177860
  return asyncModulePromise;
177819
177861
  }
177862
+ var sandboxRunChain = Promise.resolve();
177863
+ function withSandboxLock(fn) {
177864
+ const run = sandboxRunChain.then(fn, fn);
177865
+ sandboxRunChain = run.then(() => {
177866
+ return;
177867
+ }, () => {
177868
+ return;
177869
+ });
177870
+ return run;
177871
+ }
177820
177872
  var DEFAULT_TIMEOUT_MS = 2000;
177821
177873
  var DEFAULT_HEAP_LIMIT_BYTES = 8 * 1024 * 1024;
177822
177874
  var DEFAULT_STACK_LIMIT_BYTES = 512 * 1024;
177823
177875
  async function runCompiledSmartNoteCheck(options) {
177876
+ return withSandboxLock(() => runCompiledSmartNoteCheckLocked(options));
177877
+ }
177878
+ async function runCompiledSmartNoteCheckLocked(options) {
177824
177879
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
177825
177880
  const controller = new AbortController;
177826
177881
  const externalAbort = () => controller.abort(options.signal?.reason);
@@ -184652,7 +184707,7 @@ var RECENT_TOOL_SKELETON_WINDOW = 20;
184652
184707
  function buildReplacementContent(tagId) {
184653
184708
  return `[dropped §${tagId}§]`;
184654
184709
  }
184655
- function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = []) {
184710
+ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, preloadedTags, preloadedPendingOps, syntheticPendingOps = [], editMarkerTagIds = new Set) {
184656
184711
  let didMutateMessage = false;
184657
184712
  db.transaction(() => {
184658
184713
  const tags = preloadedTags ?? getTagsBySession(db, sessionId);
@@ -184683,7 +184738,15 @@ function applyPendingOperations(sessionId, db, targets, protectedTags = 0, prelo
184683
184738
  }
184684
184739
  let shouldPersistDrop = false;
184685
184740
  if (isToolTag) {
184686
- if (skeletonWindow.has(pendingOp.tagId)) {
184741
+ if (editMarkerTagIds.has(pendingOp.tagId)) {
184742
+ const markResult = target?.editMarker?.() ?? "absent";
184743
+ if (markResult === "incomplete" || markResult === "absent") {
184744
+ continue;
184745
+ }
184746
+ didMutateMessage = true;
184747
+ updateTagDropMode(db, sessionId, pendingOp.tagId, "edit_marker");
184748
+ shouldPersistDrop = true;
184749
+ } else if (skeletonWindow.has(pendingOp.tagId)) {
184687
184750
  const truncResult = target?.truncate?.() ?? "absent";
184688
184751
  if (truncResult === "incomplete" || synthetic && truncResult !== "truncated") {
184689
184752
  continue;
@@ -184728,7 +184791,12 @@ function applyFlushedStatuses(sessionId, db, targets, preloadedTags) {
184728
184791
  if (tag.status === "dropped") {
184729
184792
  const target = targets.get(tag.tagNumber);
184730
184793
  if (tag.type === "tool") {
184731
- if (tag.dropMode === "truncated") {
184794
+ if (tag.dropMode === "edit_marker") {
184795
+ const markResult = target?.editMarker?.() ?? "absent";
184796
+ if (markResult === "truncated") {
184797
+ didMutateMessage = true;
184798
+ }
184799
+ } else if (tag.dropMode === "truncated") {
184732
184800
  const truncResult = target?.truncate?.() ?? "absent";
184733
184801
  if (truncResult === "truncated") {
184734
184802
  didMutateMessage = true;
@@ -186879,6 +186947,97 @@ function renderMemoryBlockV2(memories, wrapper = "project-memory", renderOptions
186879
186947
  `);
186880
186948
  }
186881
186949
 
186950
+ // ../plugin/src/hooks/magic-context/supersession-reclaim.ts
186951
+ var TODOWRITE_KEEP = 1;
186952
+ var CTX_REDUCE_KEEP = 5;
186953
+ var ZERO_VALUE_META_TOOLS = new Set(["bash_status", "bash_kill"]);
186954
+ var CTX_NOTE_ZERO_VALUE_ACTIONS = new Set(["read", "dismiss"]);
186955
+ function buildSupersessionReclaimOps(input) {
186956
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
186957
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
186958
+ const toolTags = tags.filter((tag) => tag.type === "tool" && tag.status === "active").sort((left, right) => right.tagNumber - left.tagNumber);
186959
+ const dropTagIds = [];
186960
+ let todowriteSeen = 0;
186961
+ let ctxReduceSeen = 0;
186962
+ for (const tag of toolTags) {
186963
+ const name2 = tag.toolName;
186964
+ if (!name2)
186965
+ continue;
186966
+ let isTarget = false;
186967
+ if (name2 === "todowrite") {
186968
+ todowriteSeen += 1;
186969
+ isTarget = todowriteSeen > TODOWRITE_KEEP;
186970
+ } else if (name2 === "ctx_reduce") {
186971
+ ctxReduceSeen += 1;
186972
+ isTarget = ctxReduceSeen > CTX_REDUCE_KEEP;
186973
+ } else if (ZERO_VALUE_META_TOOLS.has(name2)) {
186974
+ isTarget = true;
186975
+ } else if (name2 === "ctx_note") {
186976
+ const action = input.targets.get(tag.tagNumber)?.readInput?.()?.action;
186977
+ isTarget = typeof action === "string" && CTX_NOTE_ZERO_VALUE_ACTIONS.has(action);
186978
+ }
186979
+ if (isTarget)
186980
+ dropTagIds.push(tag.tagNumber);
186981
+ }
186982
+ const synthetic = [];
186983
+ for (const tagId of dropTagIds) {
186984
+ if (realPendingTagIds.has(tagId))
186985
+ continue;
186986
+ if (input.targets.get(tagId)?.canDrop?.() !== true)
186987
+ continue;
186988
+ synthetic.push({
186989
+ id: 0,
186990
+ sessionId: input.sessionId,
186991
+ tagId,
186992
+ operation: "drop",
186993
+ queuedAt: 0
186994
+ });
186995
+ }
186996
+ return synthetic;
186997
+ }
186998
+ function buildEditSupersessionReclaim(input) {
186999
+ const realPendingTagIds = new Set((input.pendingOps ?? []).map((op) => op.tagId));
187000
+ const tags = getActiveTagsBySession(input.db, input.sessionId);
187001
+ const editTags = tags.filter((tag) => tag.type === "tool" && tag.status === "active" && isEditTool(tag.toolName)).sort((left, right) => right.tagNumber - left.tagNumber);
187002
+ const seenFile = new Set;
187003
+ const ops = [];
187004
+ const editMarkerTagIds = new Set;
187005
+ for (const tag of editTags) {
187006
+ const filePath = readFilePath(input.targets.get(tag.tagNumber));
187007
+ if (!filePath)
187008
+ continue;
187009
+ if (!seenFile.has(filePath)) {
187010
+ seenFile.add(filePath);
187011
+ continue;
187012
+ }
187013
+ if (realPendingTagIds.has(tag.tagNumber))
187014
+ continue;
187015
+ if (input.targets.get(tag.tagNumber)?.canDrop?.() !== true)
187016
+ continue;
187017
+ editMarkerTagIds.add(tag.tagNumber);
187018
+ ops.push({
187019
+ id: 0,
187020
+ sessionId: input.sessionId,
187021
+ tagId: tag.tagNumber,
187022
+ operation: "drop",
187023
+ queuedAt: 0
187024
+ });
187025
+ }
187026
+ return { ops, editMarkerTagIds };
187027
+ }
187028
+ var FILE_PATH_KEYS = ["filePath", "file_path", "path"];
187029
+ function readFilePath(target) {
187030
+ const input = target?.readInput?.();
187031
+ if (!input)
187032
+ return null;
187033
+ for (const key of FILE_PATH_KEYS) {
187034
+ const value = input[key];
187035
+ if (typeof value === "string" && value.length > 0)
187036
+ return value;
187037
+ }
187038
+ return null;
187039
+ }
187040
+
186882
187041
  // ../plugin/src/hooks/magic-context/tool-reclaim.ts
186883
187042
  function buildSyntheticToolReclaimOps(input) {
186884
187043
  const watermark = Math.max(0, input.watermark);
@@ -187373,9 +187532,35 @@ function buildAggregateTarget(tagId, occurrences) {
187373
187532
  }
187374
187533
  return any2 ? "truncated" : "absent";
187375
187534
  },
187535
+ editMarker() {
187536
+ const sentinel = `[dropped §${tagId}§]`;
187537
+ let any2 = false;
187538
+ for (const occ of occurrences) {
187539
+ if (occ.kind === "tool_use") {
187540
+ const input = occ.part.getToolInput?.();
187541
+ if (input) {
187542
+ const next = { ...input };
187543
+ applyEditMarkerToInput(next);
187544
+ if (occ.part.setToolInput?.(next))
187545
+ any2 = true;
187546
+ }
187547
+ } else if (setToolContentOrText(occ.part, sentinel)) {
187548
+ any2 = true;
187549
+ }
187550
+ }
187551
+ return any2 ? "truncated" : "absent";
187552
+ },
187376
187553
  canDrop() {
187377
187554
  return occurrences.length > 0;
187378
187555
  },
187556
+ readInput() {
187557
+ for (const occ of occurrences) {
187558
+ const input = occ.part.getToolInput?.();
187559
+ if (input)
187560
+ return input;
187561
+ }
187562
+ return null;
187563
+ },
187379
187564
  message: {
187380
187565
  info: { id: messageId, role },
187381
187566
  parts: []
@@ -188760,8 +188945,6 @@ function maybeDeliverChannel2Pi(pi, db, sessionId, deliverAs = "followUp") {
188760
188945
 
188761
188946
  // src/detect-recent-commit.ts
188762
188947
  var COMMIT_LOOKBACK = 5;
188763
- var COMMIT_HASH_PATTERN2 = /\b[0-9a-f]{7,12}\b/;
188764
- var COMMIT_VERB_PATTERN = /\b(commit|committed|cherry-pick|merge|rebas)/i;
188765
188948
  function detectRecentCommit(messages) {
188766
188949
  let assistantsScanned = 0;
188767
188950
  for (let i = messages.length - 1;i >= 0; i--) {
@@ -188779,8 +188962,7 @@ function detectRecentCommit(messages) {
188779
188962
  continue;
188780
188963
  for (const part of content) {
188781
188964
  if (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string") {
188782
- const text = part.text;
188783
- if (COMMIT_HASH_PATTERN2.test(text) && COMMIT_VERB_PATTERN.test(text)) {
188965
+ if (textMentionsRecentCommit(part.text)) {
188784
188966
  return true;
188785
188967
  }
188786
188968
  }
@@ -194458,16 +194640,20 @@ var PARTNER_FRAME_CLOSER_NO_REDUCE = `
194458
194640
  Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
194459
194641
  var CTX_NOTE_GUIDANCE = `Use \`ctx_note\` ONLY for genuinely future concerns — something to revisit much later, not work coming up in the next few turns (that's already in your active context) and not active multi-step work (use todos for that). Magic Context preserves your full context across both compaction and restarts, so an upcoming restart or "let's come back to this later" is never a reason to take a note — nothing is lost either way. Notes you do take survive compression and resurface at natural work boundaries (after commits, historian runs, todo completion).`;
194460
194642
  var TOOL_HISTORY_GUIDANCE = `Compressed history intentionally omits tool calls and their outputs — summaries like "I edited file X" are historian records, not patterns to replicate. In the live conversation, older tool calls and their results are cleaned up to save context — you may see your own past messages referencing actions without the corresponding tool call or result visible. This is normal context management. ALWAYS use real tool calls; never simulate, fabricate, or inline tool outputs in your text. If there is no tool result message, the action did not happen. NEVER simulate, hallucinate or claim tool calls, command output, search results, file edits, or diffs in plain text as if they actually occurred.`;
194461
- var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
194462
- Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
194463
- Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
194464
- ${CTX_NOTE_GUIDANCE}
194465
- Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
194643
+ var MEMORY_GUIDANCE = `Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
194466
194644
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
194467
194645
  - Found a project's source code path after searching → \`ctx_memory(action="write", category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/opencode")\`
194468
194646
  - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
194469
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
194470
- Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
194647
+ - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\``;
194648
+ function memoryGuidanceBlock(memoryEnabled) {
194649
+ return memoryEnabled ? `${MEMORY_GUIDANCE}
194650
+ ` : "";
194651
+ }
194652
+ var BASE_INTRO = (protectedTags, memoryEnabled) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
194653
+ Use \`ctx_reduce\` to mark spent tagged content as discardable and reclaim space. Marking is NOT an immediate delete — it queues the content, which stays fully visible until space is actually needed (as soon as the next turn if you're already under pressure, much later if not), so mark a tool output as soon as you're done with it rather than hoarding the call for the end of the turn. The last ${protectedTags} tags are protected (marking one just queues it until it ages out). Syntax: "3-5", "1,2,9", or "1-5,8,12-15".
194654
+ Do not announce or narrate \`ctx_reduce\` drops — just call the tool silently. Saying "I'll drop these outputs" wastes tokens the user does not care about.
194655
+ ${CTX_NOTE_GUIDANCE}
194656
+ ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
194471
194657
  Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
194472
194658
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
194473
194659
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
@@ -194481,13 +194667,8 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
194481
194667
  Keep your user's instructions and intent — never drop a user message for its directive, even an old one. But a large block of pasted content inside a user message (logs, data dumps, long code, attachments) is fair to mark discardable once you've extracted what you need — it stays searchable via \`ctx_search\`.
194482
194668
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
194483
194669
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
194484
- var BASE_INTRO_NO_REDUCE = () => `${CTX_NOTE_GUIDANCE}
194485
- Use \`ctx_memory\` for durable project knowledge: write what future sessions must know, update/archive/merge the memories you see in \`<project-memory>\` when they drift. Memories persist across sessions and every new session starts with them.
194486
- **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
194487
- - Found a project's source code path after searching → \`ctx_memory(action="write", category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/opencode")\`
194488
- - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="PROJECT_RULES", content="Always use scripts/release.sh for releases")\`
194489
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
194490
- Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
194670
+ var BASE_INTRO_NO_REDUCE = (memoryEnabled) => `${CTX_NOTE_GUIDANCE}
194671
+ ${memoryGuidanceBlock(memoryEnabled)}Use \`ctx_search\` to search across project memories, indexed git commits, and this session's full conversation history (including compacted parts) from one query.
194491
194672
  Use \`ctx_expand\` to recover the raw conversation behind a \`<compartment>\` summary in \`<session-history>\` — pass its \`start\`/\`end\` attributes when the summary is not enough (exact wording, values, error text).
194492
194673
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
194493
194674
  - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
@@ -194521,7 +194702,7 @@ Drop silently — do not narrate it. NEVER drop large ranges blindly (e.g., "1-5
194521
194702
  Older tool calls may show \`[dropped §N§]\` sentinels; that is normal context management, not a pattern to copy. ALWAYS make fresh real tool calls when you need data again; never fabricate or inline tool output.`;
194522
194703
  var CAVEMAN_COMPRESSION_WARNING = `
194523
194704
  **BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
194524
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language) {
194705
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true) {
194525
194706
  if (subagentMode) {
194526
194707
  return `## Magic Context
194527
194708
 
@@ -194543,14 +194724,14 @@ ${languageDirective}` : "";
194543
194724
  ${LONG_TERM_PARTNER_FRAME}
194544
194725
  ${PARTNER_FRAME_CLOSER_NO_REDUCE}
194545
194726
 
194546
- ${BASE_INTRO_NO_REDUCE()}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
194727
+ ${BASE_INTRO_NO_REDUCE(memoryEnabled)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}${languageGuidance}`;
194547
194728
  }
194548
194729
  return `## Magic Context
194549
194730
 
194550
194731
  ${LONG_TERM_PARTNER_FRAME}
194551
194732
  ${PARTNER_FRAME_CLOSER_REDUCE}
194552
194733
 
194553
- ${BASE_INTRO(protectedTags)}${smartNoteGuidance}${temporalGuidance}
194734
+ ${BASE_INTRO(protectedTags, memoryEnabled)}${smartNoteGuidance}${temporalGuidance}
194554
194735
  ${GENERIC_SECTION}
194555
194736
 
194556
194737
  Prefer many small targeted operations over one large blanket operation, and keep the working set tidy as routine maintenance.${languageGuidance}`;
@@ -194565,7 +194746,7 @@ function buildMagicContextBlock(opts) {
194565
194746
  const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
194566
194747
  if (!includeGuidance)
194567
194748
  return null;
194568
- return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language);
194749
+ return buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false, false, opts.language, opts.memoryEnabled !== false);
194569
194750
  }
194570
194751
  var DATE_PATTERN = /Today's date: .+/;
194571
194752
  function processSystemPromptForCache(args) {
@@ -195100,6 +195281,31 @@ function createPiAssistantPart(working, messageIndex, partIndex, markDirty) {
195100
195281
  }
195101
195282
  return { toolName: p.name, inputByteSize, inputTokenCount };
195102
195283
  },
195284
+ getToolInput() {
195285
+ const current = working[messageIndex].content;
195286
+ const p = current[partIndex];
195287
+ if (p?.type !== "toolCall")
195288
+ return null;
195289
+ return p.arguments && typeof p.arguments === "object" ? p.arguments : null;
195290
+ },
195291
+ setToolInput(input) {
195292
+ const current = working[messageIndex].content;
195293
+ const p = current[partIndex];
195294
+ if (p?.type !== "toolCall")
195295
+ return false;
195296
+ try {
195297
+ if (JSON.stringify(p.arguments) === JSON.stringify(input))
195298
+ return false;
195299
+ } catch {}
195300
+ const newContent = current.slice();
195301
+ newContent[partIndex] = { ...p, arguments: input };
195302
+ working[messageIndex] = {
195303
+ ...working[messageIndex],
195304
+ content: newContent
195305
+ };
195306
+ markDirty(messageIndex);
195307
+ return true;
195308
+ },
195103
195309
  replaceWithSentinel(sentinelText) {
195104
195310
  const current = working[messageIndex].content;
195105
195311
  const existing = current[partIndex];
@@ -195933,6 +196139,7 @@ function registerPiContextHandler(pi, baseOptions) {
195933
196139
  projectDirectory,
195934
196140
  messages: event.messages,
195935
196141
  ctxReduceEnabled: options.ctxReduceEnabled,
196142
+ smartDrops: options.smartDrops === true,
195936
196143
  protectedTags: options.protectedTags ?? 20,
195937
196144
  heuristics: options.heuristics,
195938
196145
  emergencyCeilingTokens,
@@ -196723,9 +196930,38 @@ async function runPipeline(args) {
196723
196930
  watermark: reclaimMeta.toolReclaimWatermark ?? 0,
196724
196931
  pendingOps
196725
196932
  });
196933
+ const editMarkerTagIds = new Set;
196934
+ if (args.smartDrops) {
196935
+ const selectedIds = new Set(syntheticPendingOps.map((op) => op.tagId));
196936
+ const supersessionOps = buildSupersessionReclaimOps({
196937
+ db: args.db,
196938
+ sessionId: args.sessionId,
196939
+ targets,
196940
+ pendingOps
196941
+ });
196942
+ for (const op of supersessionOps) {
196943
+ if (!selectedIds.has(op.tagId)) {
196944
+ syntheticPendingOps.push(op);
196945
+ selectedIds.add(op.tagId);
196946
+ }
196947
+ }
196948
+ const editReclaim = buildEditSupersessionReclaim({
196949
+ db: args.db,
196950
+ sessionId: args.sessionId,
196951
+ targets,
196952
+ pendingOps
196953
+ });
196954
+ for (const op of editReclaim.ops) {
196955
+ if (!selectedIds.has(op.tagId)) {
196956
+ syntheticPendingOps.push(op);
196957
+ selectedIds.add(op.tagId);
196958
+ editMarkerTagIds.add(op.tagId);
196959
+ }
196960
+ }
196961
+ }
196726
196962
  autoReclaimTargetCount = syntheticPendingOps.length;
196727
196963
  if (syntheticPendingOps.length > 0) {
196728
- autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, [], syntheticPendingOps);
196964
+ autoReclaimDidMutate = applyPendingOperations(args.sessionId, args.db, targets, args.protectedTags, undefined, [], syntheticPendingOps, editMarkerTagIds);
196729
196965
  if (autoReclaimDidMutate) {
196730
196966
  droppedCount += syntheticPendingOps.length;
196731
196967
  autoReclaimDidMutateThisPass = true;
@@ -199451,7 +199687,7 @@ function formatThresholdPercent(value) {
199451
199687
  // package.json
199452
199688
  var package_default = {
199453
199689
  name: "@cortexkit/pi-magic-context",
199454
- version: "0.28.0",
199690
+ version: "0.29.0",
199455
199691
  type: "module",
199456
199692
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
199457
199693
  main: "dist/index.js",
@@ -205715,6 +205951,7 @@ async function src_default2(pi) {
205715
205951
  db,
205716
205952
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
205717
205953
  allowDreamerActions: false,
205954
+ memoryToolEnabled: true,
205718
205955
  ctxReduceEnabled: config2.ctx_reduce_enabled === true,
205719
205956
  protectedTags: config2.protected_tags ?? 20,
205720
205957
  dreamerEnabled: isDreamerRunnable(config2)
@@ -205734,6 +205971,7 @@ async function src_default2(pi) {
205734
205971
  const buildContextOptions = (cfg, hist, auto) => ({
205735
205972
  db: database,
205736
205973
  ctxReduceEnabled: cfg.ctx_reduce_enabled,
205974
+ smartDrops: cfg.smart_drops === true,
205737
205975
  protectedTags: cfg.protected_tags ?? 20,
205738
205976
  heuristics: {
205739
205977
  caveman: cfg.ctx_reduce_enabled === false && cfg.caveman_text_compression ? {
@@ -9923,6 +9923,17 @@ function runMigrations(db) {
9923
9923
  log(`[migrations] schema version now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
9924
9924
  }
9925
9925
 
9926
+ // ../plugin/src/shared/commit-detection.ts
9927
+ var HASH_HEX = "[0-9a-f]{7,12}";
9928
+ var COMMIT_HASH_TEST_PATTERN = new RegExp(`\\b${HASH_HEX}\\b`, "i");
9929
+ var COMMIT_VERB_PATTERN = /\b(?:commit(?:ted|ting|s)?|cherry-?pick(?:ed|ing|s)?|merge[ds]?|merging|rebas(?:e|ed|es|ing))\b/i;
9930
+ function textMentionsRecentCommit(text) {
9931
+ return COMMIT_HASH_TEST_PATTERN.test(text) && COMMIT_VERB_PATTERN.test(text);
9932
+ }
9933
+ function createCommitHashExtractPattern() {
9934
+ return new RegExp(`\`?\\b(${HASH_HEX})\\b\`?`, "gi");
9935
+ }
9936
+
9926
9937
  // ../plugin/src/shared/internal-initiator-marker.ts
9927
9938
  var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
9928
9939
 
@@ -142374,8 +142385,6 @@ var decoder = {
142374
142385
  };
142375
142386
 
142376
142387
  // ../plugin/src/hooks/magic-context/read-session-formatting.ts
142377
- var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
142378
- var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
142379
142388
  var MAX_COMMITS_PER_BLOCK = 5;
142380
142389
  function hasMeaningfulUserText(parts) {
142381
142390
  for (const part of parts) {
@@ -142479,7 +142488,7 @@ function formatBlock(block) {
142479
142488
  function extractCommitHashes(text) {
142480
142489
  const hashes = [];
142481
142490
  const seen = new Set;
142482
- for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
142491
+ for (const match of text.matchAll(createCommitHashExtractPattern())) {
142483
142492
  const hash = match[1]?.toLowerCase();
142484
142493
  if (!hash || seen.has(hash))
142485
142494
  continue;
@@ -142492,10 +142501,10 @@ function extractCommitHashes(text) {
142492
142501
  }
142493
142502
  function compactTextForSummary(text, role) {
142494
142503
  const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
142495
- if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
142504
+ if (commitHashes.length === 0 || !COMMIT_VERB_PATTERN.test(text)) {
142496
142505
  return { text, commitHashes };
142497
142506
  }
142498
- const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
142507
+ const withoutHashes = text.replace(createCommitHashExtractPattern(), "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
142499
142508
  return {
142500
142509
  text: withoutHashes.length > 0 ? withoutHashes : text,
142501
142510
  commitHashes
@@ -159667,6 +159676,7 @@ var MagicContextConfigSchema = exports_external.object({
159667
159676
  }).describe("Embedding provider configuration"),
159668
159677
  temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
159669
159678
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
159679
+ smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
159670
159680
  caveman_text_compression: exports_external.object({
159671
159681
  enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
159672
159682
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
@@ -163577,6 +163587,36 @@ function isRecord(value) {
163577
163587
  return typeof value === "object" && value !== null && !Array.isArray(value);
163578
163588
  }
163579
163589
 
163590
+ // ../plugin/src/hooks/magic-context/edit-marker.ts
163591
+ var TRUNCATION_SENTINEL = "...[truncated]";
163592
+ var EDIT_REGION_HINT_LEN = 40;
163593
+ var PATH_KEYS = new Set(["filePath", "file_path", "path"]);
163594
+ var DIFF_KEYS = new Set(["oldString", "newString", "content", "old_string", "new_string"]);
163595
+ function safeSlice(str, maxLen) {
163596
+ if (str.length <= maxLen)
163597
+ return str;
163598
+ const lastCharCode = str.charCodeAt(maxLen - 1);
163599
+ if (lastCharCode >= 55296 && lastCharCode <= 56319) {
163600
+ return str.slice(0, maxLen - 1);
163601
+ }
163602
+ return str.slice(0, maxLen);
163603
+ }
163604
+ function isEditTool(name2) {
163605
+ return name2 === "edit" || name2 === "write";
163606
+ }
163607
+ function applyEditMarkerToInput(input) {
163608
+ for (const key of Object.keys(input)) {
163609
+ if (PATH_KEYS.has(key))
163610
+ continue;
163611
+ const value = input[key];
163612
+ if (typeof value !== "string" || !DIFF_KEYS.has(key))
163613
+ continue;
163614
+ if (value.endsWith(TRUNCATION_SENTINEL))
163615
+ continue;
163616
+ input[key] = value.length > EDIT_REGION_HINT_LEN ? `${safeSlice(value, EDIT_REGION_HINT_LEN)}${TRUNCATION_SENTINEL}` : value;
163617
+ }
163618
+ }
163619
+
163580
163620
  // ../plugin/src/hooks/magic-context/tool-drop-target.ts
163581
163621
  var IGNORE_PART_TYPES = new Set([
163582
163622
  "thinking",
@@ -171706,7 +171746,7 @@ function toTagEntry(row) {
171706
171746
  messageId: row.message_id,
171707
171747
  type,
171708
171748
  status,
171709
- dropMode: row.drop_mode === "truncated" ? "truncated" : "full",
171749
+ dropMode: row.drop_mode === "truncated" ? "truncated" : row.drop_mode === "edit_marker" ? "edit_marker" : "full",
171710
171750
  toolName: row.tool_name ?? null,
171711
171751
  inputByteSize: row.input_byte_size ?? 0,
171712
171752
  byteSize: row.byte_size,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-magic-context",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
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",