@cortexkit/pi-magic-context 0.44.0 → 0.44.1

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
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  setHarness,
3
3
  getHarness,
4
+ harnessOwnsOpenCodeStore,
4
5
  ensureCortexKitArtifactGitignore,
5
6
  getProjectMagicContextHistorianDir,
6
7
  getMagicContextStorageDir,
@@ -9,7 +10,7 @@ import {
9
10
  log,
10
11
  sessionLog,
11
12
  flushLogger
12
- } from "./index-tfzybajk.js";
13
+ } from "./index-59dmj4zs.js";
13
14
  import {
14
15
  COMPACTION_ENABLED_PATH,
15
16
  isDreamerRunnable,
@@ -43,12 +44,10 @@ import {
43
44
  cortexKitProjectConfigBasePath,
44
45
  migrateMagicContextConfigLocations,
45
46
  withContentLanguageDirective,
46
- buildPrimaryLanguageDirective,
47
47
  parseCron,
48
48
  nextOccurrence,
49
49
  nextDueAtMs,
50
50
  resolveModelConfigValue,
51
- resolveModelConfigOrDefault,
52
51
  DEFAULT_HISTORIAN_TIMEOUT_MS,
53
52
  LIVE_RELOAD_CONFIG_PATHS,
54
53
  getProtectedTokensTierOverrides,
@@ -61,6 +60,7 @@ import {
61
60
  ProjectIdentityError,
62
61
  resolveProjectIdentityStrict,
63
62
  resolveProjectIdentity,
63
+ isUsableProjectIdentity,
64
64
  resolveProjectIdentityForSession,
65
65
  beginBootQuietPeriod,
66
66
  scheduleAfterBootQuiet,
@@ -74,42 +74,6 @@ import {
74
74
  indexSingleMessage,
75
75
  indexMessagesAfterOrdinal,
76
76
  sweepOrphanedOpenCodeMessageIndexes,
77
- recordSessionProjectIdentity,
78
- piHarnessKindFromExecutable,
79
- COMPARTMENT_LEASE_RENEWAL_MS,
80
- acquireCompartmentLease,
81
- renewCompartmentLease,
82
- releaseCompartmentLease,
83
- releaseCompartmentLeaseBestEffort,
84
- isCompartmentLeaseHeld,
85
- isNoContentCompartment,
86
- HAS_COMPARTMENT_CONTENT_SQL,
87
- persistCachedM0,
88
- clearCachedM0M1,
89
- getCompartments,
90
- getLastCompartmentEndMessage,
91
- getLastCompartmentEndMessageId,
92
- getCompartmentsByEndMessageId,
93
- appendCompartments,
94
- saveRecompStagingPass,
95
- getRecompStaging,
96
- clearRecompStaging,
97
- getRecompPartialRange,
98
- setRecompPartialRange,
99
- escapeXmlAttr,
100
- escapeXmlContent,
101
- getModuleNoteEvaluationBridge,
102
- getContextStoreUuid,
103
- getAuthorityManagedMarker,
104
- drainMirrorPages,
105
- V2_MEMORY_CATEGORIES,
106
- parseCompartmentOutput,
107
- resolveWorkspaceShareCategories,
108
- resolveWorkspaceIdentitySet,
109
- expandWorkspaceIdentitySetWithAliases,
110
- sourceNameForMemory,
111
- computeWorkspaceEpochFingerprint,
112
- readProjectDocsCanonical,
113
77
  MAX_EXECUTE_THRESHOLD,
114
78
  escalationBands,
115
79
  HYGIENE_PROVIDER_UNITS_VERSION,
@@ -118,6 +82,10 @@ import {
118
82
  computeProtectionWindow,
119
83
  readEpochFloorSnapshot,
120
84
  getProtectionWindowForSession,
85
+ persistCachedM0,
86
+ clearCachedM0M1,
87
+ readReplayDocument,
88
+ updateReplayDocument,
121
89
  isProviderOverflowFailClosedProven,
122
90
  describeProtectedTailDrainBudgetSkip,
123
91
  loadProtectedTailMeta,
@@ -194,6 +162,45 @@ import {
194
162
  setSessionWorkMetrics,
195
163
  getSessionWorkMetrics,
196
164
  resolveEpochFloorForPass,
165
+ getOrCreateSessionMeta,
166
+ updateSessionMeta,
167
+ advanceToolReclaimWatermark,
168
+ retryPendingSessionCleanups,
169
+ retryPendingRustSessionCleanupsForProject,
170
+ recordSessionProjectIdentity,
171
+ piHarnessKindFromExecutable,
172
+ COMPARTMENT_LEASE_RENEWAL_MS,
173
+ acquireCompartmentLease,
174
+ renewCompartmentLease,
175
+ releaseCompartmentLease,
176
+ releaseCompartmentLeaseBestEffort,
177
+ isCompartmentLeaseHeld,
178
+ isNoContentCompartment,
179
+ HAS_COMPARTMENT_CONTENT_SQL,
180
+ getCompartments,
181
+ getLastCompartmentEndMessage,
182
+ getLastCompartmentEndMessageId,
183
+ getCompartmentsByEndMessageId,
184
+ appendCompartments,
185
+ saveRecompStagingPass,
186
+ getRecompStaging,
187
+ clearRecompStaging,
188
+ getRecompPartialRange,
189
+ setRecompPartialRange,
190
+ escapeXmlAttr,
191
+ escapeXmlContent,
192
+ getModuleNoteEvaluationBridge,
193
+ getContextStoreUuid,
194
+ getAuthorityManagedMarker,
195
+ drainMirrorPages,
196
+ V2_MEMORY_CATEGORIES,
197
+ parseCompartmentOutput,
198
+ resolveWorkspaceShareCategories,
199
+ resolveWorkspaceIdentitySet,
200
+ expandWorkspaceIdentitySetWithAliases,
201
+ sourceNameForMemory,
202
+ computeWorkspaceEpochFingerprint,
203
+ readProjectDocsCanonical,
197
204
  BoundedSessionMap,
198
205
  registerLkgPersistence,
199
206
  signatureForFields,
@@ -231,11 +238,6 @@ import {
231
238
  getMemoryMutationsForRenderByProjects,
232
239
  getMaxMemoryMutationId,
233
240
  getMaxMemoryMutationIdForProjects,
234
- getOrCreateSessionMeta,
235
- updateSessionMeta,
236
- advanceToolReclaimWatermark,
237
- retryPendingSessionCleanups,
238
- retryPendingRustSessionCleanupsForProject,
239
241
  getNotes,
240
242
  getSessionNotes,
241
243
  getPendingSmartNotes,
@@ -374,6 +376,8 @@ import {
374
376
  ensureProjectRegisteredFromPiDirectory,
375
377
  resolvePiHarnessDetection,
376
378
  resolvePiHarnessKind,
379
+ MEMORY_MURAL_BLOCK,
380
+ buildMagicContextSection,
377
381
  resolveMuralWire,
378
382
  updateCompactionMarkerAfterPublication,
379
383
  COMPARTMENT_RENDER_EPOCH,
@@ -404,7 +408,7 @@ import {
404
408
  registerTodoStateLifecycle,
405
409
  syncCtxMemoryToolEnabled,
406
410
  registerMagicContextTools
407
- } from "./index-8ytss1hp.js";
411
+ } from "./index-mwpk0wn9.js";
408
412
  import {
409
413
  CTX_REDUCE_KEEP,
410
414
  newestCtxReduceTagNumbers,
@@ -483,6 +487,7 @@ import {
483
487
  claimOpenCodeDbDiagnosticOnce,
484
488
  Database,
485
489
  closeQuietly,
490
+ RAW_SUMMARY_TEXT_MAX_CHARS,
486
491
  completedToolArcCrossesBoundary,
487
492
  estimateTrueRawMessageTokens,
488
493
  buildToolArcs,
@@ -507,6 +512,7 @@ import {
507
512
  withRawSessionMessageCache2,
508
513
  readRawSessionMessages2,
509
514
  getRawSessionMessageOrdinalCount2,
515
+ visitRawSessionMessages2,
510
516
  readRawSessionMessageRange2,
511
517
  primeTailRawMessageCache2,
512
518
  getCachedAbsoluteMessageCount2,
@@ -516,14 +522,14 @@ import {
516
522
  getRawSessionTagKeysThrough2,
517
523
  getLegacyProtectedTailStartOrdinal2,
518
524
  readSessionChunk2
519
- } from "./index-2kj55q4e.js";
525
+ } from "./index-nm3c0sn8.js";
520
526
  import {
521
527
  pushNotification2
522
528
  } from "./index-b3eqj1g6.js";
523
- import"./index-jscq7067.js";
529
+ import"./index-5fw91enm.js";
524
530
  import {
525
531
  logSlowWriteTransaction
526
- } from "./index-mmkdxa5j.js";
532
+ } from "./index-97e5fzy3.js";
527
533
  import {
528
534
  __toESM
529
535
  } from "./index-22jkk6wg.js";
@@ -531,7 +537,7 @@ import {
531
537
  // src/index.ts
532
538
  import { AsyncLocalStorage } from "node:async_hooks";
533
539
  import { createRequire as createRequire2 } from "node:module";
534
- import { join as join8, resolve as resolve4 } from "node:path";
540
+ import { join as join11, resolve as resolve4 } from "node:path";
535
541
 
536
542
  // ../plugin/src/config/live-snapshot.ts
537
543
  import { createHash } from "node:crypto";
@@ -1319,6 +1325,67 @@ function parseReportedLimit(message) {
1319
1325
  return;
1320
1326
  }
1321
1327
 
1328
+ // ../plugin/src/shared/model-cache-ttl.ts
1329
+ var MODEL_CACHE_LIFETIMES = [
1330
+ {
1331
+ source: "OpenAI GPT-5.6+ default",
1332
+ value: "30m",
1333
+ matches(model) {
1334
+ const version = /^gpt-(\d+)(?:\.(\d+))?(?:$|[^\d.])/.exec(model);
1335
+ if (!version)
1336
+ return false;
1337
+ const major = Number(version[1]);
1338
+ const minor = Number(version[2] ?? 0);
1339
+ return major > 5 || major === 5 && minor >= 6;
1340
+ }
1341
+ }
1342
+ ];
1343
+ function resolveModelCacheTtl(config, modelKey) {
1344
+ if (config && typeof config !== "string") {
1345
+ const match = modelKey && !modelKey.includes("/") && Object.hasOwn(config, modelKey) && modelKey !== "default" ? config[modelKey] : resolveModelConfigValue(config, modelKey)?.value;
1346
+ if (match !== undefined)
1347
+ return { value: match, source: "config", modelKey };
1348
+ }
1349
+ if (typeof config === "string" && config !== "5m")
1350
+ return { value: config, source: "config", modelKey };
1351
+ const model = canonicalModelIdentity(modelKey ?? "").toLowerCase().split("/").at(-1) ?? "";
1352
+ const known = MODEL_CACHE_LIFETIMES.find((entry) => entry.matches(model));
1353
+ if (known)
1354
+ return { value: known.value, source: known.source, modelKey };
1355
+ return {
1356
+ value: (typeof config === "object" ? config.default : config) ?? "5m",
1357
+ source: "default",
1358
+ modelKey
1359
+ };
1360
+ }
1361
+
1362
+ // ../plugin/src/features/magic-context/session-cache-ttl.ts
1363
+ function readSessionCacheTtl(db, sessionId) {
1364
+ const saved = readReplayDocument(db, sessionId).cacheTtlPolicy;
1365
+ return saved && typeof saved.value === "string" && saved.config !== undefined ? saved : undefined;
1366
+ }
1367
+ function resolveSessionCacheTtl(db, sessionId, config, modelKey) {
1368
+ const meta = getOrCreateSessionMeta(db, sessionId);
1369
+ let saved = readSessionCacheTtl(db, sessionId);
1370
+ if (!modelKey)
1371
+ return saved ?? resolveModelCacheTtl(config ?? meta.cacheTtl, undefined);
1372
+ if (!saved || saved.modelKey !== modelKey) {
1373
+ const frozenConfig = saved?.config ?? config ?? meta.cacheTtl;
1374
+ const resolved = resolveModelCacheTtl(frozenConfig, modelKey);
1375
+ const next = { ...resolved, config: frozenConfig };
1376
+ if (!updateReplayDocument(db, sessionId, (doc) => {
1377
+ doc.version = 2;
1378
+ doc.cacheTtlPolicy = next;
1379
+ return true;
1380
+ }))
1381
+ throw new Error("cannot persist session cache TTL policy");
1382
+ saved = next;
1383
+ }
1384
+ if (meta.cacheTtl !== saved.value)
1385
+ updateSessionMeta(db, sessionId, { cacheTtl: saved.value });
1386
+ return saved;
1387
+ }
1388
+
1322
1389
  // ../plugin/src/features/magic-context/session-project-backfill.ts
1323
1390
  import { randomUUID } from "node:crypto";
1324
1391
  import { existsSync } from "node:fs";
@@ -1896,141 +1963,6 @@ function producerSourceLocalBudget(providerTokens, modelKey) {
1896
1963
  return ratio === 1 ? providerTokens : localBudget(providerTokens, ratio);
1897
1964
  }
1898
1965
 
1899
- // ../plugin/src/hooks/magic-context/event-resolvers.ts
1900
- var DEFAULT_CONTEXT_LIMIT = 200000;
1901
- function resolveCacheTtl(cacheTtl, modelKey) {
1902
- if (typeof cacheTtl === "string") {
1903
- return cacheTtl;
1904
- }
1905
- return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
1906
- }
1907
- var clampWarnSeen = new Set;
1908
- function isFinitePositive(v) {
1909
- return typeof v === "number" && Number.isFinite(v) && v > 0;
1910
- }
1911
- function* modelKeyLookupOrder(modelKey) {
1912
- const slash = modelKey.indexOf("/");
1913
- const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
1914
- let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
1915
- while (modelId.length > 0) {
1916
- for (const providerRef of providerRefs) {
1917
- const providerSlash = providerRef.indexOf("/");
1918
- yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
1919
- }
1920
- yield modelId;
1921
- const lastDash = modelId.lastIndexOf("-");
1922
- if (lastDash <= 0)
1923
- break;
1924
- modelId = modelId.slice(0, lastDash);
1925
- }
1926
- }
1927
- function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
1928
- if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
1929
- const contextLimit = options.contextLimit;
1930
- const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
1931
- if (tokenMatch && isFinitePositive(tokenMatch.value)) {
1932
- const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
1933
- const effectiveTokens = Math.min(tokenMatch.value, cap);
1934
- if (effectiveTokens < tokenMatch.value) {
1935
- const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
1936
- if (!clampWarnSeen.has(dedupeKey)) {
1937
- clampWarnSeen.add(dedupeKey);
1938
- const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
1939
- if (options.sessionId) {
1940
- sessionLog(options.sessionId, `WARN: ${msg}`);
1941
- } else {
1942
- log(`[magic-context] WARN: ${msg}`);
1943
- }
1944
- }
1945
- }
1946
- const percentage = effectiveTokens / contextLimit * 100;
1947
- const detail = {
1948
- percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
1949
- mode: "tokens",
1950
- absoluteTokens: Math.floor(effectiveTokens),
1951
- matchedKey: tokenMatch.matchedKey
1952
- };
1953
- if (effectiveTokens < tokenMatch.value) {
1954
- detail.clamped = true;
1955
- detail.configuredValue = tokenMatch.value;
1956
- }
1957
- return detail;
1958
- }
1959
- }
1960
- let resolved;
1961
- let matchedKey;
1962
- if (typeof config === "number") {
1963
- resolved = config;
1964
- } else if (modelKey) {
1965
- let matched;
1966
- for (const candidate of modelKeyLookupOrder(modelKey)) {
1967
- if (typeof config[candidate] === "number") {
1968
- matched = config[candidate];
1969
- matchedKey = candidate;
1970
- break;
1971
- }
1972
- }
1973
- if (matched === undefined && typeof config.default === "number") {
1974
- resolved = config.default;
1975
- matchedKey = "default";
1976
- } else {
1977
- resolved = matched ?? fallback;
1978
- }
1979
- } else if (typeof config.default === "number") {
1980
- resolved = config.default;
1981
- matchedKey = "default";
1982
- } else {
1983
- resolved = fallback;
1984
- }
1985
- if (!Number.isFinite(resolved) || resolved < 0) {
1986
- resolved = fallback;
1987
- }
1988
- const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
1989
- const percentageClamped = cappedPercentage < resolved;
1990
- if (percentageClamped) {
1991
- const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
1992
- if (!clampWarnSeen.has(dedupeKey)) {
1993
- clampWarnSeen.add(dedupeKey);
1994
- const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
1995
- if (options?.sessionId) {
1996
- sessionLog(options.sessionId, `WARN: ${msg}`);
1997
- } else {
1998
- log(`[magic-context] WARN: ${msg}`);
1999
- }
2000
- }
2001
- }
2002
- const detail = {
2003
- percentage: cappedPercentage,
2004
- mode: "percentage",
2005
- matchedKey
2006
- };
2007
- if (percentageClamped) {
2008
- detail.clamped = true;
2009
- detail.configuredValue = resolved;
2010
- }
2011
- return detail;
2012
- }
2013
- function resolveExecuteThreshold(config, modelKey, fallback, options) {
2014
- return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
2015
- }
2016
- function resolveTokensMatchWithKey(tokensConfig, modelKey) {
2017
- if (!tokensConfig) {
2018
- return;
2019
- }
2020
- if (modelKey) {
2021
- for (const candidate of modelKeyLookupOrder(modelKey)) {
2022
- const value = tokensConfig[candidate];
2023
- if (typeof value === "number") {
2024
- return { value, matchedKey: candidate };
2025
- }
2026
- }
2027
- }
2028
- if (typeof tokensConfig.default === "number") {
2029
- return { value: tokensConfig.default, matchedKey: "default" };
2030
- }
2031
- return;
2032
- }
2033
-
2034
1966
  // ../plugin/src/hooks/magic-context/note-nudger.ts
2035
1967
  var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
2036
1968
  var lastDeliveredAt = new Map;
@@ -3078,6 +3010,8 @@ import { parse, resolve as resolve3 } from "node:path";
3078
3010
 
3079
3011
  // ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
3080
3012
  function openOpenCodeDb() {
3013
+ if (!harnessOwnsOpenCodeStore())
3014
+ return null;
3081
3015
  const resolution = resolveOpenCodeDbPath();
3082
3016
  const dbPath = resolution.path;
3083
3017
  if (!openCodeDbPathExists(resolution)) {
@@ -4007,6 +3941,15 @@ var HISTORIAN_AGENT = "historian";
4007
3941
  var HISTORIAN_RECOMP_AGENT = "historian-recomp";
4008
3942
  var HISTORIAN_EDITOR_AGENT = "historian-editor";
4009
3943
 
3944
+ // ../plugin/src/config/live-child-output-cap.ts
3945
+ var historianCaps = new BoundedSessionMap(4096);
3946
+ function rememberHistorianOutputCap(sessionId, cap) {
3947
+ historianCaps.set(sessionId, { value: cap });
3948
+ }
3949
+ function forgetHistorianOutputCap(sessionId) {
3950
+ historianCaps.delete(sessionId);
3951
+ }
3952
+
4010
3953
  // ../plugin/src/features/magic-context/subagent-token-capture.ts
4011
3954
  function asNumber(value) {
4012
3955
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -5423,6 +5366,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5423
5366
  const id = typeof created?.id === "string" ? created.id : "";
5424
5367
  if (id && run.kind === "dreamer-task")
5425
5368
  asyncChildren.add(id);
5369
+ if (id && run.kind !== "dreamer-task")
5370
+ rememberHistorianOutputCap(id, run.maxOutputTokens);
5426
5371
  return { id, childSessionId: id || undefined };
5427
5372
  },
5428
5373
  async attempt(handle, request) {
@@ -5459,8 +5404,10 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
5459
5404
  };
5460
5405
  },
5461
5406
  async close(handle, settlement) {
5462
- if (handle?.id)
5407
+ if (handle?.id) {
5463
5408
  asyncChildren.delete(handle.id);
5409
+ forgetHistorianOutputCap(handle.id);
5410
+ }
5464
5411
  if (!client)
5465
5412
  return;
5466
5413
  await teardownChildSession({
@@ -10603,12 +10550,12 @@ async function promotePrimers(args) {
10603
10550
 
10604
10551
  // ../plugin/src/features/magic-context/dreamer/primer-seed.ts
10605
10552
  var PRIMER_SEED_CAP_TOKENS = 4000;
10606
- function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTokens) {
10553
+ function renderUserAndToolOrientation(sessionId, startOrdinal, endOrdinal, capTokens) {
10607
10554
  const lines = [];
10608
10555
  let tokens = 0;
10609
- for (const msg of messages) {
10610
- if (msg.ordinal < startOrdinal || msg.ordinal > endOrdinal)
10611
- continue;
10556
+ let sawMessage = false;
10557
+ visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, (msg) => {
10558
+ sawMessage = true;
10612
10559
  const out = [];
10613
10560
  if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
10614
10561
  const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
@@ -10621,22 +10568,27 @@ function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTok
10621
10568
  const lineTokens = estimateTokens(line);
10622
10569
  if (tokens + lineTokens > capTokens && lines.length > 0) {
10623
10570
  lines.push("… (orientation truncated; investigate the current source directly)");
10624
- return lines.join(`
10625
- `);
10571
+ return false;
10626
10572
  }
10627
10573
  lines.push(line);
10628
10574
  tokens += lineTokens;
10629
10575
  }
10630
- }
10631
- return lines.join(`
10632
- `);
10576
+ return true;
10577
+ }, { summary: true });
10578
+ return sawMessage ? lines.join(`
10579
+ `) : null;
10580
+ }
10581
+ function selectPrimerOriginCandidate(db, primer) {
10582
+ return getPrimerCandidatesByIds(db, primer.sourceCandidateIds).filter((candidate) => candidate.projectPath === primer.projectPath).sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10633
10583
  }
10634
10584
  function loadPrePostP1(db, sessionId, originStartMessage) {
10635
10585
  const origin = db.prepare("SELECT sequence FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
10636
10586
  if (typeof origin?.sequence !== "number")
10637
10587
  return "";
10638
10588
  const originSeq = origin.sequence;
10639
- const rows = db.prepare(`SELECT sequence, start_message, end_message, title, p1, content
10589
+ const rows = db.prepare(`SELECT sequence, start_message, end_message,
10590
+ substr(title, 1, 512) AS title,
10591
+ substr(p1, 1, 1200) AS p1, substr(content, 1, 1200) AS content
10640
10592
  FROM compartments
10641
10593
  WHERE session_id = ? AND sequence IN (?, ?)
10642
10594
  ORDER BY sequence ASC`).all(sessionId, originSeq - 1, originSeq + 1);
@@ -10650,28 +10602,28 @@ function loadPrePostP1(db, sessionId, originStartMessage) {
10650
10602
  `);
10651
10603
  }
10652
10604
  function closedBookOriginP1(db, sessionId, originStartMessage) {
10653
- const row = db.prepare("SELECT title, p1, content FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
10605
+ const row = db.prepare(`SELECT substr(title, 1, 512) AS title,
10606
+ substr(p1, 1, 2000) AS p1, substr(content, 1, 2000) AS content
10607
+ FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1`).get(sessionId, originStartMessage);
10654
10608
  const body = (row?.p1 ?? row?.content ?? "").slice(0, 2000);
10655
10609
  const orientation = row?.title ? `${row.title}: ${body}` : body;
10656
10610
  return { orientation, sessionId };
10657
10611
  }
10658
10612
  function buildPrimerSeed(db, primer) {
10659
- const candidates = getPrimerCandidatesByIds(db, primer.sourceCandidateIds);
10660
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10613
+ const mostRecent = selectPrimerOriginCandidate(db, primer);
10661
10614
  if (!mostRecent || typeof mostRecent.sourceCompartmentStart !== "number" || typeof mostRecent.sourceCompartmentEnd !== "number") {
10662
10615
  return { kind: "closed-book", orientation: "", prePost: "", sessionId: null };
10663
10616
  }
10664
10617
  const sessionId = mostRecent.sessionId;
10665
10618
  const start = mostRecent.sourceCompartmentStart;
10666
10619
  const end = mostRecent.sourceCompartmentEnd;
10667
- let raw = [];
10620
+ let orientation = null;
10668
10621
  try {
10669
- raw = readRawSessionMessages2(sessionId);
10622
+ orientation = renderUserAndToolOrientation(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
10670
10623
  } catch {
10671
- raw = [];
10624
+ orientation = null;
10672
10625
  }
10673
- const inRange = raw.some((m) => m.ordinal >= start && m.ordinal <= end);
10674
- if (!inRange) {
10626
+ if (orientation === null) {
10675
10627
  const closed = closedBookOriginP1(db, sessionId, start);
10676
10628
  return {
10677
10629
  kind: "closed-book",
@@ -10680,7 +10632,6 @@ function buildPrimerSeed(db, primer) {
10680
10632
  sessionId
10681
10633
  };
10682
10634
  }
10683
- const orientation = renderUserAndToolOrientation(raw, start, end, PRIMER_SEED_CAP_TOKENS);
10684
10635
  return {
10685
10636
  kind: "raw",
10686
10637
  orientation,
@@ -10914,9 +10865,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
10914
10865
  }
10915
10866
  }
10916
10867
  function originSessionIdForPrimer(args, primer) {
10917
- const candidates = getPrimerCandidatesByIds(args.db, primer.sourceCandidateIds);
10918
- const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
10919
- return mostRecent?.sessionId ?? null;
10868
+ return selectPrimerOriginCandidate(args.db, primer)?.sessionId ?? null;
10920
10869
  }
10921
10870
  function recordInvocation3(args, startedAt, params) {
10922
10871
  if (!args.parentSessionId)
@@ -11314,11 +11263,48 @@ function normalizeOpenCodeRows(db, sessionId, rows) {
11314
11263
  return [];
11315
11264
  const messageIds = rows.map((row) => row.id);
11316
11265
  const placeholders = messageIds.map(() => "?").join(", ");
11317
- const partRows = db.prepare(`SELECT message_id, data
11266
+ const userIds = rows.filter((row) => parseJsonRecord(row.data)?.role === "user").map((row) => row.id);
11267
+ const userPlaceholders = userIds.map(() => "?").join(", ") || "NULL";
11268
+ const output = `CASE json_type(data, '$.state.output')
11269
+ WHEN 'text' THEN json_extract(data, '$.state.output')
11270
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.output', '') END`;
11271
+ const error = `CASE json_type(data, '$.state.error')
11272
+ WHEN 'text' THEN json_extract(data, '$.state.error')
11273
+ WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.error', '') END`;
11274
+ const containsError = (value) => ["error", "failed", "exception", "traceback"].map((word) => `(' ' || lower(${value}) || ' ') GLOB '*[^a-z0-9_]${word}[^a-z0-9_]*'`).join(" OR ");
11275
+ const errorWords = `CASE WHEN json_type(data, '$.state.output') IN ('object', 'array')
11276
+ THEN EXISTS (SELECT 1 FROM json_tree(data, '$.state.output') AS leaf
11277
+ WHERE (leaf.type = 'text' AND (${containsError("json_quote(leaf.atom)")}))
11278
+ OR (typeof(leaf.key) = 'text' AND (${containsError("json_quote(leaf.key)")})))
11279
+ WHEN instr(CAST(${output} AS BLOB), x'00') > 0 THEN EXISTS (
11280
+ WITH RECURSIVE segments(rest, piece) AS (
11281
+ SELECT CAST(${output} AS BLOB), x''
11282
+ UNION ALL
11283
+ SELECT CASE WHEN instr(rest, x'00') > 0
11284
+ THEN substr(rest, instr(rest, x'00') + 1) ELSE NULL END,
11285
+ CASE WHEN instr(rest, x'00') > 0
11286
+ THEN substr(rest, 1, instr(rest, x'00') - 1) ELSE rest END
11287
+ FROM segments WHERE rest IS NOT NULL
11288
+ ) SELECT 1 FROM segments WHERE (${containsError("CAST(piece AS TEXT)")}))
11289
+ ELSE (${containsError(output)}) END`;
11290
+ const whitespace = `
11291
+ \v\f\r              \u2028\u2029   \uFEFF`;
11292
+ const partRows = db.prepare(`SELECT message_id, CASE json_extract(data, '$.type')
11293
+ WHEN 'text' THEN CASE WHEN message_id IN (${userPlaceholders}) THEN json_object(
11294
+ 'type', 'text', 'text', data -> '$.text',
11295
+ 'synthetic', data -> '$.synthetic', 'ignored', data -> '$.ignored') ELSE '{}' END
11296
+ WHEN 'tool' THEN json_object(
11297
+ 'type', 'tool', 'tool', data -> '$.tool',
11298
+ 'state', json_object('isError', json(CASE WHEN
11299
+ json_type(data, '$.state.isError') = 'true'
11300
+ OR lower(json_extract(data, '$.state.status')) = 'error'
11301
+ OR length(CAST(trim(${error}, ?) AS BLOB)) > 0 OR ${errorWords}
11302
+ THEN 'true' ELSE 'false' END)))
11303
+ ELSE '{}' END AS data
11318
11304
  FROM part
11319
- WHERE +session_id = ?
11305
+ WHERE +session_id = ? AND json_valid(data)
11320
11306
  AND likelihood(message_id IN (${placeholders}), 0.000001)
11321
- ORDER BY time_created ASC, id ASC`).all(sessionId, ...messageIds);
11307
+ ORDER BY time_created ASC, id ASC`).all(...userIds, whitespace, sessionId, ...messageIds);
11322
11308
  const partsByMessageId = new Map;
11323
11309
  for (const row of partRows) {
11324
11310
  const parts = partsByMessageId.get(row.message_id) ?? [];
@@ -13568,6 +13554,8 @@ async function runManualDream(deps) {
13568
13554
  backlogBefore: {},
13569
13555
  backlogAfter: {}
13570
13556
  };
13557
+ if (!isUsableProjectIdentity(deps.projectIdentity))
13558
+ return result;
13571
13559
  let selected;
13572
13560
  let forceGate = false;
13573
13561
  if (deps.task) {
@@ -13641,6 +13629,8 @@ async function runManualDream(deps) {
13641
13629
  return result;
13642
13630
  }
13643
13631
  async function runDueTasksForProject(deps) {
13632
+ if (!isUsableProjectIdentity(deps.projectIdentity))
13633
+ return 0;
13644
13634
  const now = deps.now ?? Date.now();
13645
13635
  const due = planDueTasks(deps.db, deps.projectIdentity, deps.tasks, now);
13646
13636
  if (due.length === 0)
@@ -13886,6 +13876,13 @@ function openTimerDatabaseOrNull(context) {
13886
13876
  }
13887
13877
  return db;
13888
13878
  }
13879
+ var refusedEmptyIdentityDirectories = new Set;
13880
+ function logEmptyIdentityRefusalOnce(directory) {
13881
+ if (refusedEmptyIdentityDirectories.has(directory))
13882
+ return;
13883
+ refusedEmptyIdentityDirectories.add(directory);
13884
+ log(`[dreamer] not registering ${directory}: it has no project identity, so no project-scoped work runs for it`);
13885
+ }
13889
13886
  var registeredProjects = new Map;
13890
13887
  function stopDreamScheduleTimerIfIdle() {
13891
13888
  if (registeredProjects.size !== 0)
@@ -13905,6 +13902,10 @@ function stopDreamScheduleTimerIfIdle() {
13905
13902
  log("[dreamer] stopped dream schedule timer (no projects left)");
13906
13903
  }
13907
13904
  async function startDreamScheduleTimer(args) {
13905
+ if (!isUsableProjectIdentity(args.projectIdentity)) {
13906
+ logEmptyIdentityRefusalOnce(args.directory);
13907
+ return;
13908
+ }
13908
13909
  beginBootQuietPeriod();
13909
13910
  const db = openTimerDatabaseOrNull("schedule timer registration");
13910
13911
  if (!db)
@@ -15469,13 +15470,101 @@ function terminateChild(child) {
15469
15470
  }
15470
15471
  }
15471
15472
 
15473
+ // src/dreamer/primer-raw-provider-pi.ts
15474
+ import { join as join8 } from "node:path";
15475
+
15476
+ // src/dreamer/bounded-session-reader.ts
15477
+ import { closeSync, opendirSync, openSync, readSync } from "node:fs";
15478
+ import { join as join6 } from "node:path";
15479
+ var MAX_ENTRY_BYTES = 1024 * 1024;
15480
+ function* lines(path) {
15481
+ const fd = openSync(path, "r");
15482
+ const buffer = Buffer.alloc(64 * 1024);
15483
+ let pending = Buffer.alloc(0);
15484
+ try {
15485
+ while (true) {
15486
+ const bytes = readSync(fd, buffer, 0, buffer.length, null);
15487
+ if (bytes === 0)
15488
+ break;
15489
+ let start = 0;
15490
+ for (let i = 0;i < bytes; i++) {
15491
+ if (buffer[i] !== 10)
15492
+ continue;
15493
+ if (pending.length + i - start > MAX_ENTRY_BYTES)
15494
+ throw new Error("Pi session entry exceeds bounded reader capacity");
15495
+ yield Buffer.concat([pending, buffer.subarray(start, i)]).toString("utf8");
15496
+ pending = Buffer.alloc(0);
15497
+ start = i + 1;
15498
+ }
15499
+ if (pending.length + bytes - start > MAX_ENTRY_BYTES)
15500
+ throw new Error("Pi session entry exceeds bounded reader capacity");
15501
+ pending = Buffer.concat([pending, buffer.subarray(start, bytes)]);
15502
+ }
15503
+ if (pending.length)
15504
+ yield pending.toString("utf8");
15505
+ } finally {
15506
+ closeSync(fd);
15507
+ }
15508
+ }
15509
+ function record(value) {
15510
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
15511
+ }
15512
+ function* sessionFiles(directory, nested) {
15513
+ let dir;
15514
+ try {
15515
+ dir = opendirSync(directory);
15516
+ } catch {
15517
+ return;
15518
+ }
15519
+ try {
15520
+ while (true) {
15521
+ const entry = dir.readSync();
15522
+ if (!entry)
15523
+ break;
15524
+ const path = join6(directory, entry.name);
15525
+ if (entry.isFile() && entry.name.endsWith(".jsonl"))
15526
+ yield path;
15527
+ else if (nested && entry.isDirectory())
15528
+ yield* sessionFiles(path, false);
15529
+ }
15530
+ } finally {
15531
+ dir.closeSync();
15532
+ }
15533
+ }
15534
+ function findSession(directory, nested, sessionId) {
15535
+ for (const header of sessionHeaders(directory, nested)) {
15536
+ if (header.id === sessionId)
15537
+ return header.path;
15538
+ }
15539
+ return null;
15540
+ }
15541
+ function* sessionHeaders(directory, nested) {
15542
+ for (const path of sessionFiles(directory, nested)) {
15543
+ try {
15544
+ for (const line of lines(path)) {
15545
+ const header = record(JSON.parse(line));
15546
+ if (header.type === "session")
15547
+ yield { ...header, path };
15548
+ break;
15549
+ }
15550
+ } catch {}
15551
+ }
15552
+ }
15553
+ function* sessionEntries(path) {
15554
+ for (const line of lines(path)) {
15555
+ try {
15556
+ yield JSON.parse(line);
15557
+ } catch {}
15558
+ }
15559
+ }
15560
+
15472
15561
  // src/dreamer/pi-session-api.ts
15473
15562
  import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
15474
15563
  import {
15475
15564
  basename as basename2,
15476
15565
  dirname as dirname2,
15477
15566
  extname,
15478
- join as join6,
15567
+ join as join7,
15479
15568
  relative,
15480
15569
  resolve,
15481
15570
  sep as sep2
@@ -15525,11 +15614,11 @@ function resolveRunningEntry() {
15525
15614
  function findPackageRoot(startDir) {
15526
15615
  let dir = startDir;
15527
15616
  while (dir !== dirname2(dir)) {
15528
- const pkg = readManifest(join6(dir, "package.json"));
15617
+ const pkg = readManifest(join7(dir, "package.json"));
15529
15618
  if (isCodingAgentPackageName(pkg?.name)) {
15530
15619
  if (basename2(dir) === "dist") {
15531
15620
  const parentDir = dirname2(dir);
15532
- const parentPkg = readManifest(join6(parentDir, "package.json"));
15621
+ const parentPkg = readManifest(join7(parentDir, "package.json"));
15533
15622
  if (parentPkg?.name === pkg.name) {
15534
15623
  return { dir: parentDir, pkg: parentPkg };
15535
15624
  }
@@ -15588,7 +15677,7 @@ function toSourceEntry(entryPath, pkgDir) {
15588
15677
  if (srcRel === rel) {
15589
15678
  return null;
15590
15679
  }
15591
- return join6(pkgDir, srcRel);
15680
+ return join7(pkgDir, srcRel);
15592
15681
  }
15593
15682
  var defaultLoaders = [
15594
15683
  {
@@ -15673,35 +15762,108 @@ async function loadDefaultPiSessionApi(loaders) {
15673
15762
  }
15674
15763
 
15675
15764
  // src/dreamer/primer-raw-provider-pi.ts
15676
- function createPiPrimerRawProviderFactory(deps = {}) {
15677
- let resolved = null;
15678
- const resolveDeps = async () => {
15679
- if (deps.listSessions && deps.loadEntriesFromFile) {
15680
- return {
15681
- listSessions: deps.listSessions,
15682
- loadEntriesFromFile: deps.loadEntriesFromFile
15683
- };
15765
+ var MAX_PARTS = 256;
15766
+ var TOOL_KEYS = [
15767
+ "description",
15768
+ "filePath",
15769
+ "path",
15770
+ "pattern",
15771
+ "query",
15772
+ "symbol",
15773
+ "module",
15774
+ "action"
15775
+ ];
15776
+ function record2(value) {
15777
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
15778
+ }
15779
+ function short(value, max = 512) {
15780
+ if (typeof value !== "string")
15781
+ return;
15782
+ return value.length <= max || estimateTokens(value) <= PRIMER_SEED_CAP_TOKENS ? value : value.slice(0, max);
15783
+ }
15784
+ function projectEntry(value) {
15785
+ const entry = record2(value);
15786
+ if (entry.type !== "message")
15787
+ return { type: entry.type };
15788
+ const message = record2(entry.message);
15789
+ let content = [];
15790
+ if (message.role === "user" && typeof message.content === "string") {
15791
+ content = short(message.content, RAW_SUMMARY_TEXT_MAX_CHARS);
15792
+ } else if (message.role !== "toolResult" && Array.isArray(message.content)) {
15793
+ if (message.content.length > MAX_PARTS)
15794
+ throw new Error("Pi primer entry has too many parts");
15795
+ content = message.content.flatMap((part) => {
15796
+ const p = record2(part);
15797
+ if (p.type === "text")
15798
+ return [
15799
+ { type: "text", text: short(p.text, RAW_SUMMARY_TEXT_MAX_CHARS) }
15800
+ ];
15801
+ if (p.type !== "toolCall")
15802
+ return [];
15803
+ const args = record2(p.arguments);
15804
+ return [
15805
+ {
15806
+ type: "toolCall",
15807
+ id: short(p.id),
15808
+ name: short(p.name),
15809
+ arguments: Object.fromEntries(TOOL_KEYS.map((key) => [key, short(args[key])]))
15810
+ }
15811
+ ];
15812
+ });
15813
+ }
15814
+ return {
15815
+ type: entry.type,
15816
+ id: entry.id,
15817
+ timestamp: entry.timestamp,
15818
+ message: {
15819
+ role: message.role,
15820
+ content,
15821
+ toolCallId: short(message.toolCallId),
15822
+ toolName: short(message.toolName)
15684
15823
  }
15685
- resolved ??= loadDefaultPiSessionApi();
15686
- return resolved;
15687
15824
  };
15825
+ }
15826
+ function* summaryEntries(path) {
15827
+ let pendingResults = 0;
15828
+ for (const line of lines(path)) {
15829
+ let parsed;
15830
+ try {
15831
+ parsed = JSON.parse(line);
15832
+ } catch {
15833
+ continue;
15834
+ }
15835
+ const entry = projectEntry(parsed);
15836
+ const e = record2(entry);
15837
+ if (e.type === "message") {
15838
+ const m = record2(e.message);
15839
+ if (m.role === "toolResult" && m.toolCallId) {
15840
+ if (++pendingResults > MAX_PARTS)
15841
+ throw new Error("Pi primer has too many consecutive tool results");
15842
+ } else if (m.role === "user" || m.role === "assistant")
15843
+ pendingResults = 0;
15844
+ }
15845
+ yield entry;
15846
+ }
15847
+ }
15848
+ function createPiPrimerRawProviderFactory(deps = {}) {
15688
15849
  return async (sessionId) => {
15689
15850
  try {
15690
- const { listSessions, loadEntriesFromFile } = await resolveDeps();
15691
- const sessions = await listSessions(deps.sessionDir);
15692
- const match = sessions.find((s) => s && typeof s === "object" && s.id === sessionId && typeof s.path === "string");
15693
- if (!match || typeof match.path !== "string")
15694
- return null;
15695
- const entries = await loadEntriesFromFile(match.path);
15696
- const messages = convertEntriesToRawMessages(entries);
15697
- if (messages.length === 0)
15851
+ let directory = deps.sessionDir;
15852
+ if (!directory) {
15853
+ const mod = await resolvePiCodingAgentModule();
15854
+ if (!mod.getAgentDir)
15855
+ return null;
15856
+ directory = join8(mod.getAgentDir(), "sessions");
15857
+ }
15858
+ const path = findSession(directory, !deps.sessionDir, sessionId);
15859
+ if (!path)
15698
15860
  return null;
15699
15861
  return {
15700
15862
  readMessages() {
15701
- return messages;
15863
+ throw new Error("Pi primer history requires bounded pages");
15702
15864
  },
15703
- getMessageCount() {
15704
- return messages.length;
15865
+ readMessagePage(after, limit, watermark) {
15866
+ return convertEntriesToRawMessagePage(summaryEntries(path), after, Math.min(50, limit), watermark);
15705
15867
  }
15706
15868
  };
15707
15869
  } catch {
@@ -15711,17 +15873,22 @@ function createPiPrimerRawProviderFactory(deps = {}) {
15711
15873
  }
15712
15874
 
15713
15875
  // src/dreamer/retrospective-raw-provider-pi.ts
15714
- import { resolve as resolve2 } from "node:path";
15876
+ import { join as join9, resolve as resolve2 } from "node:path";
15715
15877
  class PiRetrospectiveRawProvider {
15716
15878
  deps;
15717
15879
  sessionPathById = new Map;
15718
- resolvedDefaultDeps = null;
15719
15880
  constructor(deps) {
15720
15881
  this.deps = deps;
15721
15882
  }
15722
15883
  async listProjectSessions(_projectIdentity) {
15723
- const deps = await this.resolveDeps();
15724
- const sessions = await deps.listSessions(this.deps.sessionDir);
15884
+ let directory = this.deps.sessionDir;
15885
+ if (!directory && !this.deps.listSessions) {
15886
+ const mod = await resolvePiCodingAgentModule();
15887
+ if (!mod.getAgentDir)
15888
+ return [];
15889
+ directory = join9(mod.getAgentDir(), "sessions");
15890
+ }
15891
+ const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? sessionHeaders(directory, !this.deps.sessionDir) : [];
15725
15892
  const projectCwd = resolve2(this.deps.projectCwd);
15726
15893
  const result = [];
15727
15894
  this.sessionPathById.clear();
@@ -15743,51 +15910,50 @@ class PiRetrospectiveRawProvider {
15743
15910
  return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
15744
15911
  }
15745
15912
  async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
15746
- const all = await this.loadUserEntries(sessionId);
15747
15913
  const limit = Math.max(1, Math.floor(capPerSession));
15748
- const eligible = all.filter((entry) => entry.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
15914
+ const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false);
15749
15915
  return {
15750
- messages: eligible.slice(0, limit),
15751
- truncated: eligible.length > limit
15916
+ messages: messages.slice(0, limit),
15917
+ truncated: messages.length > limit
15752
15918
  };
15753
15919
  }
15754
15920
  async readOldestMessageTimesSince(sessionIds, sinceMs) {
15755
15921
  const out = new Map;
15756
15922
  for (const sessionId of sessionIds) {
15757
- const oldest = (await this.loadUserEntries(sessionId)).filter((message) => message.ts > sinceMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal)[0];
15923
+ const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false);
15758
15924
  if (oldest)
15759
15925
  out.set(sessionId, oldest.ts);
15760
15926
  }
15761
15927
  return out;
15762
15928
  }
15763
15929
  async readUserMessagesBefore(sessionId, beforeMs, count) {
15764
- const all = await this.loadUserEntries(sessionId);
15765
- return all.filter((entry) => entry.ts <= beforeMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal).slice(-Math.max(1, Math.floor(count)));
15930
+ return this.selectUserEntries(sessionId, (ts) => ts <= beforeMs, Math.max(1, Math.floor(count)), true);
15766
15931
  }
15767
- async loadUserEntries(sessionId) {
15932
+ async selectUserEntries(sessionId, eligible, limit, newest) {
15768
15933
  const filePath = this.sessionPathById.get(sessionId);
15769
15934
  if (!filePath)
15770
15935
  return [];
15771
- const deps = await this.resolveDeps();
15772
- let entries;
15936
+ const kept = [];
15773
15937
  try {
15774
- entries = await deps.loadEntriesFromFile(filePath);
15938
+ const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : sessionEntries(filePath);
15939
+ let ordinal = 0;
15940
+ for (const entry of entries) {
15941
+ const row = normalizePiUserEntry(entry, sessionId, ++ordinal);
15942
+ if (!row || !eligible(row.ts))
15943
+ continue;
15944
+ kept.push(row);
15945
+ kept.sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
15946
+ if (kept.length > limit) {
15947
+ if (newest)
15948
+ kept.shift();
15949
+ else
15950
+ kept.pop();
15951
+ }
15952
+ }
15775
15953
  } catch {
15776
15954
  return [];
15777
15955
  }
15778
- if (!Array.isArray(entries))
15779
- return [];
15780
- return entries.map((entry, index) => normalizePiUserEntry(entry, sessionId, index + 1)).filter((entry) => entry !== null);
15781
- }
15782
- async resolveDeps() {
15783
- if (this.deps.listSessions && this.deps.loadEntriesFromFile) {
15784
- return {
15785
- listSessions: this.deps.listSessions,
15786
- loadEntriesFromFile: this.deps.loadEntriesFromFile
15787
- };
15788
- }
15789
- this.resolvedDefaultDeps ??= loadDefaultPiSessionApi();
15790
- return this.resolvedDefaultDeps;
15956
+ return kept;
15791
15957
  }
15792
15958
  }
15793
15959
  function normalizePiUserEntry(entry, sessionId, ordinal) {
@@ -15827,7 +15993,7 @@ function extractPiTextContent(content) {
15827
15993
  // src/dreamer/index.ts
15828
15994
  var warnedUnknownModels = new Set;
15829
15995
  var modelAvailability = new WeakMap;
15830
- function validatePiDreamerModels(tasks, registry) {
15996
+ function validatePiDreamerModels(tasks, registry, harness = "pi") {
15831
15997
  let resolved = modelAvailability.get(registry);
15832
15998
  if (!resolved) {
15833
15999
  resolved = new Map;
@@ -15837,6 +16003,8 @@ function validatePiDreamerModels(tasks, registry) {
15837
16003
  const entries = [task.model, ...task.fallbackModels ?? []].filter((entry) => entry !== undefined);
15838
16004
  const valid = entries.filter((entry) => {
15839
16005
  const model = typeof entry === "string" ? entry : entry.model;
16006
+ if (harness === "omp" && model.startsWith("@"))
16007
+ return true;
15840
16008
  let available = resolved.get(model);
15841
16009
  if (available === undefined) {
15842
16010
  const separator = model.indexOf("/");
@@ -15888,7 +16056,7 @@ var piSubagentRunnerFactory = () => new PiSubagentRunner;
15888
16056
  var startDreamScheduleTimerFn = startDreamScheduleTimer;
15889
16057
  function registerPiDreamerProject(opts) {
15890
16058
  const directory = resolve3(opts.projectDir);
15891
- if (opts.config.disable === true || directory === parse(directory).root || directory === homedir2()) {
16059
+ if (opts.config.disable === true || !isUsableProjectIdentity(opts.projectIdentity) || directory === parse(directory).root || directory === homedir2()) {
15892
16060
  return;
15893
16061
  }
15894
16062
  const existing = registeredProjects2.get(opts.projectIdentity);
@@ -15911,7 +16079,7 @@ function registerPiDreamerProject(opts) {
15911
16079
  const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
15912
16080
  const current = registeredProjects2.get(opts.projectIdentity);
15913
16081
  return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
15914
- });
16082
+ }, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens);
15915
16083
  let cleanup;
15916
16084
  let cancelled = false;
15917
16085
  const modelRegistry = opts.modelRegistry;
@@ -15921,7 +16089,7 @@ function registerPiDreamerProject(opts) {
15921
16089
  harness: opts.harness,
15922
16090
  client,
15923
16091
  dreamerConfig: opts.config,
15924
- validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry) : undefined,
16092
+ validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry, opts.harness) : undefined,
15925
16093
  sampleDreamRun: opts.sampleDreamRun,
15926
16094
  language: opts.language,
15927
16095
  gitCommitIndexing: opts.gitCommitIndexing,
@@ -15949,11 +16117,11 @@ function registerPiDreamerProject(opts) {
15949
16117
  const sampled = manualOpts.sampleDreamRun?.();
15950
16118
  const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
15951
16119
  const mural = sampled?.mural ?? manualOpts.mural;
15952
- const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir);
16120
+ const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens);
15953
16121
  const manualRun = runManualDream({
15954
16122
  db: manualOpts.db,
15955
16123
  projectIdentity: manualOpts.projectIdentity,
15956
- tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
16124
+ tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry, manualOpts.harness) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
15957
16125
  executor: createDreamTaskExecutor({
15958
16126
  client: manualClient,
15959
16127
  sessionDirectory: manualOpts.projectDir,
@@ -16029,7 +16197,7 @@ async function awaitInFlightDreamers(registrationOwner) {
16029
16197
  return;
16030
16198
  await Promise.allSettled(runs);
16031
16199
  }
16032
- function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true) {
16200
+ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens) {
16033
16201
  const runner = piSubagentRunnerFactory();
16034
16202
  const assertRegistrationOwnerActive = () => {
16035
16203
  if (!isRegistrationOwnerActive()) {
@@ -16070,6 +16238,7 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
16070
16238
  cwd: dreamSession.directory,
16071
16239
  signal: args.signal ?? undefined,
16072
16240
  thinkingLevel: extractBodyVariant(args),
16241
+ maxOutputTokens: getMaxOutputTokens(),
16073
16242
  accountingSessionId: opts.projectIdentity,
16074
16243
  accountingSubagent: "dreamer",
16075
16244
  accountingTask: accountingTaskFromTitle(dreamSession.title)
@@ -16724,6 +16893,8 @@ ${statusText}`,
16724
16893
  });
16725
16894
  }
16726
16895
  function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
16896
+ if (!isUsableProjectIdentity(projectIdentity))
16897
+ return;
16727
16898
  if (autoEmbedAttemptedBySession.has(sessionId))
16728
16899
  return;
16729
16900
  if (embedPauseBySession.has(sessionId))
@@ -16872,6 +17043,135 @@ function commitPiCompactionModeRecord(db, sessionId, record) {
16872
17043
  // src/context-handler.ts
16873
17044
  import * as crypto3 from "node:crypto";
16874
17045
 
17046
+ // ../plugin/src/hooks/magic-context/event-resolvers.ts
17047
+ var DEFAULT_CONTEXT_LIMIT = 200000;
17048
+ var clampWarnSeen = new Set;
17049
+ function isFinitePositive(v) {
17050
+ return typeof v === "number" && Number.isFinite(v) && v > 0;
17051
+ }
17052
+ function* modelKeyLookupOrder(modelKey) {
17053
+ const slash = modelKey.indexOf("/");
17054
+ const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
17055
+ let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
17056
+ while (modelId.length > 0) {
17057
+ for (const providerRef of providerRefs) {
17058
+ const providerSlash = providerRef.indexOf("/");
17059
+ yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
17060
+ }
17061
+ yield modelId;
17062
+ const lastDash = modelId.lastIndexOf("-");
17063
+ if (lastDash <= 0)
17064
+ break;
17065
+ modelId = modelId.slice(0, lastDash);
17066
+ }
17067
+ }
17068
+ function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
17069
+ if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
17070
+ const contextLimit = options.contextLimit;
17071
+ const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
17072
+ if (tokenMatch && isFinitePositive(tokenMatch.value)) {
17073
+ const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
17074
+ const effectiveTokens = Math.min(tokenMatch.value, cap);
17075
+ if (effectiveTokens < tokenMatch.value) {
17076
+ const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
17077
+ if (!clampWarnSeen.has(dedupeKey)) {
17078
+ clampWarnSeen.add(dedupeKey);
17079
+ const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
17080
+ if (options.sessionId) {
17081
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17082
+ } else {
17083
+ log(`[magic-context] WARN: ${msg}`);
17084
+ }
17085
+ }
17086
+ }
17087
+ const percentage = effectiveTokens / contextLimit * 100;
17088
+ const detail = {
17089
+ percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
17090
+ mode: "tokens",
17091
+ absoluteTokens: Math.floor(effectiveTokens),
17092
+ matchedKey: tokenMatch.matchedKey
17093
+ };
17094
+ if (effectiveTokens < tokenMatch.value) {
17095
+ detail.clamped = true;
17096
+ detail.configuredValue = tokenMatch.value;
17097
+ }
17098
+ return detail;
17099
+ }
17100
+ }
17101
+ let resolved;
17102
+ let matchedKey;
17103
+ if (typeof config === "number") {
17104
+ resolved = config;
17105
+ } else if (modelKey) {
17106
+ let matched;
17107
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17108
+ if (typeof config[candidate] === "number") {
17109
+ matched = config[candidate];
17110
+ matchedKey = candidate;
17111
+ break;
17112
+ }
17113
+ }
17114
+ if (matched === undefined && typeof config.default === "number") {
17115
+ resolved = config.default;
17116
+ matchedKey = "default";
17117
+ } else {
17118
+ resolved = matched ?? fallback;
17119
+ }
17120
+ } else if (typeof config.default === "number") {
17121
+ resolved = config.default;
17122
+ matchedKey = "default";
17123
+ } else {
17124
+ resolved = fallback;
17125
+ }
17126
+ if (!Number.isFinite(resolved) || resolved < 0) {
17127
+ resolved = fallback;
17128
+ }
17129
+ const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
17130
+ const percentageClamped = cappedPercentage < resolved;
17131
+ if (percentageClamped) {
17132
+ const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
17133
+ if (!clampWarnSeen.has(dedupeKey)) {
17134
+ clampWarnSeen.add(dedupeKey);
17135
+ const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
17136
+ if (options?.sessionId) {
17137
+ sessionLog(options.sessionId, `WARN: ${msg}`);
17138
+ } else {
17139
+ log(`[magic-context] WARN: ${msg}`);
17140
+ }
17141
+ }
17142
+ }
17143
+ const detail = {
17144
+ percentage: cappedPercentage,
17145
+ mode: "percentage",
17146
+ matchedKey
17147
+ };
17148
+ if (percentageClamped) {
17149
+ detail.clamped = true;
17150
+ detail.configuredValue = resolved;
17151
+ }
17152
+ return detail;
17153
+ }
17154
+ function resolveExecuteThreshold(config, modelKey, fallback, options) {
17155
+ return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
17156
+ }
17157
+ function resolveTokensMatchWithKey(tokensConfig, modelKey) {
17158
+ if (!tokensConfig) {
17159
+ return;
17160
+ }
17161
+ if (modelKey) {
17162
+ for (const candidate of modelKeyLookupOrder(modelKey)) {
17163
+ const value = tokensConfig[candidate];
17164
+ if (typeof value === "number") {
17165
+ return { value, matchedKey: candidate };
17166
+ }
17167
+ }
17168
+ }
17169
+ if (typeof tokensConfig.default === "number") {
17170
+ return { value: tokensConfig.default, matchedKey: "default" };
17171
+ }
17172
+ return;
17173
+ }
17174
+
16875
17175
  // ../plugin/src/features/magic-context/scheduler.ts
16876
17176
  var TTL_PATTERN = /^(\d+)([smh])$/;
16877
17177
  var NUMERIC_PATTERN = /^\d+$/;
@@ -20461,6 +20761,8 @@ async function runAutoSearchHintForPi(args) {
20461
20761
  }
20462
20762
  if (found.index !== messages.length - 1)
20463
20763
  return messages;
20764
+ if (!isUsableProjectIdentity(options.projectPath))
20765
+ return messages;
20464
20766
  await args.ensureProjectRegistered?.();
20465
20767
  const writeNoHintAndReconcile = (reason) => {
20466
20768
  const outcome = appendAutoSearchHintDecision(db, sessionId, {
@@ -25285,9 +25587,7 @@ ${decayed}
25285
25587
  if (memoryBlock)
25286
25588
  sections.push(memoryBlock);
25287
25589
  if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
25288
- sections.push(`<memory-mural>
25289
- The project memory mural image follows.
25290
- </memory-mural>`);
25590
+ sections.push(MEMORY_MURAL_BLOCK);
25291
25591
  }
25292
25592
  return sections.join(`
25293
25593
 
@@ -28601,144 +28901,6 @@ function stripPiProcessedImages(args) {
28601
28901
 
28602
28902
  // src/system-prompt.ts
28603
28903
  import { createHash as createHash13 } from "node:crypto";
28604
-
28605
- // ../plugin/src/agents/magic-context-prompt.ts
28606
- var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
28607
-
28608
- Most AI sessions are disposable: one session per task, discarded when it's done — like hiring a developer for a single bug fix and letting them go the moment they finish. Magic Context changes this completely. This session is a durable working relationship: you carry the full history and accumulated knowledge of this project, and you continue across many tasks, bugs, and features — with memory that persists across restarts. This session may continue for weeks, months, or even years.
28609
-
28610
- Because of this:
28611
- - **Your context is continuously managed and effectively unbounded.** As the session grows, earlier turns are automatically compacted into \`<session-history>\` and stay fully retrievable via \`ctx_search\`/\`ctx_expand\`. You will not hit a context wall and you will not lose continuity mid-task.
28612
- - **High context usage is normal and fully handled** — never a reason to wrap up, cut scope, rush, or defer work. Keep going at full depth.
28613
- - **Finishing a task does not end the session.** Continue naturally into the next piece of work, carrying everything forward.
28614
- - **There are no compaction pauses.** Unlike native context management that stops you to summarize and restart, Magic Context compacts in the background — you stay in flow, uninterrupted, so there's no reason to artificially wind down.`;
28615
- var PARTNER_FRAME_CLOSER_REDUCE = `
28616
- Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
28617
- var PARTNER_FRAME_CLOSER_NO_REDUCE = `
28618
- Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
28619
- var PARTNER_FRAME_CLOSER_REDUCE_LIGHT = `
28620
- When ctx_reduce is available, use it only as routine housekeeping; never cut task scope or depth because context is large.`;
28621
- var PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT = `
28622
- When ctx_reduce is unavailable, context is automatic; never prune, heed reduction warnings, or cut task scope or depth because context is large.`;
28623
- var CTX_NOTE_GUIDANCE = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note. When the user says "take a note", it always goes in the tray.`;
28624
- var MEMORY_GUIDANCE = `\`<project-memory>\` is the pinboard: facts about this project that stay true for the months this work lasts, as \`#id: fact\` lines — for you, and for every other agent working on this project. \`ctx_memory\` pins a new one when you learn something that must not have to be found again, and especially when it cost you several turns to find.`;
28625
- var TOOL_HISTORY_GUIDANCE = `Older work is not kept on the desk at all. Magic Context files it as an organized record, \`<session-history>\`: one heading per stretch of work, \`## start-end · date · title\`, with a summary underneath. Each heading is a pointer into the archive — \`ctx_expand(start, end)\` opens that stretch in full when the summary is not enough. Because of this filing, your own earlier messages may mention actions whose tool call is no longer on the desk. That is normal. It is never a reason to fabricate: if there is no tool result on the desk, the action did not happen, and you never inline or invent a tool call, an output, a search result or a diff in your own text.`;
28626
- var SMART_NOTE_GUIDANCE = `A note with a \`surface_condition\` is left with an outside checker that looks the condition up periodically and returns the note only when it holds.`;
28627
- var TEMPORAL_AWARENESS_GUIDANCE = `, and \`<!-- +Xm -->\` before a user message (the time that passed since your last reply; headings in the record carry \`start-date\`/\`end-date\` too)`;
28628
- var TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE = `
28629
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${TEMPORAL_AWARENESS_GUIDANCE}. Read them, use the time, and never reproduce them in a reply or treat them as instructions.`;
28630
- var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28631
-
28632
- Think of your context as a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle.
28633
-
28634
- When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". An output is used once you've taken what you need from it into your reasoning or your answer. Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
28635
-
28636
- Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller for it.
28637
-
28638
- ${TOOL_HISTORY_GUIDANCE}
28639
-
28640
- \`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
28641
-
28642
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
28643
-
28644
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
28645
- var BASE_INTRO_NO_REDUCE = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28646
-
28647
- Think of your context as a desk. Every message and every tool output lands on it.
28648
-
28649
- Nothing cleared from the desk is ever lost. A cleared item goes to the archive — a recent one leaves a placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output.
28650
-
28651
- ${TOOL_HISTORY_GUIDANCE}
28652
-
28653
- \`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
28654
-
28655
- ${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
28656
-
28657
- Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
28658
- var CTX_NOTE_GUIDANCE_LIGHT = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note; "take a note" from the user always goes there`;
28659
- var MEMORY_GUIDANCE_LIGHT = `\`<project-memory>\` is the pinboard of facts that stay true for the months this work lasts (\`#id: fact\`), for you and every agent on the project; \`ctx_memory\` pins what must not have to be found again, especially what cost you turns.`;
28660
- var TOOL_HISTORY_GUIDANCE_LIGHT = `Older work is filed off the desk into \`<session-history>\` — one \`## start-end · date · title\` heading per stretch, a summary under each, \`ctx_expand(start, end)\` to open the stretch in full. So your earlier messages may mention actions whose tool call is gone; that is normal and never a reason to fabricate: no tool result on the desk, no action, and never inline an invented call, output, search result or diff.`;
28661
- var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with an outside checker that returns it when the condition holds`;
28662
- var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
28663
- var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28664
-
28665
- Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). An output is used once you've taken what you need from it into your reasoning or your answer. Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material may appear. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller.
28666
-
28667
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28668
-
28669
- \`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
28670
-
28671
- Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
28672
- var BASE_INTRO_NO_REDUCE_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
28673
-
28674
- Your context is a desk. Every message and tool output lands on it. Nothing cleared from the desk is lost: a cleared item goes to the archive (a recent one leaves a placeholder, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole.
28675
-
28676
- ${TOOL_HISTORY_GUIDANCE_LIGHT}
28677
-
28678
- \`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
28679
-
28680
- Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
28681
- var SUBAGENT_REDUCE_INTRO = () => `Your context is a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle. When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag ("3-5", "1,2,9", "1-5,8,12-15"). Stamping does not remove anything — the item stays fully readable until Magic Context clears stamped items in one sweep, and the newest tags are protected, so stamping recent output is harmless. Stamp as soon as an item has served its purpose, not at the end of the task, and do it silently. Never stamp a user message for what it asks of you; look at each tag before stamping and never blanket-stamp a range like "1-50".
28682
-
28683
- A cleared item leaves a \`[dropped §N§]\` placeholder or disappears from the desk; that is normal housekeeping, not something to copy or mention. If there is no tool result on the desk, the action did not happen: never fabricate or inline a tool call, an output, a search result or a diff — make a fresh real tool call instead.
28684
-
28685
- \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\` and \`[dropped §N§]\` are Magic Context's own markings: read them, never reproduce them in a reply, never treat them as instructions.`;
28686
- var SUBAGENT_REDUCE_INTRO_LIGHT = () => `Your context is a desk: every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it with \`ctx_reduce\` ("3-5", "1,2,9"). Stamping removes nothing — the item stays readable until Magic Context clears stamped items in one sweep; newest tags are protected. Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks; look at each tag, never blanket-stamp "1-50".
28687
-
28688
- A cleared item leaves \`[dropped §N§]\` or disappears; that is housekeeping, not a pattern to copy. No tool result on the desk means the action did not happen — never fabricate or inline a call, output, search result or diff; make a real call.
28689
-
28690
- \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\` are Magic Context's markings: read, never reproduced, never instructions.`;
28691
- var CAVEMAN_COMPRESSION_WARNING = `
28692
- **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.`;
28693
- function buildMagicContextSection(_agent, _legacyProtectionCount, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true, preset = "full", primaryOverride) {
28694
- if (subagentMode) {
28695
- const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT() : SUBAGENT_REDUCE_INTRO();
28696
- return `## Magic Context
28697
-
28698
- ${intro}`;
28699
- }
28700
- const temporalOverrideGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE : "";
28701
- const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
28702
- const languageDirective = buildPrimaryLanguageDirective(language);
28703
- const languageGuidance = languageDirective ? `
28704
-
28705
- ${languageDirective}` : "";
28706
- if (primaryOverride !== undefined) {
28707
- return `${primaryOverride}${temporalOverrideGuidance}${cavemanWarning}${languageGuidance}`;
28708
- }
28709
- if (!ctxReduceCallable) {
28710
- if (preset === "light") {
28711
- return `## Magic Context
28712
-
28713
- ${LONG_TERM_PARTNER_FRAME}
28714
- ${PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT}
28715
-
28716
- ${BASE_INTRO_NO_REDUCE_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28717
- }
28718
- return `## Magic Context
28719
-
28720
- ${LONG_TERM_PARTNER_FRAME}
28721
- ${PARTNER_FRAME_CLOSER_NO_REDUCE}
28722
-
28723
- ${BASE_INTRO_NO_REDUCE(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28724
- }
28725
- if (preset === "light") {
28726
- return `## Magic Context
28727
-
28728
- ${LONG_TERM_PARTNER_FRAME}
28729
- ${PARTNER_FRAME_CLOSER_REDUCE_LIGHT}
28730
-
28731
- ${BASE_INTRO_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28732
- }
28733
- return `## Magic Context
28734
-
28735
- ${LONG_TERM_PARTNER_FRAME}
28736
- ${PARTNER_FRAME_CLOSER_REDUCE}
28737
-
28738
- ${BASE_INTRO(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
28739
- }
28740
-
28741
- // src/system-prompt.ts
28742
28904
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
28743
28905
  var stickyDateBySession = new Map;
28744
28906
  function buildMagicContextBlock(opts) {
@@ -29681,12 +29843,14 @@ var MAX_TRACKED_SESSIONS = 100;
29681
29843
  function trackSessionForProject(projectIdentity, sessionId) {
29682
29844
  activeContextHandlerSessions.delete(sessionId);
29683
29845
  activeContextHandlerSessions.add(sessionId);
29684
- let sessions = sessionsByProject.get(projectIdentity);
29685
- if (!sessions) {
29686
- sessions = new Set;
29687
- sessionsByProject.set(projectIdentity, sessions);
29846
+ if (isUsableProjectIdentity(projectIdentity)) {
29847
+ let sessions = sessionsByProject.get(projectIdentity);
29848
+ if (!sessions) {
29849
+ sessions = new Set;
29850
+ sessionsByProject.set(projectIdentity, sessions);
29851
+ }
29852
+ sessions.add(sessionId);
29688
29853
  }
29689
- sessions.add(sessionId);
29690
29854
  while (activeContextHandlerSessions.size > MAX_TRACKED_SESSIONS) {
29691
29855
  const oldest = activeContextHandlerSessions.values().next().value;
29692
29856
  if (oldest === undefined || oldest === sessionId)
@@ -30444,6 +30608,8 @@ function registerPiContextHandler(pi, baseOptions) {
30444
30608
  }
30445
30609
  const previousModelKey = liveModelBySession.get(sessionId);
30446
30610
  const currentModelKey = resolvePiContextModelKey(ctx);
30611
+ if (options.cacheTtlConfig !== undefined)
30612
+ resolveSessionCacheTtl(options.db, sessionId, options.cacheTtlConfig, currentModelKey);
30447
30613
  const modelChanged = previousModelKey !== undefined && currentModelKey !== undefined && piModelRefToCanonical(previousModelKey) !== piModelRefToCanonical(currentModelKey);
30448
30614
  if (currentModelKey !== undefined) {
30449
30615
  liveModelBySession.set(sessionId, currentModelKey);
@@ -30837,7 +31003,7 @@ function registerPiContextHandler(pi, baseOptions) {
30837
31003
  const tTodoCapture = performance.now();
30838
31004
  try {
30839
31005
  const sessionMetaForTodo = sessionMeta;
30840
- if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
31006
+ if (options.todowriteEnabled === true && !options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
30841
31007
  const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
30842
31008
  outputMessages = injectSyntheticTodowriteForPi({
30843
31009
  db: options.db,
@@ -34287,6 +34453,8 @@ ${c.content}
34287
34453
 
34288
34454
  // ../plugin/src/shared/cache-ttl-display.ts
34289
34455
  function resolveCacheTtlDisplay(args) {
34456
+ if (args.frozen)
34457
+ return args.frozen;
34290
34458
  if (args.sessionModelKey && (!args.modelKey || args.sessionModelKey === args.modelKey) || !args.modelKey && !args.sessionModelKey && args.sessionValue !== "5m") {
34291
34459
  return {
34292
34460
  value: args.sessionValue || "5m",
@@ -34294,24 +34462,14 @@ function resolveCacheTtlDisplay(args) {
34294
34462
  modelKey: args.modelKey ?? args.sessionModelKey ?? undefined
34295
34463
  };
34296
34464
  }
34297
- if (typeof args.configured === "string") {
34298
- return {
34299
- value: args.configured,
34300
- source: args.configuredExplicitly ? "config" : "default",
34301
- modelKey: args.modelKey
34302
- };
34303
- }
34304
- const matched = resolveModelConfigValue(args.configured, args.modelKey);
34305
- if (matched) {
34306
- return { value: matched.value, source: "config", modelKey: args.modelKey };
34307
- }
34308
- return {
34309
- value: args.configured.default ?? "5m",
34310
- source: "default",
34311
- modelKey: args.modelKey
34312
- };
34465
+ const resolved = resolveModelCacheTtl(args.configured, args.modelKey);
34466
+ if (resolved.source === "default" && typeof args.configured === "string" && args.configuredExplicitly)
34467
+ return { ...resolved, source: "config" };
34468
+ return resolved;
34313
34469
  }
34314
34470
  function formatCacheTtlDisplay(display) {
34471
+ if (display.source === "OpenAI GPT-5.6+ default")
34472
+ return `Cache TTL: ${display.value} (${display.source})`;
34315
34473
  if (display.source === "session")
34316
34474
  return `Cache TTL: ${display.value} (session)`;
34317
34475
  if (display.source === "config") {
@@ -34867,7 +35025,7 @@ function buildStatusView(source, options) {
34867
35025
  // package.json
34868
35026
  var package_default = {
34869
35027
  name: "@cortexkit/pi-magic-context",
34870
- version: "0.44.0",
35028
+ version: "0.44.1",
34871
35029
  type: "module",
34872
35030
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
34873
35031
  main: "dist/index.js",
@@ -35279,6 +35437,7 @@ ${safeStringify(tool.parameters)}`);
35279
35437
  sessionId
35280
35438
  });
35281
35439
  const cacheTtlDisplay = resolveCacheTtlDisplay({
35440
+ frozen: readSessionCacheTtl(deps.db, sessionId),
35282
35441
  configured: deps.cacheTtlConfig ?? "5m",
35283
35442
  configuredExplicitly: deps.cacheTtlConfigured === true,
35284
35443
  modelKey,
@@ -36188,7 +36347,7 @@ function persistPiMessageEndModelMeta(args) {
36188
36347
  const modelKey = canonicalPiModelKey(msg.provider, msg.model);
36189
36348
  if (!recordPiLiveModel(args.sessionId, modelKey, msg.timestamp))
36190
36349
  return;
36191
- const cacheTtl = resolveCacheTtl(args.cacheTtlConfig, modelKey);
36350
+ const cacheTtl = resolveSessionCacheTtl(args.db, args.sessionId, args.cacheTtlConfig, modelKey).value;
36192
36351
  const currentMeta = getOrCreateSessionMeta(args.db, args.sessionId);
36193
36352
  updateSessionMeta(args.db, args.sessionId, {
36194
36353
  ...currentMeta.cacheTtl === cacheTtl ? {} : { cacheTtl },
@@ -36254,6 +36413,13 @@ function info(message, data) {
36254
36413
  function warn(message, data) {
36255
36414
  log(`${PREFIX2} WARN ${message}`, data);
36256
36415
  }
36416
+ var loggedNoProjectIdentityDirs = new Set;
36417
+ function logNoProjectIdentityOnce(directory) {
36418
+ if (loggedNoProjectIdentityDirs.has(directory))
36419
+ return;
36420
+ loggedNoProjectIdentityDirs.add(directory);
36421
+ info(`no project identity for ${directory}: it is not treated as a project, so memory, search, ` + "embeddings and the dreamer stay off here. Start Pi inside a project folder, or set " + "`allow_home_project: true` in the user-level magic-context.jsonc to give home sessions their own project.");
36422
+ }
36257
36423
  var migratedConfigDirs = new Set;
36258
36424
  var loggedPiConfigDirs = new Set;
36259
36425
  function ensureConfigLocationsMigrated(dir) {
@@ -36294,6 +36460,14 @@ function claimProtectedTagsDeprecationNoticeOnce() {
36294
36460
  function resetClaimedProtectedTagsDeprecationNoticeForTesting() {
36295
36461
  protectedTagsDeprecationClaimed = false;
36296
36462
  }
36463
+ function registerConfiguredTodoLifecycle(pi, options) {
36464
+ if (!options.configured)
36465
+ return;
36466
+ registerTodoStateLifecycle(pi, {
36467
+ readLastTodoState: options.readLastTodoState
36468
+ });
36469
+ return options.overlay ? registerTodoOverlay(pi, { readLastTodoState: options.readLastTodoState }) : undefined;
36470
+ }
36297
36471
  var __test = {
36298
36472
  logPiConfigLoad,
36299
36473
  resetLoggedPiConfigDirs() {
@@ -36475,7 +36649,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegi
36475
36649
  model: resolved.primary,
36476
36650
  fallbackModels: resolved.fallbacks
36477
36651
  }
36478
- ], modelRegistry)[0] : undefined;
36652
+ ], modelRegistry, harness)[0] : undefined;
36479
36653
  const primary = validated ? typeof validated.model === "string" ? { model: validated.model } : validated.model : resolved.primary;
36480
36654
  const model = primary?.model;
36481
36655
  if (!model)
@@ -36547,7 +36721,7 @@ async function src_default(pi) {
36547
36721
  mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
36548
36722
  });
36549
36723
  const storageDir = getMagicContextStorageDir();
36550
- const dbPath = join8(storageDir, "context.db");
36724
+ const dbPath = join11(storageDir, "context.db");
36551
36725
  let openFailureCause = null;
36552
36726
  const openStorage = async () => {
36553
36727
  try {
@@ -36652,7 +36826,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36652
36826
  const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
36653
36827
  if (projectIdentity)
36654
36828
  seenDreamerProjectIdentities.add(projectIdentity);
36655
- info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
36829
+ info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity || "(none)"} | dir=${projectDir}`);
36656
36830
  const compactionOff = !isCompactionEnabled(config);
36657
36831
  setCtxReduceRegisteredGlobally(!compactionOff);
36658
36832
  if (!compactionOff) {
@@ -36687,7 +36861,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36687
36861
  return;
36688
36862
  }
36689
36863
  await ensureProjectRegisteredFromPiDirectory(projectDir, db);
36690
- info(`registered embedding config for project ${projectIdentity ?? "(no project identity; cwd is $HOME)"}`);
36864
+ if (isUsableProjectIdentity(projectIdentity)) {
36865
+ info(`registered embedding config for project ${projectIdentity}`);
36866
+ } else {
36867
+ logNoProjectIdentityOnce(projectDir);
36868
+ }
36691
36869
  const projectDepsByDir = new Map;
36692
36870
  const liveReaders = new Map;
36693
36871
  function liveReaderFor(dir, boot) {
@@ -36711,6 +36889,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36711
36889
  }
36712
36890
  const buildContextOptions = (cfg, hist, auto) => ({
36713
36891
  db: database,
36892
+ cacheTtlConfig: cfg.cache_ttl,
36714
36893
  smartDrops: cfg.smart_drops === true,
36715
36894
  protectedTokens: cfg.protected_tokens,
36716
36895
  protectedTokenTierOverrides: getProtectedTokensTierOverrides(cfg) ?? {},
@@ -36737,6 +36916,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36737
36916
  language: cfg.language,
36738
36917
  autoSearch: auto,
36739
36918
  resolveForProject: resolveContextOptionsForProject,
36919
+ todowriteEnabled: cfg.todowrite.enabled,
36740
36920
  compactionOff,
36741
36921
  allowHomeProject: cfg.allow_home_project,
36742
36922
  maybeAutoEmbedSession: (sessionId, dir, identity) => {
@@ -36814,6 +36994,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36814
36994
  function syncDreamerProjectRegistration(current, modelRegistry) {
36815
36995
  if (sessionShuttingDown)
36816
36996
  return;
36997
+ if (!isUsableProjectIdentity(current.projectIdentity)) {
36998
+ logNoProjectIdentityOnce(current.projectDir);
36999
+ return;
37000
+ }
36817
37001
  seenDreamerProjectIdentities.add(current.projectIdentity);
36818
37002
  if (!current.dreamerConfig) {
36819
37003
  unregisterPiDreamerProject({
@@ -36849,8 +37033,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36849
37033
  });
36850
37034
  }
36851
37035
  registerPiDroppedInputGuard(pi);
36852
- const todowriteEnabled = bootProjectDeps.config.todowrite.enabled !== false;
36853
- const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay !== false;
37036
+ const todowriteEnabled = bootProjectDeps.config.todowrite.enabled;
37037
+ const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay;
36854
37038
  registerMagicContextTools(pi, {
36855
37039
  db,
36856
37040
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
@@ -36896,12 +37080,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36896
37080
  });
36897
37081
  });
36898
37082
  const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
36899
- if (todowriteEnabled) {
36900
- registerTodoStateLifecycle(pi, { readLastTodoState });
36901
- }
36902
- const todoOverlay = todowriteOverlayEnabled ? registerTodoOverlay(pi, {
37083
+ const todoOverlay = registerConfiguredTodoLifecycle(pi, {
37084
+ configured: todowriteEnabled,
37085
+ overlay: bootProjectDeps.config.todowrite.overlay,
36903
37086
  readLastTodoState
36904
- }) : undefined;
37087
+ });
36905
37088
  info(todowriteOverlayEnabled ? "registered todowrite overlay" : "registered todowrite overlay: DISABLED (todowrite.enabled=false or todowrite.overlay=false)");
36906
37089
  registerPiContextHandler(pi, bootProjectDeps.contextOptions);
36907
37090
  info(bootProjectDeps.historianConfig ? `registered historian trigger (model=${bootProjectDeps.historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(bootProjectDeps.historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (configure the active harness's historian model in magic-context.jsonc)");
@@ -36954,7 +37137,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
36954
37137
  const registry = activeModelRegistry;
36955
37138
  if (!registry)
36956
37139
  return;
36957
- const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry);
37140
+ const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry, PI_HARNESS_KIND);
36958
37141
  const empty = tasks.filter((task) => task.modelChainUnavailable).map((task) => task.task);
36959
37142
  if (!resolveHistorianFromConfig(current.config, PI_HARNESS_KIND, registry) && current.historianConfig)
36960
37143
  empty.push("historian");
@@ -37441,6 +37624,7 @@ export {
37441
37624
  handlePiSessionBeforeCompact,
37442
37625
  persistPiMessageEndModelMeta,
37443
37626
  persistPiPressureFromMessageEnd,
37627
+ registerConfiguredTodoLifecycle,
37444
37628
  resetClaimedProtectedTagsDeprecationNoticeForTesting,
37445
37629
  resolveDreamerFromConfig,
37446
37630
  resolveHistorianFromConfig,