@cortexkit/pi-magic-context 0.21.0 → 0.21.2

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
@@ -8163,10 +8163,10 @@ var require_stringify = __commonJS((exports, module) => {
8163
8163
  replacer = null;
8164
8164
  indent = EMPTY;
8165
8165
  };
8166
- var join8 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
8166
+ var join9 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
8167
8167
  var join_content = (inside, value, gap) => {
8168
8168
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
8169
- return join8(comment, inside, gap);
8169
+ return join9(comment, inside, gap);
8170
8170
  };
8171
8171
  var stringify_string = (holder, key, value) => {
8172
8172
  const raw = get_raw_string_literal(holder, key);
@@ -8188,13 +8188,13 @@ var require_stringify = __commonJS((exports, module) => {
8188
8188
  if (i !== 0) {
8189
8189
  inside += COMMA;
8190
8190
  }
8191
- const before = join8(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8191
+ const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
8192
8192
  inside += before || LF + deeper_gap;
8193
8193
  inside += stringify(i, value, deeper_gap) || STR_NULL;
8194
8194
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
8195
8195
  after_comma = process_comments(value, AFTER(i), deeper_gap);
8196
8196
  }
8197
- inside += join8(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8197
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8198
8198
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
8199
8199
  };
8200
8200
  var object_stringify = (value, gap) => {
@@ -8215,13 +8215,13 @@ var require_stringify = __commonJS((exports, module) => {
8215
8215
  inside += COMMA;
8216
8216
  }
8217
8217
  first = false;
8218
- const before = join8(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8218
+ const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
8219
8219
  inside += before || LF + deeper_gap;
8220
8220
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
8221
8221
  after_comma = process_comments(value, AFTER(key), deeper_gap);
8222
8222
  };
8223
8223
  keys.forEach(iteratee);
8224
- inside += join8(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8224
+ inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
8225
8225
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
8226
8226
  };
8227
8227
  function stringify(key, holder, gap) {
@@ -8342,8 +8342,8 @@ var init_rpc_notifications = __esm(() => {
8342
8342
  });
8343
8343
 
8344
8344
  // src/index.ts
8345
- import { createRequire as createRequire3 } from "node:module";
8346
- import { join as join18 } from "node:path";
8345
+ import { createRequire as createRequire4 } from "node:module";
8346
+ import { join as join19 } from "node:path";
8347
8347
 
8348
8348
  // ../plugin/src/features/magic-context/memory/project-identity.ts
8349
8349
  import { execSync } from "node:child_process";
@@ -143698,6 +143698,13 @@ function getDefaultHistorianFailureState() {
143698
143698
  lastFailureAt: null
143699
143699
  };
143700
143700
  }
143701
+ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
143702
+ ensureSessionMetaRow(db, sessionId);
143703
+ db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
143704
+ }
143705
+ function clearPersistedReasoningWatermark(db, sessionId) {
143706
+ setPersistedReasoningWatermark(db, sessionId, 0);
143707
+ }
143701
143708
  function getPersistedStickyTurnReminder(db, sessionId) {
143702
143709
  const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
143703
143710
  if (!isPersistedStickyTurnReminderRow(result)) {
@@ -143939,6 +143946,12 @@ function clearEmergencyRecovery(db, sessionId) {
143939
143946
  db.prepare("UPDATE session_meta SET needs_emergency_recovery = 0 WHERE session_id = ?").run(sessionId);
143940
143947
  })();
143941
143948
  }
143949
+ function clearDetectedContextLimit(db, sessionId) {
143950
+ db.transaction(() => {
143951
+ ensureSessionMetaRow(db, sessionId);
143952
+ db.prepare("UPDATE session_meta SET detected_context_limit = 0 WHERE session_id = ?").run(sessionId);
143953
+ })();
143954
+ }
143942
143955
  function getPersistedCompactionMarkerState(db, sessionId) {
143943
143956
  const row = db.prepare("SELECT compaction_marker_state FROM session_meta WHERE session_id = ?").get(sessionId);
143944
143957
  const raw = row?.compaction_marker_state;
@@ -145203,9 +145216,22 @@ init_logger();
145203
145216
  // src/subagent-runner.ts
145204
145217
  import * as childProcess from "node:child_process";
145205
145218
  import { existsSync as existsSync6 } from "node:fs";
145206
- import { dirname as dirname3, resolve as resolvePath } from "node:path";
145219
+ import { createRequire as createRequire3 } from "node:module";
145220
+ import { dirname as dirname3, join as join8, resolve as resolvePath } from "node:path";
145207
145221
  import { createInterface } from "node:readline";
145208
145222
  import { fileURLToPath } from "node:url";
145223
+ function resolveBundledPiCli() {
145224
+ try {
145225
+ const require_ = createRequire3(import.meta.url);
145226
+ const pkgJson = require_.resolve("@earendil-works/pi-coding-agent/package.json");
145227
+ const cliPath = join8(dirname3(pkgJson), "dist/cli.js");
145228
+ if (existsSync6(cliPath))
145229
+ return cliPath;
145230
+ return null;
145231
+ } catch {
145232
+ return null;
145233
+ }
145234
+ }
145209
145235
  function resolveSubagentEntryPath() {
145210
145236
  try {
145211
145237
  const here = dirname3(fileURLToPath(import.meta.url));
@@ -145229,7 +145255,16 @@ class PiSubagentRunner {
145229
145255
  piBinary;
145230
145256
  spawnImpl;
145231
145257
  constructor(options = {}) {
145232
- this.piBinary = options.piBinary ?? "pi";
145258
+ if (options.piBinary) {
145259
+ this.piBinary = options.piBinary;
145260
+ } else {
145261
+ const bundled = resolveBundledPiCli();
145262
+ if (bundled) {
145263
+ this.piBinary = bundled;
145264
+ } else {
145265
+ this.piBinary = "pi";
145266
+ }
145267
+ }
145233
145268
  this.spawnImpl = options.spawnImpl ?? childProcess.spawn;
145234
145269
  }
145235
145270
  async run(options) {
@@ -145455,10 +145490,10 @@ class PiSubagentRunner {
145455
145490
  });
145456
145491
  return;
145457
145492
  }
145458
- if (finalStopReason === "error" || finalStopReason === "aborted") {
145493
+ if (finalStopReason === "error" || finalStopReason === "aborted" || finalStopReason === "length") {
145459
145494
  settle({
145460
145495
  ok: false,
145461
- reason: "model_failed",
145496
+ reason: finalStopReason === "length" ? "truncated" : "model_failed",
145462
145497
  error: finalErrorMessage ?? `pi assistant stopped with reason "${finalStopReason}"`,
145463
145498
  durationMs: Date.now() - startTime,
145464
145499
  meta: { stderr: stderr.length > 0 ? stderr : undefined }
@@ -145580,10 +145615,18 @@ function parsePiEventLine(line) {
145580
145615
  }
145581
145616
  }
145582
145617
  function terminateChild(child) {
145618
+ let exited = false;
145619
+ child.once("close", () => {
145620
+ exited = true;
145621
+ });
145622
+ child.once("exit", () => {
145623
+ exited = true;
145624
+ });
145583
145625
  child.kill("SIGTERM");
145584
145626
  const killHandle = setTimeout(() => {
145585
- if (!child.killed)
145627
+ if (!exited && child.exitCode === null && child.signalCode === null) {
145586
145628
  child.kill("SIGKILL");
145629
+ }
145587
145630
  }, 2000);
145588
145631
  if (typeof killHandle.unref === "function") {
145589
145632
  killHandle.unref();
@@ -145832,7 +145875,7 @@ function clearStaleEntries(db, maxAgeMs) {
145832
145875
  init_logger();
145833
145876
  // ../plugin/src/features/magic-context/dreamer/runner.ts
145834
145877
  import { existsSync as existsSync8 } from "node:fs";
145835
- import { join as join10 } from "node:path";
145878
+ import { join as join11 } from "node:path";
145836
145879
 
145837
145880
  // ../plugin/src/shared/index.ts
145838
145881
  init_logger();
@@ -146127,14 +146170,14 @@ await init_sqlite();
146127
146170
 
146128
146171
  // ../plugin/src/features/magic-context/key-files/identify-key-files.ts
146129
146172
  import { readFileSync as readFileSync4 } from "node:fs";
146130
- import { join as join9 } from "node:path";
146173
+ import { join as join10 } from "node:path";
146131
146174
  init_logger();
146132
146175
 
146133
146176
  // ../plugin/src/features/magic-context/key-files/aft-availability.ts
146134
146177
  var import_comment_json = __toESM(require_src2(), 1);
146135
146178
  import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
146136
146179
  import { homedir as homedir3 } from "node:os";
146137
- import { join as join8 } from "node:path";
146180
+ import { join as join9 } from "node:path";
146138
146181
  var overrideAvailability = null;
146139
146182
  function parseConfig(path5) {
146140
146183
  if (!existsSync7(path5))
@@ -146161,10 +146204,10 @@ function hasAftAtKeys(value, keys) {
146161
146204
  function getAftAvailability() {
146162
146205
  const home = process.env.HOME || homedir3();
146163
146206
  const opencodePaths = [
146164
- join8(home, ".config", "opencode", "opencode.jsonc"),
146165
- join8(home, ".config", "opencode", "opencode.json")
146207
+ join9(home, ".config", "opencode", "opencode.jsonc"),
146208
+ join9(home, ".config", "opencode", "opencode.json")
146166
146209
  ];
146167
- const piPaths = [join8(home, ".pi", "agent", "settings.json")];
146210
+ const piPaths = [join9(home, ".pi", "agent", "settings.json")];
146168
146211
  const checkedPaths = [...opencodePaths, ...piPaths];
146169
146212
  let opencode = false;
146170
146213
  for (const path5 of opencodePaths) {
@@ -146551,7 +146594,7 @@ async function runKeyFilesLlm(args) {
146551
146594
  });
146552
146595
  const messagesResponse = await args.client.session.messages({
146553
146596
  path: { id: agentSessionId },
146554
- query: { directory: args.projectPath }
146597
+ query: { directory: args.projectPath, limit: 50 }
146555
146598
  });
146556
146599
  const messages = normalizeSDKResponse(messagesResponse, [], {
146557
146600
  preferResponseOnMissingData: true
@@ -146588,7 +146631,7 @@ async function runKeyFilesTask(args) {
146588
146631
  if (row.staleReason !== null || row.generationConfigHash !== configHash)
146589
146632
  return false;
146590
146633
  try {
146591
- return sha256(readFileSync4(join9(projectPath, row.path))) === row.contentHash;
146634
+ return sha256(readFileSync4(join10(projectPath, row.path))) === row.contentHash;
146592
146635
  } catch {
146593
146636
  return false;
146594
146637
  }
@@ -147659,7 +147702,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
147659
147702
  });
147660
147703
  const messagesResponse = await args.client.session.messages({
147661
147704
  path: { id: agentSessionId },
147662
- query: { directory: args.sessionDirectory }
147705
+ query: { directory: args.sessionDirectory, limit: 50 }
147663
147706
  });
147664
147707
  const messages = normalizeSDKResponse(messagesResponse, [], {
147665
147708
  preferResponseOnMissingData: true
@@ -147785,7 +147828,7 @@ function logWithStackHead(message, stackHead) {
147785
147828
  log(message, stackHead ? { stackHead } : undefined);
147786
147829
  }
147787
147830
  function getOpenCodeDbPath2() {
147788
- return join10(getDataDir(), "opencode", "opencode.db");
147831
+ return join11(getDataDir(), "opencode", "opencode.db");
147789
147832
  }
147790
147833
  function openOpenCodeDb() {
147791
147834
  const dbPath = getOpenCodeDbPath2();
@@ -147920,8 +147963,8 @@ async function runDream(args) {
147920
147963
  try {
147921
147964
  const docsDir = args.sessionDirectory ?? args.projectIdentity;
147922
147965
  const existingDocs = taskName === "maintain-docs" ? {
147923
- architecture: existsSync8(join10(docsDir, "ARCHITECTURE.md")),
147924
- structure: existsSync8(join10(docsDir, "STRUCTURE.md"))
147966
+ architecture: existsSync8(join11(docsDir, "ARCHITECTURE.md")),
147967
+ structure: existsSync8(join11(docsDir, "STRUCTURE.md"))
147925
147968
  } : undefined;
147926
147969
  const userMemories = taskName === "archive-stale" ? getActiveUserMemories(args.db).map((um) => ({
147927
147970
  id: um.id,
@@ -147965,7 +148008,7 @@ async function runDream(args) {
147965
148008
  }
147966
148009
  const messagesResponse = await args.client.session.messages({
147967
148010
  path: { id: agentSessionId },
147968
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
148011
+ query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
147969
148012
  });
147970
148013
  const messages = normalizeSDKResponse(messagesResponse, [], {
147971
148014
  preferResponseOnMissingData: true
@@ -148288,7 +148331,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
148288
148331
  });
148289
148332
  const messagesResponse = await args.client.session.messages({
148290
148333
  path: { id: agentSessionId },
148291
- query: { directory: args.sessionDirectory ?? args.projectIdentity }
148334
+ query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
148292
148335
  });
148293
148336
  const messages = normalizeSDKResponse(messagesResponse, [], {
148294
148337
  preferResponseOnMissingData: true
@@ -163109,7 +163152,7 @@ function getEmbeddingProviderIdentity(config2) {
163109
163152
  // ../plugin/src/features/magic-context/memory/embedding-local.ts
163110
163153
  import { mkdirSync as mkdirSync4 } from "node:fs";
163111
163154
  import { open, stat, unlink, writeFile } from "node:fs/promises";
163112
- import { dirname as dirname4, join as join11 } from "node:path";
163155
+ import { dirname as dirname4, join as join12 } from "node:path";
163113
163156
  import { pathToFileURL } from "node:url";
163114
163157
  init_data_path();
163115
163158
  init_logger();
@@ -163176,7 +163219,7 @@ async function injectWasmOrtForElectron() {
163176
163219
  const { createRequire: createRequireFn } = await import("node:module");
163177
163220
  const requireFn = createRequireFn(import.meta.url);
163178
163221
  const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
163179
- const distDir = join11(dirname4(pkgPath), "dist");
163222
+ const distDir = join12(dirname4(pkgPath), "dist");
163180
163223
  const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
163181
163224
  if (ortWeb.env?.wasm) {
163182
163225
  ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
@@ -163287,7 +163330,7 @@ class LocalEmbeddingProvider {
163287
163330
  if (LogLevel && "ERROR" in LogLevel) {
163288
163331
  env.logLevel = LogLevel.ERROR;
163289
163332
  }
163290
- const modelCacheDir = join11(getMagicContextStorageDir(), "models");
163333
+ const modelCacheDir = join12(getMagicContextStorageDir(), "models");
163291
163334
  try {
163292
163335
  mkdirSync4(modelCacheDir, { recursive: true });
163293
163336
  env.cacheDir = modelCacheDir;
@@ -163295,7 +163338,7 @@ class LocalEmbeddingProvider {
163295
163338
  log("[magic-context] could not create model cache dir, using library default");
163296
163339
  }
163297
163340
  const createPipeline = transformersModule.pipeline;
163298
- const lockPath = join11(modelCacheDir, ".load.lock");
163341
+ const lockPath = join12(modelCacheDir, ".load.lock");
163299
163342
  const releaseLock = await acquireModelLoadLock(lockPath);
163300
163343
  const stopHeartbeat = startLockHeartbeat(lockPath);
163301
163344
  try {
@@ -164649,7 +164692,7 @@ function handleUntrustedLoad(db, projectIdentity, directory, detailed) {
164649
164692
  // src/config/index.ts
164650
164693
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
164651
164694
  import { homedir as homedir5 } from "node:os";
164652
- import { join as join12 } from "node:path";
164695
+ import { join as join13 } from "node:path";
164653
164696
 
164654
164697
  // ../plugin/src/config/variable.ts
164655
164698
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
@@ -164717,12 +164760,12 @@ function substituteConfigVariables(input) {
164717
164760
  var import_comment_json2 = __toESM(require_src2(), 1);
164718
164761
  var CONFIG_FILE_NAME = "magic-context";
164719
164762
  function getProjectConfigPaths(cwd) {
164720
- const basePath = join12(cwd, ".pi", CONFIG_FILE_NAME);
164763
+ const basePath = join13(cwd, ".pi", CONFIG_FILE_NAME);
164721
164764
  return [`${basePath}.jsonc`, `${basePath}.json`];
164722
164765
  }
164723
164766
  function getUserConfigPaths() {
164724
164767
  const home = process.env.HOME ?? homedir5();
164725
- const basePath = join12(home, ".pi", "agent", CONFIG_FILE_NAME);
164768
+ const basePath = join13(home, ".pi", "agent", CONFIG_FILE_NAME);
164726
164769
  return [`${basePath}.jsonc`, `${basePath}.json`];
164727
164770
  }
164728
164771
  function resolveFirstExisting(paths) {
@@ -164952,7 +164995,6 @@ function loadPiConfigDetailed(opts = {}) {
164952
164995
  // src/embedding-bootstrap.ts
164953
164996
  async function ensureProjectRegisteredFromPiDirectory(directory, db) {
164954
164997
  const projectIdentity = resolveProjectIdentity(directory);
164955
- invalidateProject(projectIdentity);
164956
164998
  const detailed = loadPiConfigDetailed({ cwd: directory });
164957
164999
  if (isConfigLoadUntrusted(detailed)) {
164958
165000
  handleUntrustedLoad(db, projectIdentity, directory, detailed);
@@ -164971,6 +165013,7 @@ var sessionsById = new Map;
164971
165013
  var inFlightDreams = new Set;
164972
165014
  var sessionCounter = 0;
164973
165015
  var piSubagentRunnerFactory = () => new PiSubagentRunner;
165016
+ var startDreamScheduleTimerFn = startDreamScheduleTimer;
164974
165017
  function registerPiDreamerProject(opts) {
164975
165018
  if (!opts.config.enabled) {
164976
165019
  return;
@@ -164991,7 +165034,8 @@ function registerPiDreamerProject(opts) {
164991
165034
  min_reads: opts.config.pin_key_files.min_reads
164992
165035
  } : undefined;
164993
165036
  let cleanup;
164994
- startDreamScheduleTimer({
165037
+ let cancelled = false;
165038
+ startDreamScheduleTimerFn({
164995
165039
  directory: opts.projectDir,
164996
165040
  projectIdentity: opts.projectIdentity,
164997
165041
  client,
@@ -165001,6 +165045,10 @@ function registerPiDreamerProject(opts) {
165001
165045
  gitCommitIndexing: opts.gitCommitIndexing,
165002
165046
  ensureRegistered: ensureProjectRegisteredFromPiDirectory
165003
165047
  }).then((timerCleanup) => {
165048
+ if (cancelled) {
165049
+ timerCleanup?.();
165050
+ return;
165051
+ }
165004
165052
  cleanup = timerCleanup;
165005
165053
  });
165006
165054
  const runOnce = async () => processDreamQueue({
@@ -165014,7 +165062,10 @@ function registerPiDreamerProject(opts) {
165014
165062
  projectIdentity: opts.projectIdentity
165015
165063
  });
165016
165064
  registeredProjects2.set(opts.projectIdentity, {
165017
- cleanup: () => cleanup?.(),
165065
+ cleanup: () => {
165066
+ cancelled = true;
165067
+ cleanup?.();
165068
+ },
165018
165069
  runOnce
165019
165070
  });
165020
165071
  }
@@ -165086,6 +165137,7 @@ function createPiDreamerClient(opts) {
165086
165137
  { type: "text", text: result.assistantText }
165087
165138
  ])
165088
165139
  ];
165140
+ opts.onAdjunctsRefreshNeeded?.(opts.projectIdentity);
165089
165141
  } finally {
165090
165142
  inFlightDreams.delete(runPromise);
165091
165143
  }
@@ -165189,21 +165241,37 @@ function sendCtxStatusMessage(pi, content, details) {
165189
165241
  function registerCtxDreamCommand(pi, deps) {
165190
165242
  pi.registerCommand("ctx-dream", {
165191
165243
  description: "Run a Magic Context dreamer cycle for this project now",
165192
- handler: async () => {
165193
- const enqueued = enqueueDream(deps.db, deps.projectIdentity, "manual");
165244
+ handler: async (_args, ctx) => {
165245
+ const project = deps.resolveProject?.(ctx) ?? {
165246
+ projectDir: deps.projectDir,
165247
+ projectIdentity: deps.projectIdentity
165248
+ };
165249
+ deps.onProjectSeen?.(project.projectIdentity);
165250
+ if (deps.dreamerEnabled === false) {
165251
+ sendCtxStatusMessage(pi, {
165252
+ title: "/ctx-dream",
165253
+ text: "## /ctx-dream\n\nDreamer is not configured for this project (`dreamer.enabled=false`).",
165254
+ level: "info"
165255
+ }, {
165256
+ projectDir: project.projectDir,
165257
+ projectIdentity: project.projectIdentity
165258
+ });
165259
+ return;
165260
+ }
165261
+ const enqueued = enqueueDream(deps.db, project.projectIdentity, "manual", true);
165194
165262
  if (!enqueued) {
165195
165263
  sendCtxStatusMessage(pi, {
165196
165264
  title: "/ctx-dream",
165197
165265
  text: [
165198
165266
  "## /ctx-dream",
165199
165267
  "",
165200
- `Dream already queued or running for ${deps.projectIdentity}.`
165268
+ `Dream already queued or running for ${project.projectIdentity}.`
165201
165269
  ].join(`
165202
165270
  `),
165203
165271
  level: "info"
165204
165272
  }, {
165205
- projectDir: deps.projectDir,
165206
- projectIdentity: deps.projectIdentity,
165273
+ projectDir: project.projectDir,
165274
+ projectIdentity: project.projectIdentity,
165207
165275
  entry: null
165208
165276
  });
165209
165277
  return;
@@ -165213,18 +165281,18 @@ function registerCtxDreamCommand(pi, deps) {
165213
165281
  text: [
165214
165282
  "## /ctx-dream",
165215
165283
  "",
165216
- `Starting dream run #${enqueued.id} for ${deps.projectIdentity}…`,
165217
- `Project directory: ${deps.projectDir}`
165284
+ `Starting dream run #${enqueued.id} for ${project.projectIdentity}…`,
165285
+ `Project directory: ${project.projectDir}`
165218
165286
  ].join(`
165219
165287
  `),
165220
165288
  level: "info"
165221
165289
  }, {
165222
- projectDir: deps.projectDir,
165223
- projectIdentity: deps.projectIdentity,
165290
+ projectDir: project.projectDir,
165291
+ projectIdentity: project.projectIdentity,
165224
165292
  entry: enqueued
165225
165293
  });
165226
165294
  try {
165227
- const result = await runPiDreamForProject(deps.projectIdentity);
165295
+ const result = await runPiDreamForProject(project.projectIdentity);
165228
165296
  let summary;
165229
165297
  if (!result) {
165230
165298
  summary = "Dream queued, but another worker is already processing the queue.";
@@ -165250,13 +165318,13 @@ function registerCtxDreamCommand(pi, deps) {
165250
165318
  `),
165251
165319
  level: result ? "success" : "info"
165252
165320
  }, {
165253
- projectDir: deps.projectDir,
165254
- projectIdentity: deps.projectIdentity,
165321
+ projectDir: project.projectDir,
165322
+ projectIdentity: project.projectIdentity,
165255
165323
  entry: enqueued
165256
165324
  });
165257
165325
  } catch (error51) {
165258
165326
  const message = error51 instanceof Error ? error51.message : String(error51);
165259
- sessionLog(deps.projectIdentity, `/ctx-dream failed to drain queue: ${message}`);
165327
+ sessionLog(project.projectIdentity, `/ctx-dream failed to drain queue: ${message}`);
165260
165328
  sendCtxStatusMessage(pi, {
165261
165329
  title: "/ctx-dream",
165262
165330
  text: [
@@ -165268,8 +165336,8 @@ function registerCtxDreamCommand(pi, deps) {
165268
165336
  `),
165269
165337
  level: "error"
165270
165338
  }, {
165271
- projectDir: deps.projectDir,
165272
- projectIdentity: deps.projectIdentity,
165339
+ projectDir: project.projectDir,
165340
+ projectIdentity: project.projectIdentity,
165273
165341
  entry: enqueued
165274
165342
  });
165275
165343
  }
@@ -167526,8 +167594,31 @@ function collectUserPromptParts(message) {
167526
167594
  function hasStackedAugmentation(rawText) {
167527
167595
  return rawText.includes("<sidekick-augmentation>") || rawText.includes("<ctx-search-hint>") || rawText.includes("<ctx-search-auto>");
167528
167596
  }
167597
+ function stripNestedSystemReminders(text) {
167598
+ const OPEN = "<system-reminder>";
167599
+ const CLOSE = "</system-reminder>";
167600
+ let result = "";
167601
+ let depth = 0;
167602
+ let i = 0;
167603
+ while (i < text.length) {
167604
+ if (text.startsWith(OPEN, i)) {
167605
+ depth += 1;
167606
+ i += OPEN.length;
167607
+ } else if (text.startsWith(CLOSE, i)) {
167608
+ if (depth > 0)
167609
+ depth -= 1;
167610
+ i += CLOSE.length;
167611
+ } else if (depth === 0) {
167612
+ result += text[i];
167613
+ i += 1;
167614
+ } else {
167615
+ i += 1;
167616
+ }
167617
+ }
167618
+ return result;
167619
+ }
167529
167620
  function extractUserPromptText(message) {
167530
- return collectUserPromptParts(message).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
167621
+ return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<!--\s*ALFONSO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
167531
167622
  `).replace(/\n{3,}/g, `
167532
167623
 
167533
167624
  `).trim();
@@ -167769,7 +167860,8 @@ var DEDUP_SAFE_TOOLS = new Set([
167769
167860
  ]);
167770
167861
  function buildPiToolFingerprints(messages) {
167771
167862
  const fingerprints2 = new Map;
167772
- for (const message of messages) {
167863
+ for (let i = 0;i < messages.length; i++) {
167864
+ const message = messages[i];
167773
167865
  if (!message || typeof message !== "object")
167774
167866
  continue;
167775
167867
  const msg = message;
@@ -167777,6 +167869,7 @@ function buildPiToolFingerprints(messages) {
167777
167869
  continue;
167778
167870
  if (!Array.isArray(msg.content))
167779
167871
  continue;
167872
+ const ownerMsgId = typeof msg.timestamp === "number" ? `pi-msg-${i}-${msg.timestamp}-assistant` : `pi-msg-${i}-assistant`;
167780
167873
  for (const part of msg.content) {
167781
167874
  if (!part || typeof part !== "object")
167782
167875
  continue;
@@ -167799,7 +167892,9 @@ function buildPiToolFingerprints(messages) {
167799
167892
  } catch {
167800
167893
  continue;
167801
167894
  }
167802
- fingerprints2.set(p.id, `${p.name}:${serialized}`);
167895
+ const fingerprint = `${ownerMsgId}:${p.name}:${serialized}`;
167896
+ const compositeKey = `${ownerMsgId}\x00${p.id}`;
167897
+ fingerprints2.set(compositeKey, fingerprint);
167803
167898
  }
167804
167899
  }
167805
167900
  return fingerprints2;
@@ -167917,15 +168012,16 @@ function applyPiHeuristicCleanup(sessionId, db, targets, piMessages, config2, pr
167917
168012
  })();
167918
168013
  const toolFingerprints = buildPiToolFingerprints(piMessages);
167919
168014
  if (toolFingerprints.size > 0) {
167920
- const tagsByMessageId = new Map;
168015
+ const tagsByCompositeKey = new Map;
167921
168016
  for (const tag of tags) {
167922
168017
  if (tag.type === "tool" && tag.status === "active" && tag.messageId) {
167923
- tagsByMessageId.set(tag.messageId, tag);
168018
+ const key = tag.toolOwnerMessageId ? `${tag.toolOwnerMessageId}\x00${tag.messageId}` : tag.messageId;
168019
+ tagsByCompositeKey.set(key, tag);
167924
168020
  }
167925
168021
  }
167926
168022
  const fingerprintGroups = new Map;
167927
- for (const [callId, fingerprint] of toolFingerprints) {
167928
- const tag = tagsByMessageId.get(callId);
168023
+ for (const [compositeKey, fingerprint] of toolFingerprints) {
168024
+ const tag = tagsByCompositeKey.get(compositeKey);
167929
168025
  if (!tag || tag.tagNumber > protectedCutoff)
167930
168026
  continue;
167931
168027
  const group = fingerprintGroups.get(fingerprint) ?? [];
@@ -169169,7 +169265,7 @@ function getReducedRecompTokenBudget(currentBudget) {
169169
169265
  // ../plugin/src/hooks/magic-context/historian-state-file.ts
169170
169266
  init_data_path();
169171
169267
  import { mkdirSync as mkdirSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
169172
- import { join as join13 } from "node:path";
169268
+ import { join as join14 } from "node:path";
169173
169269
  var HISTORIAN_STATE_INLINE_THRESHOLD = 30000;
169174
169270
  function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
169175
169271
  if (existingState.length <= HISTORIAN_STATE_INLINE_THRESHOLD)
@@ -169177,7 +169273,7 @@ function maybeWriteHistorianStateFile(sessionId, existingState, directory) {
169177
169273
  try {
169178
169274
  const dir = getProjectMagicContextHistorianDir(directory);
169179
169275
  mkdirSync5(dir, { recursive: true });
169180
- const path5 = join13(dir, `state-${sessionId}-${Date.now()}.xml`);
169276
+ const path5 = join14(dir, `state-${sessionId}-${Date.now()}.xml`);
169181
169277
  writeFileSync2(path5, existingState, "utf8");
169182
169278
  return path5;
169183
169279
  } catch {
@@ -169877,8 +169973,18 @@ function injectSyntheticTodowriteForPi(args) {
169877
169973
  }
169878
169974
 
169879
169975
  // src/reasoning-replay-pi.ts
169880
- var INLINE_THINKING_PATTERN = /<thinking>[\s\S]*?<\/thinking>\s*/gi;
169976
+ var INLINE_THINKING_PATTERNS = [
169977
+ /<thinking>[\s\S]*?<\/thinking>\s*/gi,
169978
+ /<think>[\s\S]*?<\/think>\s*/gi
169979
+ ];
169881
169980
  var CLEARED = "[cleared]";
169981
+ function stripInlineThinkingMarkup(text) {
169982
+ let cleaned = text;
169983
+ for (const pattern of INLINE_THINKING_PATTERNS) {
169984
+ cleaned = cleaned.replace(pattern, "");
169985
+ }
169986
+ return cleaned;
169987
+ }
169882
169988
  function buildMessageIdToMaxTag(targets) {
169883
169989
  const out = new Map;
169884
169990
  for (const [tagNumber, target] of targets) {
@@ -169931,6 +170037,51 @@ function clearOldReasoningPi(args) {
169931
170037
  }
169932
170038
  return { cleared, newWatermark };
169933
170039
  }
170040
+ function stripInlineThinkingPi(args) {
170041
+ const { messages, messageIdToMaxTag, clearReasoningAge, piMessageStableId } = args;
170042
+ let maxTag = 0;
170043
+ for (const t of messageIdToMaxTag.values())
170044
+ if (t > maxTag)
170045
+ maxTag = t;
170046
+ if (maxTag === 0)
170047
+ return { stripped: 0, newWatermark: 0 };
170048
+ const ageCutoff = maxTag - clearReasoningAge;
170049
+ if (ageCutoff <= 0)
170050
+ return { stripped: 0, newWatermark: 0 };
170051
+ let stripped = 0;
170052
+ let newWatermark = 0;
170053
+ for (let i = 0;i < messages.length; i++) {
170054
+ const raw = messages[i];
170055
+ if (!raw || typeof raw !== "object")
170056
+ continue;
170057
+ const msg = raw;
170058
+ if (msg.role !== "assistant" || !Array.isArray(msg.content))
170059
+ continue;
170060
+ const id = piMessageStableId(raw, i);
170061
+ if (!id)
170062
+ continue;
170063
+ const msgTag = messageIdToMaxTag.get(id) ?? 0;
170064
+ if (msgTag === 0 || msgTag > ageCutoff)
170065
+ continue;
170066
+ let strippedThisMessage = false;
170067
+ for (const part of msg.content) {
170068
+ if (part && typeof part === "object" && part.type === "text") {
170069
+ const tp = part;
170070
+ if (typeof tp.text !== "string")
170071
+ continue;
170072
+ const cleaned = stripInlineThinkingMarkup(tp.text);
170073
+ if (cleaned !== tp.text) {
170074
+ tp.text = cleaned;
170075
+ stripped++;
170076
+ strippedThisMessage = true;
170077
+ }
170078
+ }
170079
+ }
170080
+ if (strippedThisMessage && msgTag > newWatermark)
170081
+ newWatermark = msgTag;
170082
+ }
170083
+ return { stripped, newWatermark };
170084
+ }
169934
170085
  function replayClearedReasoningPi(args) {
169935
170086
  const { db, sessionId, messages, messageIdToMaxTag, piMessageStableId } = args;
169936
170087
  const meta3 = getOrCreateSessionMeta(db, sessionId);
@@ -169988,7 +170139,7 @@ function replayStrippedInlineThinkingPi(args) {
169988
170139
  const tp = part;
169989
170140
  if (typeof tp.text !== "string")
169990
170141
  continue;
169991
- const cleaned = tp.text.replace(INLINE_THINKING_PATTERN, "");
170142
+ const cleaned = stripInlineThinkingMarkup(tp.text);
169992
170143
  if (cleaned !== tp.text) {
169993
170144
  tp.text = cleaned;
169994
170145
  stripped++;
@@ -170079,6 +170230,408 @@ function stripPiDroppedPlaceholderMessages(args) {
170079
170230
  return { removed, discovered };
170080
170231
  }
170081
170232
 
170233
+ // src/system-prompt.ts
170234
+ import { createHash as createHash8 } from "node:crypto";
170235
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
170236
+ import { join as join16 } from "node:path";
170237
+
170238
+ // ../plugin/src/agents/magic-context-prompt.ts
170239
+ function getToolHistoryGuidance(dropToolStructure) {
170240
+ if (dropToolStructure) {
170241
+ return `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.`;
170242
+ }
170243
+ return `Older tool calls in your conversation show truncated inputs and [truncated] outputs — this is normal context management, not a pattern to follow. The original tool calls executed successfully with full inputs and produced real outputs that were later cleaned up to save context. ALWAYS use real tool calls with complete arguments; never copy truncated patterns like "filePa...[truncated]" into your tool inputs. If you need to re-read a file or re-run a command, make a fresh tool call.`;
170244
+ }
170245
+ var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
170246
+ Use \`ctx_reduce\` to manage context size. It supports one operation:
170247
+ - \`drop\`: Remove entirely (best for tool outputs you already acted on).
170248
+ Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
170249
+ Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
170250
+ Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
170251
+ **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:
170252
+ - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
170253
+ - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
170254
+ - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
170255
+ Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
170256
+ Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170257
+ **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:
170258
+ - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
170259
+ - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
170260
+ - Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
170261
+ - Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
170262
+ - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
170263
+ \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
170264
+ ${getToolHistoryGuidance(dropToolStructure)}
170265
+ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
170266
+ NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
170267
+ NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
170268
+ Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
170269
+ var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
170270
+ Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
170271
+ **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:
170272
+ - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
170273
+ - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
170274
+ - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
170275
+ Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
170276
+ Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170277
+ **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:
170278
+ - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
170279
+ - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
170280
+ - Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
170281
+ - Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
170282
+ - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
170283
+ \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
170284
+ ${getToolHistoryGuidance(dropToolStructure)}`;
170285
+ var GENERIC_SECTION = `
170286
+ ### Reduction Triggers
170287
+ - After reading files or search results you already acted on — drop raw outputs.
170288
+ - After completing a logical step — drop intermediate outputs from that step.
170289
+ - Between major context switches — when moving to a new task area.
170290
+
170291
+ ### What to Drop
170292
+ - Large file reads, grep results, and tool outputs you already used.
170293
+ - Large build/test output after you analyzed and acted on it.
170294
+ - Old diagnostic or exploration results that are no longer relevant.
170295
+
170296
+ ### What to Keep
170297
+ - ALL user messages and assistant conversation text — these are cheap and compartmentalized automatically.
170298
+ - Your current task requirements and constraints.
170299
+ - Recent errors and unresolved decisions.
170300
+ - Active work context and files being edited.`;
170301
+ var TEMPORAL_AWARENESS_GUIDANCE = `
170302
+ **Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
170303
+ var CAVEMAN_COMPRESSION_WARNING = `
170304
+ **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.`;
170305
+ function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false) {
170306
+ const smartNoteGuidance = dreamerEnabled ? `
170307
+ When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
170308
+ The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
170309
+ Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
170310
+ const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
170311
+ const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
170312
+ if (!ctxReduceEnabled) {
170313
+ return `## Magic Context
170314
+
170315
+ ${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
170316
+ }
170317
+ return `## Magic Context
170318
+
170319
+ ${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
170320
+ ${GENERIC_SECTION}
170321
+
170322
+ Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
170323
+ }
170324
+
170325
+ // ../plugin/src/hooks/magic-context/key-files-block.ts
170326
+ import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
170327
+ import { join as join15, sep as sep2 } from "node:path";
170328
+ init_logger();
170329
+ var cachedKeyFilesBySession = new Map;
170330
+ var staleUpdates = new Map;
170331
+ function staleKey(update) {
170332
+ return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
170333
+ }
170334
+ function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
170335
+ const update = { projectPath, path: path5, generatedAtWitness, staleReason };
170336
+ staleUpdates.set(staleKey(update), update);
170337
+ }
170338
+ function flushStaleUpdates(db) {
170339
+ if (staleUpdates.size === 0)
170340
+ return 0;
170341
+ const updates = [...staleUpdates.values()];
170342
+ staleUpdates.clear();
170343
+ const stmt = db.prepare(`UPDATE project_key_files
170344
+ SET stale_reason = ?1
170345
+ WHERE project_path = ?2
170346
+ AND path = ?3
170347
+ AND generated_at = ?4
170348
+ AND (stale_reason IS NULL OR stale_reason != ?1)`);
170349
+ let changed = 0;
170350
+ for (const update of updates) {
170351
+ try {
170352
+ changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
170353
+ } catch (error51) {
170354
+ log("[key-files] flushStaleUpdates failed:", error51);
170355
+ }
170356
+ }
170357
+ return changed;
170358
+ }
170359
+ function clearKeyFilesCacheForSession(sessionId) {
170360
+ cachedKeyFilesBySession.delete(sessionId);
170361
+ }
170362
+ function isUnderProject(projectPath, absPath) {
170363
+ const root = realpathSync3(projectPath);
170364
+ return absPath.startsWith(root + sep2) || absPath === root;
170365
+ }
170366
+ function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
170367
+ if (!config2.enabled)
170368
+ return null;
170369
+ if (!isAftAvailable())
170370
+ return null;
170371
+ const rows = readCurrentKeyFiles(db, projectPath);
170372
+ if (rows.length === 0)
170373
+ return null;
170374
+ for (const row of rows) {
170375
+ if (row.staleReason !== null)
170376
+ continue;
170377
+ let nextStale = null;
170378
+ let observed = false;
170379
+ try {
170380
+ const absPath = join15(projectPath, row.path);
170381
+ const real = realpathSync3(absPath);
170382
+ if (!isUnderProject(projectPath, real)) {
170383
+ nextStale = "missing";
170384
+ observed = true;
170385
+ } else {
170386
+ const diskHash = sha256(readFileSync7(real));
170387
+ if (diskHash !== row.contentHash)
170388
+ nextStale = "content_drift";
170389
+ observed = true;
170390
+ }
170391
+ } catch (error51) {
170392
+ const code = error51?.code;
170393
+ if (code === "ENOENT" || code === "ELOOP") {
170394
+ nextStale = "missing";
170395
+ observed = true;
170396
+ } else {
170397
+ log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
170398
+ }
170399
+ }
170400
+ if (observed && nextStale !== null) {
170401
+ queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
170402
+ }
170403
+ }
170404
+ const blocks = [];
170405
+ let used = 0;
170406
+ for (const row of rows) {
170407
+ if (used + row.localTokenEstimate > config2.tokenBudget)
170408
+ break;
170409
+ blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
170410
+ ${escapeXmlContent(row.content)}
170411
+ </key-file>`);
170412
+ used += row.localTokenEstimate;
170413
+ }
170414
+ if (blocks.length === 0)
170415
+ return null;
170416
+ const rendered = `<key-files>
170417
+ ${blocks.join(`
170418
+ `)}
170419
+ </key-files>`;
170420
+ flushStaleUpdates(db);
170421
+ return rendered;
170422
+ }
170423
+ function readVersionedKeyFiles(args) {
170424
+ const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
170425
+ if (args.sessionMeta.isSubagent)
170426
+ return null;
170427
+ if (!config2.enabled)
170428
+ return null;
170429
+ if (!isAftAvailable())
170430
+ return null;
170431
+ const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
170432
+ if (!projectPath)
170433
+ return null;
170434
+ const currentVersion = getKeyFilesVersion(args.db, projectPath);
170435
+ if (args.sessionId) {
170436
+ const cached2 = cachedKeyFilesBySession.get(args.sessionId);
170437
+ if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
170438
+ return cached2.value;
170439
+ }
170440
+ }
170441
+ const value = buildKeyFilesBlock(args.db, projectPath, config2);
170442
+ if (args.sessionId) {
170443
+ cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
170444
+ if (value)
170445
+ sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
170446
+ }
170447
+ return value;
170448
+ }
170449
+
170450
+ // src/system-prompt.ts
170451
+ init_logger();
170452
+ var PROJECT_DOCS_MARKER = "<project-docs>";
170453
+ var USER_PROFILE_MARKER = "<user-profile>";
170454
+ var KEY_FILES_MARKER = "<key-files>";
170455
+ var MAGIC_CONTEXT_MARKER = "## Magic Context";
170456
+ var stickyDateBySession = new Map;
170457
+ var cachedDocsBySession = new Map;
170458
+ var cachedUserProfileBySession = new Map;
170459
+ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
170460
+ function readProjectDocs(directory) {
170461
+ const sections = [];
170462
+ for (const filename of DOC_FILES) {
170463
+ const filePath = join16(directory, filename);
170464
+ try {
170465
+ if (existsSync11(filePath)) {
170466
+ const content = readFileSync8(filePath, "utf-8").trim();
170467
+ if (content.length > 0) {
170468
+ sections.push(`<${filename}>
170469
+ ${escapeXmlContent(content)}
170470
+ </${filename}>`);
170471
+ }
170472
+ }
170473
+ } catch (error51) {
170474
+ log(`[magic-context-pi] failed to read ${filename}:`, error51);
170475
+ }
170476
+ }
170477
+ if (sections.length === 0)
170478
+ return null;
170479
+ return `${PROJECT_DOCS_MARKER}
170480
+ ${sections.join(`
170481
+
170482
+ `)}
170483
+ </project-docs>`;
170484
+ }
170485
+ function buildUserProfileBlock(db) {
170486
+ const memories = getActiveUserMemories(db);
170487
+ if (memories.length === 0)
170488
+ return null;
170489
+ const items = memories.map((m) => `- ${escapeXmlContent(m.content)}`).join(`
170490
+ `);
170491
+ return `${USER_PROFILE_MARKER}
170492
+ ${items}
170493
+ </user-profile>`;
170494
+ }
170495
+ function buildMagicContextBlock(opts) {
170496
+ const sections = [];
170497
+ const sessionId = opts.sessionId;
170498
+ const isCacheBusting = opts.isCacheBusting ?? false;
170499
+ const existing = opts.existingSystemPrompt ?? "";
170500
+ if (opts.injectDocs && !existing.includes(PROJECT_DOCS_MARKER)) {
170501
+ const docsBlock = readCachedAdjunct({
170502
+ cache: cachedDocsBySession,
170503
+ sessionId,
170504
+ isCacheBusting,
170505
+ compute: () => readProjectDocs(opts.cwd),
170506
+ describe: "project docs"
170507
+ });
170508
+ if (docsBlock)
170509
+ sections.push(docsBlock);
170510
+ }
170511
+ if (opts.userMemoriesEnabled && !existing.includes(USER_PROFILE_MARKER)) {
170512
+ const profileBlock = readCachedAdjunct({
170513
+ cache: cachedUserProfileBySession,
170514
+ sessionId,
170515
+ isCacheBusting,
170516
+ compute: () => buildUserProfileBlock(opts.db),
170517
+ describe: "user profile"
170518
+ });
170519
+ if (profileBlock)
170520
+ sections.push(profileBlock);
170521
+ }
170522
+ if (opts.pinKeyFilesEnabled && sessionId && !existing.includes(KEY_FILES_MARKER)) {
170523
+ let sessionMeta = null;
170524
+ try {
170525
+ sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
170526
+ } catch (error51) {
170527
+ sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
170528
+ }
170529
+ const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
170530
+ db: opts.db,
170531
+ sessionId,
170532
+ sessionMeta,
170533
+ directory: opts.cwd,
170534
+ isCacheBusting,
170535
+ config: {
170536
+ enabled: opts.pinKeyFilesEnabled,
170537
+ tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
170538
+ }
170539
+ }) : null;
170540
+ if (keyFilesBlock)
170541
+ sections.push(keyFilesBlock);
170542
+ }
170543
+ const dataBlock = sections.length > 0 ? `<magic-context>
170544
+ ${sections.join(`
170545
+
170546
+ `)}
170547
+ </magic-context>` : null;
170548
+ const includeGuidance = (opts.includeGuidance ?? true) && !existing.includes(MAGIC_CONTEXT_MARKER);
170549
+ if (!includeGuidance) {
170550
+ return dataBlock;
170551
+ }
170552
+ const guidance = buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.dropToolStructure ?? true, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false);
170553
+ if (dataBlock) {
170554
+ return `${guidance}
170555
+
170556
+ ${dataBlock}`;
170557
+ }
170558
+ return guidance;
170559
+ }
170560
+ function readCachedAdjunct(args) {
170561
+ const { cache, sessionId, isCacheBusting, compute, describe: describe3 } = args;
170562
+ if (!sessionId) {
170563
+ return compute();
170564
+ }
170565
+ const hasCached = cache.has(sessionId);
170566
+ if (!hasCached || isCacheBusting) {
170567
+ const value = compute();
170568
+ cache.set(sessionId, value);
170569
+ if (value && !hasCached) {
170570
+ sessionLog(sessionId, `loaded ${describe3} (${value.length} chars)`);
170571
+ } else if (value && isCacheBusting) {
170572
+ sessionLog(sessionId, `refreshed ${describe3} (cache-busting pass)`);
170573
+ }
170574
+ return value;
170575
+ }
170576
+ return cache.get(sessionId) ?? null;
170577
+ }
170578
+ var DATE_PATTERN = /Today's date: .+/;
170579
+ function processSystemPromptForCache(args) {
170580
+ const { db, sessionId, systemPrompt, isCacheBusting } = args;
170581
+ let sessionMeta;
170582
+ try {
170583
+ sessionMeta = getOrCreateSessionMeta(db, sessionId);
170584
+ } catch (error51) {
170585
+ sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
170586
+ }
170587
+ const previousHash = sessionMeta?.systemPromptHash ?? "";
170588
+ const isFirstHash = previousHash === "" || previousHash === "0";
170589
+ let frozenPrompt = systemPrompt;
170590
+ const dateMatch = systemPrompt.match(DATE_PATTERN);
170591
+ const liveDate = dateMatch ? dateMatch[0] : null;
170592
+ const stickyDate = stickyDateBySession.get(sessionId);
170593
+ if (liveDate && !stickyDate) {
170594
+ stickyDateBySession.set(sessionId, liveDate);
170595
+ } else if (liveDate && stickyDate && liveDate !== stickyDate) {
170596
+ if (isCacheBusting) {
170597
+ stickyDateBySession.set(sessionId, liveDate);
170598
+ sessionLog(sessionId, `system prompt date updated: ${stickyDate} → ${liveDate} (cache-busting pass)`);
170599
+ } else {
170600
+ frozenPrompt = systemPrompt.replace(DATE_PATTERN, stickyDate);
170601
+ sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
170602
+ }
170603
+ }
170604
+ const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
170605
+ const hashChanged = !isFirstHash && currentHash !== previousHash;
170606
+ if (hashChanged) {
170607
+ sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
170608
+ } else if (isFirstHash) {
170609
+ sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${frozenPrompt.length})`);
170610
+ }
170611
+ const systemPromptTokens = estimateTokens(frozenPrompt);
170612
+ if (sessionMeta) {
170613
+ if (currentHash !== previousHash) {
170614
+ updateSessionMeta(db, sessionId, {
170615
+ systemPromptHash: currentHash,
170616
+ systemPromptTokens
170617
+ });
170618
+ } else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
170619
+ updateSessionMeta(db, sessionId, { systemPromptTokens });
170620
+ }
170621
+ }
170622
+ return {
170623
+ systemPrompt: frozenPrompt,
170624
+ hashChanged,
170625
+ currentHash
170626
+ };
170627
+ }
170628
+ function clearPiSystemPromptSession(sessionId) {
170629
+ stickyDateBySession.delete(sessionId);
170630
+ cachedDocsBySession.delete(sessionId);
170631
+ cachedUserProfileBySession.delete(sessionId);
170632
+ clearKeyFilesCacheForSession(sessionId);
170633
+ }
170634
+
170082
170635
  // src/temporal-awareness-pi.ts
170083
170636
  function injectPiTemporalMarkers(messages) {
170084
170637
  let injected = 0;
@@ -170639,6 +171192,12 @@ var commitSeenLastPass = new Map;
170639
171192
  var historyRefreshSessions = new Set;
170640
171193
  var systemPromptRefreshSessions = new Set;
170641
171194
  var pendingMaterializationSessions = new Set;
171195
+ var deferredHistoryRefreshSessions = new Set;
171196
+ var deferredMaterializationSessions = new Set;
171197
+ var sessionsByProject = new Map;
171198
+ var lastSeenProjectIdentityBySession = new Map;
171199
+ var rawMessageProviderUnregistersBySession = new Map;
171200
+ var lastHeuristicsTurnIdBySession = new Map;
170642
171201
  var firstContextPassSeenBySession = new Set;
170643
171202
  var recentReduceBySession = new Map;
170644
171203
  var liveModelBySession = new Map;
@@ -170672,6 +171231,50 @@ function signalPiSystemPromptRefresh(sessionId) {
170672
171231
  function signalPiPendingMaterialization(sessionId) {
170673
171232
  pendingMaterializationSessions.add(sessionId);
170674
171233
  }
171234
+ function signalPiDeferredHistoryRefresh(sessionId) {
171235
+ deferredHistoryRefreshSessions.add(sessionId);
171236
+ }
171237
+ function signalPiDeferredMaterialization(sessionId) {
171238
+ deferredMaterializationSessions.add(sessionId);
171239
+ }
171240
+ function consumeDeferredHistoryRefresh(sessionId) {
171241
+ const wasSet = deferredHistoryRefreshSessions.has(sessionId);
171242
+ deferredHistoryRefreshSessions.delete(sessionId);
171243
+ return wasSet;
171244
+ }
171245
+ function consumeDeferredMaterialization(sessionId) {
171246
+ const wasSet = deferredMaterializationSessions.has(sessionId);
171247
+ deferredMaterializationSessions.delete(sessionId);
171248
+ return wasSet;
171249
+ }
171250
+ function trackSessionForProject(projectIdentity, sessionId) {
171251
+ let sessions = sessionsByProject.get(projectIdentity);
171252
+ if (!sessions) {
171253
+ sessions = new Set;
171254
+ sessionsByProject.set(projectIdentity, sessions);
171255
+ }
171256
+ sessions.add(sessionId);
171257
+ }
171258
+ function updateSessionProjectTracking(sessionId, projectIdentity) {
171259
+ const prev = lastSeenProjectIdentityBySession.get(sessionId);
171260
+ if (prev && prev !== projectIdentity) {
171261
+ const prevSessions = sessionsByProject.get(prev);
171262
+ prevSessions?.delete(sessionId);
171263
+ if (prevSessions?.size === 0)
171264
+ sessionsByProject.delete(prev);
171265
+ clearPiSystemPromptSession(sessionId);
171266
+ }
171267
+ trackSessionForProject(projectIdentity, sessionId);
171268
+ lastSeenProjectIdentityBySession.set(sessionId, projectIdentity);
171269
+ }
171270
+ function signalPiSystemPromptRefreshForProject(projectIdentity) {
171271
+ const sessions = sessionsByProject.get(projectIdentity);
171272
+ if (!sessions)
171273
+ return;
171274
+ for (const sessionId of sessions) {
171275
+ systemPromptRefreshSessions.add(sessionId);
171276
+ }
171277
+ }
170675
171278
  function recordPiLiveModel(sessionId, modelKey) {
170676
171279
  liveModelBySession.set(sessionId, modelKey);
170677
171280
  }
@@ -170748,109 +171351,55 @@ function resolveSessionId2(ctx) {
170748
171351
  return;
170749
171352
  }
170750
171353
  }
170751
- function collectMessageEntryIds(ctx, expectedLength, sessionId, strict = false) {
171354
+ function collectMessageEntryIdsByRef(ctx, messages, sessionId) {
170752
171355
  const sm = ctx.sessionManager;
170753
171356
  if (typeof sm?.getBranch !== "function")
170754
- return;
171357
+ return null;
170755
171358
  let entries;
170756
171359
  try {
170757
171360
  entries = sm.getBranch.call(sm);
170758
- } catch {
170759
- return;
171361
+ } catch (error51) {
171362
+ sessionLog(sessionId ?? "pi", `collectMessageEntryIdsByRef getBranch failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
171363
+ return null;
170760
171364
  }
170761
171365
  if (!Array.isArray(entries))
170762
- return;
170763
- let compactionIndex = -1;
170764
- let firstKeptEntryId;
170765
- for (let i = entries.length - 1;i >= 0; i--) {
170766
- const e = entries[i];
170767
- if (e && typeof e === "object" && e.type === "compaction") {
170768
- compactionIndex = i;
170769
- if (typeof e.firstKeptEntryId === "string") {
170770
- firstKeptEntryId = e.firstKeptEntryId;
170771
- }
170772
- break;
170773
- }
170774
- }
170775
- const ids = [];
170776
- const isEmitEligible = (entry) => {
171366
+ return null;
171367
+ const entryIdByMsgRef = new Map;
171368
+ for (const entry of entries) {
170777
171369
  if (!entry || typeof entry !== "object")
170778
- return false;
170779
- const t = entry.type;
170780
- const id = entry.id;
170781
- if (typeof id !== "string")
170782
- return false;
170783
- if (t === "message")
170784
- return true;
170785
- if (t === "custom_message")
170786
- return true;
170787
- if (t === "branch_summary") {
170788
- const summary = entry.summary;
170789
- return typeof summary === "string" && summary.length > 0;
170790
- }
170791
- return false;
170792
- };
170793
- if (compactionIndex >= 0) {
170794
- ids.push(undefined);
170795
- if (firstKeptEntryId !== undefined) {
170796
- let foundFirstKept = false;
170797
- for (let i = 0;i < compactionIndex; i++) {
170798
- const entry = entries[i];
170799
- const entryId = entry?.id;
170800
- if (typeof entryId === "string" && entryId === firstKeptEntryId) {
170801
- foundFirstKept = true;
170802
- }
170803
- if (!foundFirstKept)
170804
- continue;
170805
- if (isEmitEligible(entry)) {
170806
- ids.push(entry.id);
170807
- }
170808
- }
170809
- }
170810
- for (let i = compactionIndex + 1;i < entries.length; i++) {
170811
- const entry = entries[i];
170812
- if (isEmitEligible(entry)) {
170813
- ids.push(entry.id);
170814
- }
170815
- }
170816
- } else {
170817
- for (const entry of entries) {
170818
- if (isEmitEligible(entry)) {
170819
- ids.push(entry.id);
170820
- }
170821
- }
171370
+ continue;
171371
+ const e = entry;
171372
+ if (e.type !== "message")
171373
+ continue;
171374
+ if (typeof e.id !== "string")
171375
+ continue;
171376
+ if (!e.message || typeof e.message !== "object")
171377
+ continue;
171378
+ entryIdByMsgRef.set(e.message, e.id);
170822
171379
  }
170823
- if (ids.length !== expectedLength) {
170824
- const sm2 = sm;
170825
- const totalEntries = entries.length;
170826
- log(`[magic-context][pi]${sessionId ? `[${sessionId}]` : ""} collectMessageEntryIds length mismatch: ` + `expected=${expectedLength} got=${ids.length} (compactionIndex=${compactionIndex} ` + `firstKeptEntryId=${firstKeptEntryId ?? "<none>"} totalBranchEntries=${totalEntries})` + ` — best-effort mapping returned; boundary trim may not match exactly`);
170827
- if (strict)
170828
- return;
170829
- const _unused = sm2;
170830
- if (ids.length < expectedLength) {
170831
- const padded = [];
170832
- for (let i = 0;i < expectedLength - ids.length; i++) {
170833
- padded.push(undefined);
170834
- }
170835
- padded.push(...ids);
170836
- return padded;
171380
+ const result = new Array(messages.length);
171381
+ let resolved = 0;
171382
+ for (let i = 0;i < messages.length; i++) {
171383
+ const msg = messages[i];
171384
+ if (!msg || typeof msg !== "object") {
171385
+ result[i] = undefined;
171386
+ continue;
171387
+ }
171388
+ const id = entryIdByMsgRef.get(msg);
171389
+ if (typeof id === "string") {
171390
+ result[i] = id;
171391
+ resolved += 1;
171392
+ } else {
171393
+ result[i] = undefined;
170837
171394
  }
170838
- return ids.slice(ids.length - expectedLength);
170839
171395
  }
170840
- return ids;
170841
- }
170842
- function collectMessageEntryIdsStrict(ctx, expectedLength, sessionId) {
170843
- try {
170844
- return collectMessageEntryIds(ctx, expectedLength, sessionId, true) ?? null;
170845
- } catch (error51) {
170846
- sessionLog(sessionId ?? "pi", `collectMessageEntryIdsStrict failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
170847
- return null;
171396
+ if (resolved < messages.length) {
171397
+ log(`[magic-context][pi]${sessionId ? `[${sessionId}]` : ""} ` + `collectMessageEntryIdsByRef: resolved=${resolved}/${messages.length} ` + `(branchEntries=${entries.length}, messageEntries=${entryIdByMsgRef.size}) — ` + `unmapped slots fall through to synthesized ids; boundary lookup still works ` + `for any compartment whose start/end message is among the resolved set`);
170848
171398
  }
171399
+ return result;
170849
171400
  }
170850
171401
  function registerPiContextHandler(pi, options) {
170851
171402
  const tagger = createTagger();
170852
- const projectDirectory = process.cwd();
170853
- const projectIdentity = resolveProjectIdentity(projectDirectory);
170854
171403
  const schedulerConfig = options.scheduler ?? {
170855
171404
  executeThresholdPercentage: 65
170856
171405
  };
@@ -170876,14 +171425,19 @@ function registerPiContextHandler(pi, options) {
170876
171425
  return;
170877
171426
  }
170878
171427
  sessionIdForError = sessionId;
171428
+ const projectDirectory = ctx.cwd;
171429
+ const projectIdentity = resolveProjectIdentity(projectDirectory);
171430
+ updateSessionProjectTracking(sessionId, projectIdentity);
170879
171431
  logTransformTiming(sessionId, "findSessionId", tFindSession, `messages=${event.messages.length}`);
170880
171432
  const rawMessageProvider = {
170881
171433
  readMessages: () => readPiSessionMessages(ctx),
170882
171434
  readMessageById: (messageId) => readPiSessionMessageById(ctx, messageId)
170883
171435
  };
170884
- setRawMessageProvider(sessionId, rawMessageProvider);
171436
+ rawMessageProviderUnregistersBySession.get(sessionId)?.();
171437
+ const unregisterRaw = setRawMessageProvider(sessionId, rawMessageProvider);
171438
+ rawMessageProviderUnregistersBySession.set(sessionId, unregisterRaw);
170885
171439
  scheduleReconciliation(options.db, sessionId, readRawSessionMessages);
170886
- const strictEntryIds = collectMessageEntryIdsStrict(ctx, event.messages.length, sessionId);
171440
+ const strictEntryIds = collectMessageEntryIdsByRef(ctx, event.messages, sessionId);
170887
171441
  const tLastUser = performance.now();
170888
171442
  const latestUser = findLatestUserMessageIdPi(event.messages, buildPiMessageIdByIndex(event.messages, strictEntryIds));
170889
171443
  logTransformTiming(sessionId, "findLastUserMessageId", tLastUser);
@@ -170916,12 +171470,18 @@ function registerPiContextHandler(pi, options) {
170916
171470
  lastContextPercentage: 0,
170917
171471
  lastInputTokens: 0,
170918
171472
  observedSafeInputTokens: 0,
170919
- cacheAlertSent: false
171473
+ cacheAlertSent: false,
171474
+ clearedReasoningThroughTag: 0
170920
171475
  });
171476
+ clearHistorianFailureState(options.db, sessionId);
171477
+ clearPersistedReasoningWatermark(options.db, sessionId);
171478
+ clearDetectedContextLimit(options.db, sessionId);
171479
+ clearEmergencyRecovery(options.db, sessionId);
170921
171480
  sessionMetaForUsage.lastContextPercentage = 0;
170922
171481
  sessionMetaForUsage.lastInputTokens = 0;
170923
171482
  sessionMetaForUsage.observedSafeInputTokens = 0;
170924
171483
  sessionMetaForUsage.cacheAlertSent = false;
171484
+ sessionMetaForUsage.clearedReasoningThroughTag = 0;
170925
171485
  }
170926
171486
  let usagePercentage = 0;
170927
171487
  let usageInputTokens = 0;
@@ -171010,7 +171570,7 @@ function registerPiContextHandler(pi, options) {
171010
171570
  const isCacheBusting = historyRefreshSessions.has(sessionId);
171011
171571
  sessionLog(sessionId, `transform: usage=${usagePercentage.toFixed(1)}% (${usageInputTokens} tokens, limit=${usageContextLimit ?? "?"}) decision=${schedulerDecision}${forceMaterialization ? " force=true" : ""}${isEmergency ? " EMERGENCY=true" : ""}${isCacheBusting ? " busting=true" : ""}`);
171012
171572
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tEmergencyRecovery);
171013
- const entryIds = collectMessageEntryIds(ctx, event.messages.length, sessionId);
171573
+ const entryIds = collectMessageEntryIdsByRef(ctx, event.messages, sessionId) ?? undefined;
171014
171574
  const result = await runPipeline({
171015
171575
  db: options.db,
171016
171576
  tagger,
@@ -171050,11 +171610,25 @@ function registerPiContextHandler(pi, options) {
171050
171610
  isCacheBusting,
171051
171611
  usagePercentage,
171052
171612
  usageInputTokens,
171053
- usageContextLimit
171613
+ usageContextLimit,
171614
+ schedulerDecision
171054
171615
  });
171055
171616
  }
171056
171617
  const tPostTransform = performance.now();
171057
171618
  let outputMessages = result.messages;
171619
+ try {
171620
+ const cacheBustingPass = isCacheBusting || result.executedWorkThisPass;
171621
+ outputMessages = applyStickyTurnReminder({
171622
+ sessionId,
171623
+ db: options.db,
171624
+ messages: outputMessages,
171625
+ entryIds: entryIds ?? null,
171626
+ hasRecentReduceCall: recentReduceBySession.has(sessionId),
171627
+ isCacheBustingPass: cacheBustingPass
171628
+ });
171629
+ } catch (err) {
171630
+ sessionLog(sessionId, `sticky turn reminder failed: ${err instanceof Error ? err.message : String(err)}`);
171631
+ }
171058
171632
  if (nudgerFn && options.nudge) {
171059
171633
  try {
171060
171634
  const tNudge = performance.now();
@@ -171168,7 +171742,7 @@ function maybeFireCompressor(args) {
171168
171742
  const compressor = historian.compressor;
171169
171743
  if (!compressor?.enabled)
171170
171744
  return;
171171
- if (!args.isCacheBusting)
171745
+ if (!args.isCacheBusting && args.schedulerDecision !== "execute")
171172
171746
  return;
171173
171747
  if (inFlightHistorian.has(sessionId) || inFlightCompressor.has(sessionId)) {
171174
171748
  sessionLog(sessionId, "compressor trigger eval: in-flight historian/compressor, skipping");
@@ -171188,7 +171762,6 @@ function maybeFireCompressor(args) {
171188
171762
  });
171189
171763
  if (!historyBudgetTokens || historyBudgetTokens <= 0)
171190
171764
  return;
171191
- markPiCompressorRun(sessionId);
171192
171765
  const runPromise = runPiCompressionPassIfNeeded({
171193
171766
  db,
171194
171767
  sessionId,
@@ -171204,10 +171777,14 @@ function maybeFireCompressor(args) {
171204
171777
  maxCompartmentsPerPass: compressor.maxCompartmentsPerPass,
171205
171778
  graceCompartments: compressor.graceCompartments,
171206
171779
  onPublished: () => {
171207
- signalPiHistoryRefresh(sessionId);
171208
- signalPiPendingMaterialization(sessionId);
171780
+ signalPiDeferredHistoryRefresh(sessionId);
171781
+ signalPiDeferredMaterialization(sessionId);
171209
171782
  historian.onStatusChange?.(ctx, sessionId);
171210
171783
  }
171784
+ }).then((didPublish) => {
171785
+ if (didPublish === true) {
171786
+ markPiCompressorRun(sessionId);
171787
+ }
171211
171788
  }).catch((err) => {
171212
171789
  sessionLog(sessionId, `compressor failed in background: ${err instanceof Error ? err.message : String(err)}`);
171213
171790
  }).finally(() => {
@@ -171262,8 +171839,8 @@ function spawnPiHistorianRun(args) {
171262
171839
  } catch (err) {
171263
171840
  sessionLog(sessionId, `historian: clearEmergencyRecovery failed: ${err instanceof Error ? err.message : String(err)}`);
171264
171841
  }
171265
- signalPiHistoryRefresh(sessionId);
171266
- signalPiPendingMaterialization(sessionId);
171842
+ signalPiDeferredHistoryRefresh(sessionId);
171843
+ signalPiDeferredMaterialization(sessionId);
171267
171844
  historian.onStatusChange?.(ctx, sessionId);
171268
171845
  }
171269
171846
  }).finally(() => {
@@ -171393,6 +171970,12 @@ async function runPipeline(args) {
171393
171970
  logTransformTiming(args.sessionId, "injectTemporalMarkers", tTemporal);
171394
171971
  }
171395
171972
  const transcript = createPiTranscript(args.messages, args.sessionId);
171973
+ const currentTurnId = (() => {
171974
+ const ids = buildPiMessageIdByIndex(args.messages, args.entryIds ?? null);
171975
+ return findLatestUserMessageIdPi(args.messages, ids)?.messageId ?? null;
171976
+ })();
171977
+ const alreadyRanHeuristicsThisTurn = currentTurnId !== null && lastHeuristicsTurnIdBySession.get(args.sessionId) === currentTurnId;
171978
+ const shouldRunHeuristics = args.heuristics !== undefined && (args.forceMaterialization === true || args.schedulerDecision === "execute" && !alreadyRanHeuristicsThisTurn);
171396
171979
  const tTag = performance.now();
171397
171980
  const { targets } = tagTranscript(args.sessionId, transcript, args.tagger, args.db, {
171398
171981
  skipPrefixInjection: !args.ctxReduceEnabled
@@ -171413,10 +171996,16 @@ async function runPipeline(args) {
171413
171996
  sessionLog(args.sessionId, `commit-detect failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
171414
171997
  }
171415
171998
  const hasPendingMaterializeSignal = hasPendingMaterialization(args.sessionId);
171999
+ const deferredMaterializationWasPending = deferredMaterializationSessions.has(args.sessionId);
172000
+ const deferredHistoryRefreshWasPending = deferredHistoryRefreshSessions.has(args.sessionId);
171416
172001
  const pendingOps = getPendingOps(args.db, args.sessionId);
171417
- const shouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
172002
+ const baseShouldApplyPendingOps = args.schedulerDecision === "execute" || args.forceMaterialization || hasPendingMaterializeSignal;
172003
+ const canConsumeDeferredLate = baseShouldApplyPendingOps || shouldRunHeuristics;
172004
+ const deferredMaterialize = canConsumeDeferredLate && deferredMaterializationWasPending;
172005
+ const deferredHistoryRefresh = canConsumeDeferredLate && deferredHistoryRefreshWasPending;
172006
+ const shouldApplyPendingOps = baseShouldApplyPendingOps || deferredMaterialize;
171418
172007
  if (shouldApplyPendingOps) {
171419
- const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
172008
+ const applyReason = hasPendingMaterializeSignal ? "explicit_flush" : deferredMaterialize ? "deferred_publication" : args.forceMaterialization ? "force_materialization" : `scheduler_execute (scheduler=${args.schedulerDecision})`;
171420
172009
  sessionLog(args.sessionId, `pending ops WILL APPLY — reason=${applyReason}, pendingOps=${pendingOps.length}, context=${args.contextUsage.percentage.toFixed(1)}%`);
171421
172010
  try {
171422
172011
  const tApplyPending = performance.now();
@@ -171426,6 +172015,9 @@ async function runPipeline(args) {
171426
172015
  if (hasPendingMaterializeSignal) {
171427
172016
  consumePendingMaterialization(args.sessionId);
171428
172017
  }
172018
+ if (deferredMaterialize) {
172019
+ consumeDeferredMaterialization(args.sessionId);
172020
+ }
171429
172021
  } catch (err) {
171430
172022
  sessionLog(args.sessionId, `pending operations failed: ${err instanceof Error ? err.message : String(err)}`);
171431
172023
  throw err;
@@ -171482,7 +172074,6 @@ async function runPipeline(args) {
171482
172074
  const tActiveTags = performance.now();
171483
172075
  const activeTags = getActiveTagsBySession(args.db, args.sessionId);
171484
172076
  logTransformTiming(args.sessionId, "getActiveTagsBySession", tActiveTags, `count=${activeTags.length}`);
171485
- const shouldRunHeuristics = args.heuristics !== undefined && (args.schedulerDecision === "execute" || args.forceMaterialization === true);
171486
172077
  if (shouldRunHeuristics) {
171487
172078
  const reason = args.forceMaterialization ? "force_materialization" : `scheduler_execute (pendingOps=${pendingOps.length}, scheduler=${args.schedulerDecision})`;
171488
172079
  sessionLog(args.sessionId, `heuristics WILL RUN — reason=${reason}, context=${args.contextUsage.percentage.toFixed(1)}%, turn=n/a`);
@@ -171502,6 +172093,9 @@ async function runPipeline(args) {
171502
172093
  }, activeTags);
171503
172094
  heuristicsExecuted = true;
171504
172095
  executedWorkThisPass = true;
172096
+ if (currentTurnId !== null) {
172097
+ lastHeuristicsTurnIdBySession.set(args.sessionId, currentTurnId);
172098
+ }
171505
172099
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", tHeuristic, `droppedTools=${heuristicsResult.droppedTools} deduplicatedTools=${heuristicsResult.deduplicatedTools} droppedInjections=${heuristicsResult.droppedInjections} compressedTextTags=${heuristicsResult.compressedTextTags}`);
171506
172100
  } catch (err) {
171507
172101
  sessionLog(args.sessionId, `heuristic cleanup failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
@@ -171510,21 +172104,26 @@ async function runPipeline(args) {
171510
172104
  if (args.reasoningClearing && (args.schedulerDecision === "execute" || args.forceMaterialization === true)) {
171511
172105
  try {
171512
172106
  const tClearReasoning = performance.now();
172107
+ const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
172108
+ const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
171513
172109
  const clearOutcome = clearOldReasoningPi({
171514
172110
  messages: args.messages,
171515
172111
  messageIdToMaxTag,
171516
172112
  clearReasoningAge: args.reasoningClearing.clearReasoningAge,
171517
172113
  piMessageStableId
171518
172114
  });
171519
- const meta3 = getOrCreateSessionMeta(args.db, args.sessionId);
171520
- const prevWatermark = meta3.clearedReasoningThroughTag ?? 0;
171521
- if (clearOutcome.cleared > 0 && clearOutcome.newWatermark > 0) {
171522
- if (clearOutcome.newWatermark > prevWatermark) {
171523
- updateSessionMeta(args.db, args.sessionId, {
171524
- clearedReasoningThroughTag: clearOutcome.newWatermark
171525
- });
171526
- sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=0 watermark=${prevWatermark}→${clearOutcome.newWatermark}`);
171527
- }
172115
+ const stripOutcome = stripInlineThinkingPi({
172116
+ messages: args.messages,
172117
+ messageIdToMaxTag,
172118
+ clearReasoningAge: args.reasoningClearing.clearReasoningAge,
172119
+ piMessageStableId
172120
+ });
172121
+ const combinedWatermark = Math.max(clearOutcome.newWatermark, stripOutcome.newWatermark);
172122
+ if (combinedWatermark > prevWatermark) {
172123
+ updateSessionMeta(args.db, args.sessionId, {
172124
+ clearedReasoningThroughTag: combinedWatermark
172125
+ });
172126
+ sessionLog(args.sessionId, `reasoning cleanup: cleared=${clearOutcome.cleared} inlineStripped=${stripOutcome.stripped} watermark=${prevWatermark}→${combinedWatermark}`);
171528
172127
  }
171529
172128
  logTransformTiming(args.sessionId, "clearOldReasoning", tClearReasoning);
171530
172129
  logTransformTiming(args.sessionId, "watermarkCleanup", tClearReasoning);
@@ -171538,10 +172137,13 @@ async function runPipeline(args) {
171538
172137
  if (args.injection) {
171539
172138
  try {
171540
172139
  const tInjection = performance.now();
171541
- injectionResult = injectSessionHistoryIntoPi(args.db, args.sessionId, args.messages, args.isCacheBusting, args.projectIdentity, args.injection.injectionBudgetTokens, args.injection.temporalAwareness, args.entryIds);
172140
+ injectionResult = injectSessionHistoryIntoPi(args.db, args.sessionId, args.messages, args.isCacheBusting || deferredHistoryRefresh, args.projectIdentity, args.injection.injectionBudgetTokens, args.injection.temporalAwareness, args.entryIds);
171542
172141
  if (args.isCacheBusting) {
171543
172142
  historyRefreshSessions.delete(args.sessionId);
171544
172143
  }
172144
+ if (deferredHistoryRefresh) {
172145
+ consumeDeferredHistoryRefresh(args.sessionId);
172146
+ }
171545
172147
  logTransformTiming(args.sessionId, "prepareCompartmentInjection", tInjection);
171546
172148
  logTransformTiming(args.sessionId, "compartmentPhase", tInjection);
171547
172149
  } catch (err) {
@@ -171604,6 +172206,29 @@ function applyRollingNudge(args) {
171604
172206
  return messages;
171605
172207
  return injectPiNudge(messages, nudge);
171606
172208
  }
172209
+ function applyStickyTurnReminder(args) {
172210
+ const reminder = getPersistedStickyTurnReminder(args.db, args.sessionId);
172211
+ if (!reminder)
172212
+ return args.messages;
172213
+ if (args.hasRecentReduceCall && args.isCacheBustingPass) {
172214
+ clearPersistedStickyTurnReminder(args.db, args.sessionId);
172215
+ return args.messages;
172216
+ }
172217
+ const messageIdByIndex = buildPiMessageIdByIndex(args.messages, args.entryIds);
172218
+ if (reminder.messageId) {
172219
+ const reinjected = appendReminderToUserMessageByIdPi(args.messages, messageIdByIndex, reminder.messageId, reminder.text);
172220
+ if (!reinjected && args.isCacheBustingPass) {
172221
+ clearPersistedStickyTurnReminder(args.db, args.sessionId);
172222
+ }
172223
+ return args.messages;
172224
+ }
172225
+ const latest = findLatestUserMessageIdPi(args.messages, messageIdByIndex);
172226
+ if (latest) {
172227
+ appendReminderToPiUserMessage(args.messages[latest.index], reminder.text);
172228
+ setPersistedStickyTurnReminder(args.db, args.sessionId, reminder.text, latest.messageId);
172229
+ }
172230
+ return args.messages;
172231
+ }
171607
172232
  function applyNoteNudges(args) {
171608
172233
  const { sessionId, db, messages, projectIdentity, entryIds, isCacheBusting } = args;
171609
172234
  const messageIdByIndex = buildPiMessageIdByIndex(messages, entryIds);
@@ -171743,12 +172368,26 @@ function clearContextHandlerSession(sessionId) {
171743
172368
  historyRefreshSessions.delete(sessionId);
171744
172369
  pendingMaterializationSessions.delete(sessionId);
171745
172370
  systemPromptRefreshSessions.delete(sessionId);
172371
+ deferredHistoryRefreshSessions.delete(sessionId);
172372
+ deferredMaterializationSessions.delete(sessionId);
171746
172373
  firstContextPassSeenBySession.delete(sessionId);
171747
172374
  commitSeenLastPass.delete(sessionId);
171748
172375
  recentReduceBySession.delete(sessionId);
171749
172376
  liveModelBySession.delete(sessionId);
171750
172377
  toolUsageSinceUserTurn.delete(sessionId);
171751
172378
  latestUserMessageBySession.delete(sessionId);
172379
+ lastHeuristicsTurnIdBySession.delete(sessionId);
172380
+ lastSeenProjectIdentityBySession.delete(sessionId);
172381
+ for (const [projectIdentity, sessions] of sessionsByProject) {
172382
+ sessions.delete(sessionId);
172383
+ if (sessions.size === 0)
172384
+ sessionsByProject.delete(projectIdentity);
172385
+ }
172386
+ const unregister = rawMessageProviderUnregistersBySession.get(sessionId);
172387
+ if (unregister) {
172388
+ unregister();
172389
+ rawMessageProviderUnregistersBySession.delete(sessionId);
172390
+ }
171752
172391
  clearSessionTracking(sessionId);
171753
172392
  clearPiCompressorState(sessionId);
171754
172393
  }
@@ -171799,7 +172438,7 @@ init_logger();
171799
172438
  init_data_path();
171800
172439
  init_logger();
171801
172440
  await init_sqlite();
171802
- import { join as join14 } from "node:path";
172441
+ import { join as join17 } from "node:path";
171803
172442
  var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
171804
172443
  function randomBase62(length) {
171805
172444
  const chars = [];
@@ -171820,7 +172459,7 @@ function generatePartId(timestampMs, counter = 0n) {
171820
172459
  return generateId("prt", timestampMs, counter);
171821
172460
  }
171822
172461
  function getOpenCodeDbPath3() {
171823
- return join14(getDataDir(), "opencode", "opencode.db");
172462
+ return join17(getDataDir(), "opencode", "opencode.db");
171824
172463
  }
171825
172464
  var cachedWriteDb = null;
171826
172465
  var REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
@@ -172340,7 +172979,7 @@ async function runCompressorPass2(args) {
172340
172979
  });
172341
172980
  const messagesResponse = await client.session.messages({
172342
172981
  path: { id: agentSessionId },
172343
- query: { directory }
172982
+ query: { directory, limit: 50 }
172344
172983
  });
172345
172984
  const messages = normalizeSDKResponse(messagesResponse, [], {
172346
172985
  preferResponseOnMissingData: true
@@ -172378,7 +173017,7 @@ async function runCompressorPass2(args) {
172378
173017
 
172379
173018
  // ../plugin/src/hooks/magic-context/compartment-runner-historian.ts
172380
173019
  import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
172381
- import { join as join15 } from "node:path";
173020
+ import { join as join18 } from "node:path";
172382
173021
  init_data_path();
172383
173022
  function historianResponseDumpDir(directory) {
172384
173023
  return getProjectMagicContextHistorianDir(directory);
@@ -172525,7 +173164,7 @@ async function runHistorianPrompt(args) {
172525
173164
  }
172526
173165
  const messagesResponse = await client.session.messages({
172527
173166
  path: { id: agentSessionId },
172528
- query: { directory: sessionDirectory }
173167
+ query: { directory: sessionDirectory, limit: 50 }
172529
173168
  });
172530
173169
  const messages = normalizeSDKResponse(messagesResponse, [], {
172531
173170
  preferResponseOnMissingData: true
@@ -172632,7 +173271,7 @@ function dumpHistorianResponse(sessionId, directory, label, text) {
172632
173271
  mkdirSync6(dumpDir, { recursive: true });
172633
173272
  const safeSessionId = sanitizeDumpName(sessionId);
172634
173273
  const safeLabel = sanitizeDumpName(label);
172635
- const dumpPath = join15(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
173274
+ const dumpPath = join18(dumpDir, `${safeSessionId}-${safeLabel}-${Date.now()}.xml`);
172636
173275
  writeFileSync3(dumpPath, text, "utf8");
172637
173276
  sessionLog(sessionId, "compartment agent: historian response dumped", {
172638
173277
  label,
@@ -173838,7 +174477,7 @@ import {
173838
174477
  // package.json
173839
174478
  var package_default = {
173840
174479
  name: "@cortexkit/pi-magic-context",
173841
- version: "0.21.0",
174480
+ version: "0.21.2",
173842
174481
  type: "module",
173843
174482
  description: "Pi coding agent extension for Magic Context — cross-session memory and context management",
173844
174483
  main: "dist/index.js",
@@ -174290,6 +174929,8 @@ function registerCtxStatusCommand(pi, deps) {
174290
174929
  pi.registerCommand("ctx-status", {
174291
174930
  description: "Show Magic Context status for the current Pi session",
174292
174931
  handler: async (_args, ctx) => {
174932
+ const projectIdentity = deps.resolveProject?.(ctx).projectIdentity ?? deps.projectIdentity;
174933
+ const currentDeps = { ...deps, projectIdentity };
174293
174934
  const sessionId = resolveSessionId(ctx);
174294
174935
  if (!sessionId) {
174295
174936
  sendCtxStatusMessage(pi, {
@@ -174303,13 +174944,13 @@ No active Pi session is available.`,
174303
174944
  }
174304
174945
  try {
174305
174946
  if (ctx.hasUI) {
174306
- await showStatusDialog(pi, ctx, deps);
174947
+ await showStatusDialog(pi, ctx, currentDeps);
174307
174948
  return;
174308
174949
  }
174309
174950
  const usage = ctx.getContextUsage?.();
174310
174951
  const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
174311
- const statusText = executeStatus(deps.db, sessionId, deps.protectedTags ?? 20, deps.nudgeIntervalTokens, deps.executeThresholdPercentage, modelKey, deps.historyBudgetPercentage, deps.commitClusterTrigger, deps.executeThresholdTokens, usage?.contextWindow);
174312
- const details = buildStatusDetails(deps, sessionId);
174952
+ const statusText = executeStatus(currentDeps.db, sessionId, currentDeps.protectedTags ?? 20, currentDeps.nudgeIntervalTokens, currentDeps.executeThresholdPercentage, modelKey, currentDeps.historyBudgetPercentage, currentDeps.commitClusterTrigger, currentDeps.executeThresholdTokens, usage?.contextWindow);
174953
+ const details = buildStatusDetails(currentDeps, sessionId);
174313
174954
  sendCtxStatusMessage(pi, { title: "/ctx-status", text: statusText, level: "info" }, details);
174314
174955
  } catch (error51) {
174315
174956
  sendCtxStatusMessage(pi, {
@@ -174434,405 +175075,6 @@ function stripTagPrefixFromAssistantMessage(message) {
174434
175075
  return mutated;
174435
175076
  }
174436
175077
 
174437
- // src/system-prompt.ts
174438
- import { createHash as createHash8 } from "node:crypto";
174439
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
174440
- import { join as join17 } from "node:path";
174441
-
174442
- // ../plugin/src/agents/magic-context-prompt.ts
174443
- function getToolHistoryGuidance(dropToolStructure) {
174444
- if (dropToolStructure) {
174445
- return `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.`;
174446
- }
174447
- return `Older tool calls in your conversation show truncated inputs and [truncated] outputs — this is normal context management, not a pattern to follow. The original tool calls executed successfully with full inputs and produced real outputs that were later cleaned up to save context. ALWAYS use real tool calls with complete arguments; never copy truncated patterns like "filePa...[truncated]" into your tool inputs. If you need to re-read a file or re-run a command, make a fresh tool call.`;
174448
- }
174449
- var BASE_INTRO = (protectedTags, dropToolStructure) => `Messages and tool outputs are tagged with §N§ identifiers (e.g., §1§, §42§).
174450
- Use \`ctx_reduce\` to manage context size. It supports one operation:
174451
- - \`drop\`: Remove entirely (best for tool outputs you already acted on).
174452
- Syntax: "3-5", "1,2,9", or "1-5,8,12-15". Last ${protectedTags} tags are protected.
174453
- Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
174454
- Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
174455
- **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:
174456
- - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
174457
- - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
174458
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
174459
- Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
174460
- Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
174461
- **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:
174462
- - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
174463
- - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
174464
- - Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
174465
- - Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
174466
- - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
174467
- \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
174468
- ${getToolHistoryGuidance(dropToolStructure)}
174469
- NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
174470
- NEVER drop user messages — they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
174471
- NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
174472
- Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
174473
- var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions — things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
174474
- Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
174475
- **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:
174476
- - Found a project's source code path after searching → \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
174477
- - Discovered a non-obvious build/test command → \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
174478
- - Learned a constraint the hard way → \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
174479
- Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
174480
- Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
174481
- **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:
174482
- - Can't remember where a related codebase or dependency lives → \`ctx_search(query="opencode source code path")\`
174483
- - Forgot a prior architectural decision or constraint → \`ctx_search(query="why did we choose SQLite over postgres")\`
174484
- - Need a config value, API key location, or environment detail → \`ctx_search(query="embedding provider configuration")\`
174485
- - Looking for how something was implemented previously → \`ctx_search(query="how does the dreamer lease work")\`
174486
- - Want to recall what was decided in an earlier conversation → \`ctx_search(query="dashboard release signing setup")\`
174487
- \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
174488
- ${getToolHistoryGuidance(dropToolStructure)}`;
174489
- var GENERIC_SECTION = `
174490
- ### Reduction Triggers
174491
- - After reading files or search results you already acted on — drop raw outputs.
174492
- - After completing a logical step — drop intermediate outputs from that step.
174493
- - Between major context switches — when moving to a new task area.
174494
-
174495
- ### What to Drop
174496
- - Large file reads, grep results, and tool outputs you already used.
174497
- - Large build/test output after you analyzed and acted on it.
174498
- - Old diagnostic or exploration results that are no longer relevant.
174499
-
174500
- ### What to Keep
174501
- - ALL user messages and assistant conversation text — these are cheap and compartmentalized automatically.
174502
- - Your current task requirements and constraints.
174503
- - Recent errors and unresolved decisions.
174504
- - Active work context and files being edited.`;
174505
- var TEMPORAL_AWARENESS_GUIDANCE = `
174506
- **Temporal awareness**: User messages may be preceded by HTML comments like \`<!-- +12m -->\`, \`<!-- +2h 15m -->\`, or \`<!-- +3d 4h -->\` indicating time elapsed since the previous message's completion. Compartments in \`<session-history>\` carry \`start-date\` and \`end-date\` attributes (YYYY-MM-DD) showing real-time boundaries. Use these when reasoning about workflow pacing, log durations, build times, or how long ago something happened.`;
174507
- var CAVEMAN_COMPRESSION_WARNING = `
174508
- **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.`;
174509
- function buildMagicContextSection(_agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false, dropToolStructure = true, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false) {
174510
- const smartNoteGuidance = dreamerEnabled ? `
174511
- When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
174512
- The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
174513
- Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
174514
- const temporalGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : "";
174515
- const cavemanWarning = cavemanTextCompressionEnabled && !ctxReduceEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
174516
- if (!ctxReduceEnabled) {
174517
- return `## Magic Context
174518
-
174519
- ${BASE_INTRO_NO_REDUCE(dropToolStructure)}${smartNoteGuidance}${temporalGuidance}${cavemanWarning}`;
174520
- }
174521
- return `## Magic Context
174522
-
174523
- ${BASE_INTRO(protectedTags, dropToolStructure)}${smartNoteGuidance}${temporalGuidance}
174524
- ${GENERIC_SECTION}
174525
-
174526
- Prefer many small targeted operations over one large blanket operation. Compress early and often — don't wait for warnings.`;
174527
- }
174528
-
174529
- // ../plugin/src/hooks/magic-context/key-files-block.ts
174530
- import { readFileSync as readFileSync7, realpathSync as realpathSync3 } from "node:fs";
174531
- import { join as join16, sep as sep2 } from "node:path";
174532
- init_logger();
174533
- var cachedKeyFilesBySession = new Map;
174534
- var staleUpdates = new Map;
174535
- function staleKey(update) {
174536
- return `${update.projectPath}\x00${update.path}\x00${update.generatedAtWitness}\x00${update.staleReason}`;
174537
- }
174538
- function queueStaleUpdate(projectPath, path5, generatedAtWitness, staleReason) {
174539
- const update = { projectPath, path: path5, generatedAtWitness, staleReason };
174540
- staleUpdates.set(staleKey(update), update);
174541
- }
174542
- function flushStaleUpdates(db) {
174543
- if (staleUpdates.size === 0)
174544
- return 0;
174545
- const updates = [...staleUpdates.values()];
174546
- staleUpdates.clear();
174547
- const stmt = db.prepare(`UPDATE project_key_files
174548
- SET stale_reason = ?1
174549
- WHERE project_path = ?2
174550
- AND path = ?3
174551
- AND generated_at = ?4
174552
- AND (stale_reason IS NULL OR stale_reason != ?1)`);
174553
- let changed = 0;
174554
- for (const update of updates) {
174555
- try {
174556
- changed += stmt.run(update.staleReason, update.projectPath, update.path, update.generatedAtWitness).changes;
174557
- } catch (error51) {
174558
- log("[key-files] flushStaleUpdates failed:", error51);
174559
- }
174560
- }
174561
- return changed;
174562
- }
174563
- function clearKeyFilesCacheForSession(sessionId) {
174564
- cachedKeyFilesBySession.delete(sessionId);
174565
- }
174566
- function isUnderProject(projectPath, absPath) {
174567
- const root = realpathSync3(projectPath);
174568
- return absPath.startsWith(root + sep2) || absPath === root;
174569
- }
174570
- function buildKeyFilesBlock(db, projectPath, config2 = { enabled: true, tokenBudget: 1e4 }) {
174571
- if (!config2.enabled)
174572
- return null;
174573
- if (!isAftAvailable())
174574
- return null;
174575
- const rows = readCurrentKeyFiles(db, projectPath);
174576
- if (rows.length === 0)
174577
- return null;
174578
- for (const row of rows) {
174579
- if (row.staleReason !== null)
174580
- continue;
174581
- let nextStale = null;
174582
- let observed = false;
174583
- try {
174584
- const absPath = join16(projectPath, row.path);
174585
- const real = realpathSync3(absPath);
174586
- if (!isUnderProject(projectPath, real)) {
174587
- nextStale = "missing";
174588
- observed = true;
174589
- } else {
174590
- const diskHash = sha256(readFileSync7(real));
174591
- if (diskHash !== row.contentHash)
174592
- nextStale = "content_drift";
174593
- observed = true;
174594
- }
174595
- } catch (error51) {
174596
- const code = error51?.code;
174597
- if (code === "ENOENT" || code === "ELOOP") {
174598
- nextStale = "missing";
174599
- observed = true;
174600
- } else {
174601
- log(`[key-files] freshness check transient failure: ${row.path}: ${error51 instanceof Error ? error51.message : String(error51)}`);
174602
- }
174603
- }
174604
- if (observed && nextStale !== null) {
174605
- queueStaleUpdate(row.projectPath, row.path, row.generatedAt, nextStale);
174606
- }
174607
- }
174608
- const blocks = [];
174609
- let used = 0;
174610
- for (const row of rows) {
174611
- if (used + row.localTokenEstimate > config2.tokenBudget)
174612
- break;
174613
- blocks.push(` <key-file path="${escapeXmlAttr(row.path)}">
174614
- ${escapeXmlContent(row.content)}
174615
- </key-file>`);
174616
- used += row.localTokenEstimate;
174617
- }
174618
- if (blocks.length === 0)
174619
- return null;
174620
- const rendered = `<key-files>
174621
- ${blocks.join(`
174622
- `)}
174623
- </key-files>`;
174624
- flushStaleUpdates(db);
174625
- return rendered;
174626
- }
174627
- function readVersionedKeyFiles(args) {
174628
- const config2 = args.config ?? { enabled: true, tokenBudget: 1e4 };
174629
- if (args.sessionMeta.isSubagent)
174630
- return null;
174631
- if (!config2.enabled)
174632
- return null;
174633
- if (!isAftAvailable())
174634
- return null;
174635
- const projectPath = resolveProjectPath(args.directory ?? args.sessionMeta.sessionId);
174636
- if (!projectPath)
174637
- return null;
174638
- const currentVersion = getKeyFilesVersion(args.db, projectPath);
174639
- if (args.sessionId) {
174640
- const cached2 = cachedKeyFilesBySession.get(args.sessionId);
174641
- if (cached2 && !args.isCacheBusting && cached2.version === currentVersion) {
174642
- return cached2.value;
174643
- }
174644
- }
174645
- const value = buildKeyFilesBlock(args.db, projectPath, config2);
174646
- if (args.sessionId) {
174647
- cachedKeyFilesBySession.set(args.sessionId, { value, version: currentVersion });
174648
- if (value)
174649
- sessionLog(args.sessionId, `loaded key-files block (v${currentVersion}, ${value.length} chars)`);
174650
- }
174651
- return value;
174652
- }
174653
-
174654
- // src/system-prompt.ts
174655
- init_logger();
174656
- var PROJECT_DOCS_MARKER = "<project-docs>";
174657
- var USER_PROFILE_MARKER = "<user-profile>";
174658
- var stickyDateBySession = new Map;
174659
- var cachedDocsBySession = new Map;
174660
- var cachedUserProfileBySession = new Map;
174661
- var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
174662
- function readProjectDocs(directory) {
174663
- const sections = [];
174664
- for (const filename of DOC_FILES) {
174665
- const filePath = join17(directory, filename);
174666
- try {
174667
- if (existsSync11(filePath)) {
174668
- const content = readFileSync8(filePath, "utf-8").trim();
174669
- if (content.length > 0) {
174670
- sections.push(`<${filename}>
174671
- ${content}
174672
- </${filename}>`);
174673
- }
174674
- }
174675
- } catch (error51) {
174676
- log(`[magic-context-pi] failed to read ${filename}:`, error51);
174677
- }
174678
- }
174679
- if (sections.length === 0)
174680
- return null;
174681
- return `${PROJECT_DOCS_MARKER}
174682
- ${sections.join(`
174683
-
174684
- `)}
174685
- </project-docs>`;
174686
- }
174687
- function buildUserProfileBlock(db) {
174688
- const memories = getActiveUserMemories(db);
174689
- if (memories.length === 0)
174690
- return null;
174691
- const items = memories.map((m) => `- ${m.content}`).join(`
174692
- `);
174693
- return `${USER_PROFILE_MARKER}
174694
- ${items}
174695
- </user-profile>`;
174696
- }
174697
- function buildMagicContextBlock(opts) {
174698
- const sections = [];
174699
- const sessionId = opts.sessionId;
174700
- const isCacheBusting = opts.isCacheBusting ?? false;
174701
- if (opts.injectDocs) {
174702
- const docsBlock = readCachedAdjunct({
174703
- cache: cachedDocsBySession,
174704
- sessionId,
174705
- isCacheBusting,
174706
- compute: () => readProjectDocs(opts.cwd),
174707
- describe: "project docs"
174708
- });
174709
- if (docsBlock)
174710
- sections.push(docsBlock);
174711
- }
174712
- if (opts.userMemoriesEnabled) {
174713
- const profileBlock = readCachedAdjunct({
174714
- cache: cachedUserProfileBySession,
174715
- sessionId,
174716
- isCacheBusting,
174717
- compute: () => buildUserProfileBlock(opts.db),
174718
- describe: "user profile"
174719
- });
174720
- if (profileBlock)
174721
- sections.push(profileBlock);
174722
- }
174723
- if (opts.pinKeyFilesEnabled && sessionId) {
174724
- let sessionMeta = null;
174725
- try {
174726
- sessionMeta = getOrCreateSessionMeta(opts.db, sessionId);
174727
- } catch (error51) {
174728
- sessionLog(sessionId, "Pi key-files session meta load failed:", error51);
174729
- }
174730
- const keyFilesBlock = sessionMeta ? readVersionedKeyFiles({
174731
- db: opts.db,
174732
- sessionId,
174733
- sessionMeta,
174734
- directory: opts.cwd,
174735
- isCacheBusting,
174736
- config: {
174737
- enabled: opts.pinKeyFilesEnabled,
174738
- tokenBudget: opts.pinKeyFilesTokenBudget ?? 1e4
174739
- }
174740
- }) : null;
174741
- if (keyFilesBlock)
174742
- sections.push(keyFilesBlock);
174743
- }
174744
- const dataBlock = sections.length > 0 ? `<magic-context>
174745
- ${sections.join(`
174746
-
174747
- `)}
174748
- </magic-context>` : null;
174749
- const includeGuidance = opts.includeGuidance ?? true;
174750
- if (!includeGuidance) {
174751
- return dataBlock;
174752
- }
174753
- const guidance = buildMagicContextSection(null, opts.protectedTags ?? 20, opts.ctxReduceEnabled ?? true, opts.dreamerEnabled ?? false, opts.dropToolStructure ?? true, opts.temporalAwarenessEnabled ?? false, opts.cavemanTextCompressionEnabled ?? false);
174754
- if (dataBlock) {
174755
- return `${guidance}
174756
-
174757
- ${dataBlock}`;
174758
- }
174759
- return guidance;
174760
- }
174761
- function readCachedAdjunct(args) {
174762
- const { cache, sessionId, isCacheBusting, compute, describe: describe3 } = args;
174763
- if (!sessionId) {
174764
- return compute();
174765
- }
174766
- const hasCached = cache.has(sessionId);
174767
- if (!hasCached || isCacheBusting) {
174768
- const value = compute();
174769
- cache.set(sessionId, value);
174770
- if (value && !hasCached) {
174771
- sessionLog(sessionId, `loaded ${describe3} (${value.length} chars)`);
174772
- } else if (value && isCacheBusting) {
174773
- sessionLog(sessionId, `refreshed ${describe3} (cache-busting pass)`);
174774
- }
174775
- return value;
174776
- }
174777
- return cache.get(sessionId) ?? null;
174778
- }
174779
- var DATE_PATTERN = /Today's date: .+/;
174780
- function processSystemPromptForCache(args) {
174781
- const { db, sessionId, systemPrompt, isCacheBusting } = args;
174782
- let sessionMeta;
174783
- try {
174784
- sessionMeta = getOrCreateSessionMeta(db, sessionId);
174785
- } catch (error51) {
174786
- sessionLog(sessionId, "system-prompt-hash session meta load failed:", error51);
174787
- }
174788
- const previousHash = sessionMeta?.systemPromptHash ?? "";
174789
- const isFirstHash = previousHash === "" || previousHash === "0";
174790
- let frozenPrompt = systemPrompt;
174791
- const dateMatch = systemPrompt.match(DATE_PATTERN);
174792
- const liveDate = dateMatch ? dateMatch[0] : null;
174793
- const stickyDate = stickyDateBySession.get(sessionId);
174794
- if (liveDate && !stickyDate) {
174795
- stickyDateBySession.set(sessionId, liveDate);
174796
- } else if (liveDate && stickyDate && liveDate !== stickyDate) {
174797
- if (isCacheBusting) {
174798
- stickyDateBySession.set(sessionId, liveDate);
174799
- sessionLog(sessionId, `system prompt date updated: ${stickyDate} → ${liveDate} (cache-busting pass)`);
174800
- } else {
174801
- frozenPrompt = systemPrompt.replace(DATE_PATTERN, stickyDate);
174802
- sessionLog(sessionId, `system prompt date frozen: real=${liveDate}, using=${stickyDate} (cache-stable pass)`);
174803
- }
174804
- }
174805
- const currentHash = createHash8("md5").update(frozenPrompt).digest("hex");
174806
- const hashChanged = !isFirstHash && currentHash !== previousHash;
174807
- if (hashChanged) {
174808
- sessionLog(sessionId, `system prompt hash changed: ${previousHash} → ${currentHash} (len=${frozenPrompt.length})`);
174809
- } else if (isFirstHash) {
174810
- sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${frozenPrompt.length})`);
174811
- }
174812
- const systemPromptTokens = estimateTokens(frozenPrompt);
174813
- if (sessionMeta) {
174814
- if (currentHash !== previousHash) {
174815
- updateSessionMeta(db, sessionId, {
174816
- systemPromptHash: currentHash,
174817
- systemPromptTokens
174818
- });
174819
- } else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
174820
- updateSessionMeta(db, sessionId, { systemPromptTokens });
174821
- }
174822
- }
174823
- return {
174824
- systemPrompt: frozenPrompt,
174825
- hashChanged,
174826
- currentHash
174827
- };
174828
- }
174829
- function clearPiSystemPromptSession(sessionId) {
174830
- stickyDateBySession.delete(sessionId);
174831
- cachedDocsBySession.delete(sessionId);
174832
- cachedUserProfileBySession.delete(sessionId);
174833
- clearKeyFilesCacheForSession(sessionId);
174834
- }
174835
-
174836
175078
  // ../plugin/src/tools/ctx-expand/constants.ts
174837
175079
  var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + 'Use start/end from <compartment start="N" end="M"> attributes. ' + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
174838
175080
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
@@ -178974,6 +179216,7 @@ var ALL_ACTIONS = [
178974
179216
  "archive"
178975
179217
  ];
178976
179218
  var DREAMER_ONLY_ACTIONS = new Set([
179219
+ "list",
178977
179220
  "update",
178978
179221
  "merge",
178979
179222
  "archive"
@@ -179062,7 +179305,7 @@ function queueEmbedding(args) {
179062
179305
  }
179063
179306
  function createCtxMemoryTool(deps) {
179064
179307
  const dreamerAllowed = deps.allowDreamerActions === true;
179065
- const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list.";
179308
+ const description = dreamerAllowed ? "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete, list, update, merge, archive." : "Manage cross-session project memories. Memories persist across sessions and are " + "shared with OpenCode sessions on the same project. " + "Supported actions: write, delete.";
179066
179309
  return {
179067
179310
  name: "ctx_memory",
179068
179311
  label: "Magic Context: Memory",
@@ -179655,8 +179898,10 @@ function formatResult(result, index) {
179655
179898
  ].join(`
179656
179899
  `);
179657
179900
  }
179901
+ const expandStart = Math.max(1, result.messageOrdinal - 3);
179902
+ const expandEnd = result.messageOrdinal + 3;
179658
179903
  return [
179659
- `[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} role=${result.role}`,
179904
+ `[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
179660
179905
  result.content
179661
179906
  ].join(`
179662
179907
  `);
@@ -179665,7 +179910,11 @@ function formatSearchResults(query, results) {
179665
179910
  if (results.length === 0) {
179666
179911
  return `No results found for "${query}" across memories, git commits, or message history.`;
179667
179912
  }
179668
- const body = results.map((result, index) => formatResult(result, index + 1)).join(`
179913
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1));
179914
+ if (results.some((result) => result.source === "message")) {
179915
+ bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
179916
+ }
179917
+ const body = bodyParts.join(`
179669
179918
 
179670
179919
  `);
179671
179920
  return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
@@ -179825,6 +180074,11 @@ function registerMagicContextTools(pi, opts) {
179825
180074
 
179826
180075
  // src/index.ts
179827
180076
  var PREFIX2 = "[magic-context][pi]";
180077
+ function resolveCurrentProject(ctx) {
180078
+ const projectDir = ctx.cwd;
180079
+ const projectIdentity = resolveProjectIdentity(projectDir);
180080
+ return { projectDir, projectIdentity };
180081
+ }
179828
180082
  function persistPiMessageEndModelMeta(args) {
179829
180083
  if (!args.message || typeof args.message !== "object")
179830
180084
  return;
@@ -179926,7 +180180,7 @@ async function persistPiPressureFromMessageEnd(args) {
179926
180180
  }
179927
180181
  var PLUGIN_VERSION = (() => {
179928
180182
  try {
179929
- const req = createRequire3(import.meta.url);
180183
+ const req = createRequire4(import.meta.url);
179930
180184
  return req("../package.json").version;
179931
180185
  } catch {
179932
180186
  return "0.0.0";
@@ -180004,7 +180258,7 @@ function resolveDreamerFromConfig(config2) {
180004
180258
  }
180005
180259
  async function src_default2(pi) {
180006
180260
  const storageDir = getMagicContextStorageDir();
180007
- const dbPath = join18(storageDir, "context.db");
180261
+ const dbPath = join19(storageDir, "context.db");
180008
180262
  let db;
180009
180263
  try {
180010
180264
  db = openDatabase();
@@ -180015,6 +180269,7 @@ async function src_default2(pi) {
180015
180269
  }
180016
180270
  const projectDir = process.cwd();
180017
180271
  const projectIdentity = resolveProjectIdentity(projectDir);
180272
+ const seenDreamerProjectIdentities = new Set([projectIdentity]);
180018
180273
  info(`loaded v${PLUGIN_VERSION} | harness=pi | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
180019
180274
  const { config: config2, warnings, loadedFromPaths } = loadPiConfig({
180020
180275
  cwd: projectDir
@@ -180045,7 +180300,10 @@ async function src_default2(pi) {
180045
180300
  const historianConfig = resolveHistorianFromConfig(config2);
180046
180301
  if (historianConfig) {
180047
180302
  historianConfig.onStatusChange = (ctx) => {
180048
- updateStatusLine(ctx, { db, projectIdentity });
180303
+ updateStatusLine(ctx, {
180304
+ db,
180305
+ projectIdentity: resolveCurrentProject(ctx).projectIdentity
180306
+ });
180049
180307
  };
180050
180308
  }
180051
180309
  const nudgeConfig = resolveNudgeFromConfig(config2);
@@ -180084,12 +180342,17 @@ async function src_default2(pi) {
180084
180342
  registerCtxStatusCommand(pi, {
180085
180343
  db,
180086
180344
  projectIdentity,
180345
+ resolveProject: resolveCurrentProject,
180087
180346
  protectedTags: config2.protected_tags,
180088
180347
  nudgeIntervalTokens: config2.nudge_interval_tokens,
180089
180348
  executeThresholdPercentage: config2.execute_threshold_percentage,
180090
180349
  historyBudgetPercentage: config2.history_budget_percentage,
180091
180350
  commitClusterTrigger: config2.commit_cluster_trigger,
180092
- executeThresholdTokens: config2.execute_threshold_tokens
180351
+ executeThresholdTokens: config2.execute_threshold_tokens,
180352
+ dreamer: {
180353
+ enabled: config2.dreamer?.enabled ?? false,
180354
+ schedule: config2.dreamer?.schedule
180355
+ }
180093
180356
  });
180094
180357
  info("registered /ctx-status");
180095
180358
  registerStatusLine(pi, { db, projectIdentity });
@@ -180111,7 +180374,10 @@ async function src_default2(pi) {
180111
180374
  registerCtxDreamCommand(pi, {
180112
180375
  db,
180113
180376
  projectDir,
180114
- projectIdentity
180377
+ projectIdentity,
180378
+ resolveProject: resolveCurrentProject,
180379
+ dreamerEnabled: config2.dreamer?.enabled ?? false,
180380
+ onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity)
180115
180381
  });
180116
180382
  info("registered /ctx-dream");
180117
180383
  const dreamerConfig = resolveDreamerFromConfig(config2);
@@ -180123,7 +180389,8 @@ async function src_default2(pi) {
180123
180389
  config: dreamerConfig,
180124
180390
  embeddingConfig: config2.embedding,
180125
180391
  memoryEnabled: config2.memory.enabled,
180126
- gitCommitIndexing: config2.experimental.git_commit_indexing
180392
+ gitCommitIndexing: config2.experimental.git_commit_indexing,
180393
+ onAdjunctsRefreshNeeded: signalPiSystemPromptRefreshForProject
180127
180394
  });
180128
180395
  info(dreamerConfig.enabled ? `registered dreamer (schedule=${dreamerConfig.schedule}, tasks=[${dreamerConfig.tasks.join(",")}])` : "registered dreamer: DISABLED (dreamer.enabled=false)");
180129
180396
  } else {
@@ -180131,6 +180398,8 @@ async function src_default2(pi) {
180131
180398
  }
180132
180399
  pi.on("before_agent_start", async (event, ctx) => {
180133
180400
  try {
180401
+ const currentProject = resolveCurrentProject(ctx);
180402
+ seenDreamerProjectIdentities.add(currentProject.projectIdentity);
180134
180403
  const sm = ctx.sessionManager;
180135
180404
  let sessionId;
180136
180405
  if (sm !== undefined) {
@@ -180143,10 +180412,20 @@ async function src_default2(pi) {
180143
180412
  } catch {}
180144
180413
  }
180145
180414
  }
180415
+ if (sessionId) {
180416
+ trackSessionForProject(currentProject.projectIdentity, sessionId);
180417
+ }
180418
+ if (config2.system_prompt_injection?.enabled === false) {
180419
+ return;
180420
+ }
180421
+ const skipSigs = config2.system_prompt_injection?.skip_signatures ?? [];
180422
+ if (skipSigs.some((sig) => sig.length > 0 && event.systemPrompt.includes(sig))) {
180423
+ return;
180424
+ }
180146
180425
  const isCacheBusting = sessionId ? hasSystemPromptRefresh(sessionId) : true;
180147
180426
  const block = buildMagicContextBlock({
180148
180427
  db,
180149
- cwd: ctx.cwd,
180428
+ cwd: currentProject.projectDir,
180150
180429
  sessionId,
180151
180430
  memoryEnabled: config2.memory.enabled,
180152
180431
  injectDocs: config2.dreamer?.inject_docs ?? true,
@@ -180160,7 +180439,8 @@ async function src_default2(pi) {
180160
180439
  userMemoriesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.user_memories?.enabled ?? false),
180161
180440
  pinKeyFilesEnabled: (config2.dreamer?.enabled ?? false) && (config2.dreamer?.pin_key_files?.enabled ?? false),
180162
180441
  pinKeyFilesTokenBudget: config2.dreamer?.pin_key_files?.token_budget ?? 1e4,
180163
- isCacheBusting
180442
+ isCacheBusting,
180443
+ existingSystemPrompt: event.systemPrompt
180164
180444
  });
180165
180445
  const composedPrompt = block ? `${event.systemPrompt}
180166
180446
 
@@ -180252,10 +180532,13 @@ ${block}` : event.systemPrompt;
180252
180532
  const sessionId = sm?.getSessionId?.();
180253
180533
  if (typeof sessionId !== "string" || sessionId.length === 0)
180254
180534
  return;
180255
- const rawMessages = readPiSessionMessages(ctx);
180256
- const latest = rawMessages[rawMessages.length - 1];
180257
- if (latest?.role === "assistant" && latest.id.length > 0) {
180258
- scheduleIncrementalIndex(db, sessionId, latest.id, () => latest);
180535
+ const endedMsg = event.message;
180536
+ if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
180537
+ const messageId = endedMsg.id;
180538
+ scheduleIncrementalIndex(db, sessionId, messageId, () => {
180539
+ const rawMessages = readPiSessionMessages(ctx);
180540
+ return rawMessages.find((message) => message.id === messageId) ?? null;
180541
+ });
180259
180542
  }
180260
180543
  persistPiMessageEndModelMeta({
180261
180544
  db,
@@ -180358,7 +180641,9 @@ ${block}` : event.systemPrompt;
180358
180641
  warn("shutdown: dreamer drain threw:", err5);
180359
180642
  }
180360
180643
  try {
180361
- unregisterPiDreamerProject({ projectIdentity });
180644
+ for (const identity of seenDreamerProjectIdentities) {
180645
+ unregisterPiDreamerProject({ projectIdentity: identity });
180646
+ }
180362
180647
  } catch (err5) {
180363
180648
  warn("shutdown: unregisterPiDreamerProject threw:", err5);
180364
180649
  }