@cortexkit/magic-context 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +204 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2973,7 +2973,7 @@ function sortJson(value) {
2973
2973
  if (value && typeof value === "object") {
2974
2974
  const sorted = {};
2975
2975
  for (const key of Object.keys(value).sort()) {
2976
- if (key === "protected_tags")
2976
+ if (key === "protected_tags" || key === "clear_reasoning_age")
2977
2977
  continue;
2978
2978
  sorted[key] = sortJson(value[key]);
2979
2979
  }
@@ -3108,7 +3108,9 @@ function migrateConfigFile(opts) {
3108
3108
  let migratedContent = first.content;
3109
3109
  let strippedProtectedTags = false;
3110
3110
  try {
3111
- const stripped = removeJsoncValue(first.content, ["protected_tags"]);
3111
+ const stripped = removeJsoncValue(removeJsoncValue(first.content, ["protected_tags"]), [
3112
+ "clear_reasoning_age"
3113
+ ]);
3112
3114
  if (stripped !== first.content) {
3113
3115
  migratedContent = stripped;
3114
3116
  strippedProtectedTags = true;
@@ -3116,7 +3118,10 @@ function migrateConfigFile(opts) {
3116
3118
  } catch {}
3117
3119
  atomicWriteConfigFile(opts.targetPath, migratedContent);
3118
3120
  if (strippedProtectedTags) {
3119
- info?.(`Stripped deprecated "protected_tags" key during config location migration`);
3121
+ if (first.content.includes('"protected_tags"'))
3122
+ info?.(`Stripped deprecated "protected_tags" key during config location migration`);
3123
+ if (first.content.includes('"clear_reasoning_age"'))
3124
+ info?.(`Stripped deprecated "clear_reasoning_age" key during config location migration`);
3120
3125
  }
3121
3126
  info?.(`Migrated Magic Context ${opts.scope} config from ${first.path} to ${opts.targetPath}`);
3122
3127
  warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
@@ -8694,7 +8699,7 @@ class Parser {
8694
8699
  } else
8695
8700
  break;
8696
8701
  }
8697
- const expression = { steps, cap: 300 };
8702
+ const expression = { steps };
8698
8703
  if (this.take(".each(")) {
8699
8704
  if (relative)
8700
8705
  return this.error();
@@ -8760,9 +8765,23 @@ function compiled(source) {
8760
8765
  function oneLine(value) {
8761
8766
  return value.replace(/[\r\n\u2028\u2029]+/g, " ");
8762
8767
  }
8763
- function truncate(value, cap) {
8768
+ function truncate(value, cap, legacyPreview = false) {
8764
8769
  const chars = Array.from(oneLine(value));
8765
- return chars.length > cap ? `${chars.slice(0, cap).join("")}…` : chars.join("");
8770
+ if (cap === undefined || chars.length <= cap)
8771
+ return chars.join("");
8772
+ if (legacyPreview)
8773
+ return `${chars.slice(0, cap).join("")}…`;
8774
+ let end = 0;
8775
+ for (let i = 0;i < Math.min(cap, chars.length); i++) {
8776
+ if (!".!?。!?".includes(chars[i]))
8777
+ continue;
8778
+ let next = i + 1;
8779
+ while (next < chars.length && `"'”’)]`.includes(chars[next]))
8780
+ next++;
8781
+ if (next <= cap && ("。!?".includes(chars[i]) || next === chars.length || /[ \t]/.test(chars[next])))
8782
+ end = next;
8783
+ }
8784
+ return `${end ? `${chars.slice(0, end).join("")} ` : ""}[… ${chars.length - end} more characters]`;
8766
8785
  }
8767
8786
  function scalar(value) {
8768
8787
  if (value === undefined)
@@ -8779,10 +8798,11 @@ function compactJson(value) {
8779
8798
  function field(value, key) {
8780
8799
  return value !== null && typeof value === "object" && !Array.isArray(value) && Object.hasOwn(value, key) ? value[key] : undefined;
8781
8800
  }
8782
- function render(nodes, root) {
8801
+ function render(nodes, root, legacyPreview) {
8783
8802
  return nodes.map((node) => {
8784
8803
  if (typeof node === "string")
8785
8804
  return node;
8805
+ const cap = node.cap ?? (legacyPreview ? 300 : undefined);
8786
8806
  let value = root;
8787
8807
  let list = false;
8788
8808
  for (const step of node.steps) {
@@ -8796,19 +8816,19 @@ function render(nodes, root) {
8796
8816
  }
8797
8817
  }
8798
8818
  if (node.count)
8799
- return truncate(Array.isArray(value) ? String(value.length) : "", node.cap);
8819
+ return truncate(Array.isArray(value) ? String(value.length) : "", cap, legacyPreview);
8800
8820
  if (node.each || list || node.join !== undefined) {
8801
8821
  if (!Array.isArray(value))
8802
8822
  return "";
8803
- const elements = value.slice(0, 10).map((item) => truncate(node.each ? render(node.each, item) : scalar(item), 300));
8804
- if (value.length > 10)
8823
+ const elements = value.slice(0, legacyPreview ? 10 : undefined).map((item) => truncate(node.each ? render(node.each, item, legacyPreview) : scalar(item), legacyPreview ? 300 : undefined, legacyPreview));
8824
+ if (legacyPreview && value.length > 10)
8805
8825
  elements.push(`… +${value.length - 10} more`);
8806
- return truncate(elements.join(node.join ?? (node.each ? "; " : ", ")), node.cap);
8826
+ return truncate(elements.join(node.join ?? (node.each ? "; " : ", ")), cap, legacyPreview);
8807
8827
  }
8808
- return truncate(scalar(value), node.cap);
8828
+ return truncate(scalar(value), cap, legacyPreview);
8809
8829
  }).join("");
8810
8830
  }
8811
- function renderToolTemplate(source, input, output) {
8831
+ function renderToolTemplate(source, input, output, legacyPreview = false) {
8812
8832
  const nodes = compiled(source);
8813
8833
  if (!nodes)
8814
8834
  return null;
@@ -8823,8 +8843,8 @@ function renderToolTemplate(source, input, output) {
8823
8843
  }
8824
8844
  }
8825
8845
  const root = { input, output: structured };
8826
- const bareOutputNodes = nodes.map((node) => typeof node !== "string" && node.steps.length === 1 && ("field" in node.steps[0]) && node.steps[0].field === "output" && !node.each && !node.count && node.join === undefined ? truncate(text ?? "", node.cap) : node);
8827
- return truncate(render(bareOutputNodes, root), 1000);
8846
+ const bareOutputNodes = nodes.map((node) => typeof node !== "string" && node.steps.length === 1 && ("field" in node.steps[0]) && node.steps[0].field === "output" && !node.each && !node.count && node.join === undefined ? truncate(text ?? "", node.cap ?? (legacyPreview ? 300 : undefined), legacyPreview) : node);
8847
+ return truncate(render(bareOutputNodes, root, legacyPreview), legacyPreview ? 1000 : undefined, legacyPreview);
8828
8848
  }
8829
8849
  var templates;
8830
8850
  var init_historian_tool_template = __esm(() => {
@@ -9166,6 +9186,7 @@ var init_magic_context = __esm(() => {
9166
9186
  maxTokens: true
9167
9187
  });
9168
9188
  DreamerConfigSchema = AgentMetadataSchema.extend({
9189
+ disable: boolean2().optional().describe("Disable this agent"),
9169
9190
  runner: _enum(["broca", "host"]).optional().describe(`Which side runs the dreamer completions the Rust module routes (classify-memories) in Rust transform mode: "host" runs them on this process's carrier, "broca" routes them to the Broca module. When unset, historian.runner applies, and when that is unset too the harness decides the same way it does for the historian. User-level config only.`),
9170
9191
  opencode: DreamerOpenCodeHarnessBlockSchema.optional(),
9171
9192
  pi: DreamerPiHarnessBlockSchema.optional(),
@@ -9181,7 +9202,7 @@ var init_magic_context = __esm(() => {
9181
9202
  ctx.addIssue({ code: "custom", message: error });
9182
9203
  }),
9183
9204
  literal(false)
9184
- ])).optional().describe('Readable historian tool expansions, keyed by exact host tool name. Templates override built-in defaults; false disables an expansion. Supports ${input.path}, ${output.path}, bare ${output}, array [N], [*].field, .each("${field}"), .join("separator"), .count and final .truncate(N). Missing fields are empty; placeholders default to 300 characters, lists to 10 elements, expansions to 1000 characters. Valid in user and project config; affects historian/recomp and verbose ctx_expand only, never the wire or default ctx_expand transcript.'),
9205
+ ])).optional().describe('Readable historian tool expansions, keyed by exact host tool name. Templates override built-in defaults; false disables an expansion. Supports ${input.path}, ${output.path}, bare ${output}, array [N], [*].field, .each("${field}"), .join("separator"), .count and final .truncate(N). Missing fields are empty. Historian text and lists have no implicit caps; explicit .truncate(N) keeps complete sentences and marks omitted characters, or emits only the marker if no sentence fits. When the ctx_expand history-recovery tool is called in verbose mode, its tool-call previews retain their original built-in templates and limits (300 characters per placeholder, 10 list elements, 1000 characters per expansion). Valid in user and project config; never changes the wire or default ctx_expand transcript.'),
9185
9206
  opencode: OpenCodeHarnessBlockSchema.optional(),
9186
9207
  pi: PiHarnessBlockSchema.optional(),
9187
9208
  omp: OmpHarnessBlockSchema.optional(),
@@ -9325,7 +9346,11 @@ var init_magic_context = __esm(() => {
9325
9346
  }).catchall(number2().min(5000).max(2000000)).optional().describe("Absolute token thresholds per model. When matched, overrides execute_threshold_percentage for that model. Accepts `default` for all models or per-model keys. Values above 90% × context_limit are clamped with a warning log. Min 5_000, max 2_000_000."),
9326
9347
  protected_tokens: number2().int().min(PROTECTED_TOKENS_MIN).max(1e6).optional().describe("Positive integer token floor to protect from automatic reclaim (min: 4_000, max: 1_000_000). When omitted, the derived default is clamp(round(0.05 × usableSoft), min(16_000, round(0.08 × usableSoft)), 64_000)."),
9327
9348
  protected_tags: unknown().optional().describe("Deprecated: number of recent tags to protect. Ignored for behaviour; use protected_tokens instead.").meta({ deprecated: true }),
9328
- clear_reasoning_age: number2().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
9349
+ clear_reasoning_age: unknown().optional().describe("Deprecated and ignored. Use keep_reasoning_tokens instead.").meta({ deprecated: true }),
9350
+ keep_reasoning_tokens: union([
9351
+ number2().int().min(0).max(1e6),
9352
+ object({ default: number2().int().min(0).max(1e6).optional() }).catchall(number2().int().min(0).max(1e6))
9353
+ ]).optional().describe("Reasoning tokens to keep on rebuilding passes. Number or per-model object; exact, shorter model keys, provider/*, then default. Omitted: fixed 10,000. 0 removes all eligible historical reasoning; newest and exempt steps always stay."),
9329
9354
  history_budget_percentage: number2().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE).describe("Fraction of usable context (context_limit × execute_threshold) reserved for the session history block (default: 0.15)"),
9330
9355
  historian_timeout_ms: number2().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in both TypeScript and Rust transform modes, in milliseconds (default: 600000)"),
9331
9356
  commit_cluster_trigger: object({
@@ -9424,6 +9449,7 @@ var init_magic_context = __esm(() => {
9424
9449
  "commit_cluster_trigger.enabled",
9425
9450
  "commit_cluster_trigger.min_clusters",
9426
9451
  "memory.auto_promote",
9452
+ "dreamer.disable",
9427
9453
  "dreamer.maxTokens",
9428
9454
  "dreamer.opencode.model",
9429
9455
  "dreamer.opencode.fallback_models",
@@ -10889,6 +10915,11 @@ function warnProtectedTagsDeprecationOnce() {
10889
10915
  function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
10890
10916
  const preMigrationWarnings = [];
10891
10917
  const configWithoutRemovedAgent = stripRemovedAgentConfig(rawConfig, preMigrationWarnings);
10918
+ if (Object.hasOwn(rawConfig, "clear_reasoning_age")) {
10919
+ const warning = "clear_reasoning_age is deprecated and ignored. Magic Context now keeps reasoning up to a token budget, keep_reasoning_tokens (default 10,000). Remove the key, or set keep_reasoning_tokens to a token count.";
10920
+ preMigrationWarnings.push(warning);
10921
+ log(`[magic-context] ${warning}`);
10922
+ }
10892
10923
  if (Object.hasOwn(rawConfig, "protected_tags")) {
10893
10924
  warnProtectedTagsDeprecationOnce();
10894
10925
  preMigrationWarnings.push("protected_tags is deprecated and ignored; use protected_tokens instead.");
@@ -12820,9 +12851,12 @@ __export(exports_sqlite, {
12820
12851
  Database: () => Database,
12821
12852
  SqliteAcquisitionBusyError: () => SqliteAcquisitionBusyError,
12822
12853
  SqliteRuntimeUnavailableError: () => SqliteRuntimeUnavailableError,
12854
+ assertTransformWrite: () => assertTransformWrite,
12823
12855
  beginSqliteWriterAsync: () => beginSqliteWriterAsync,
12824
12856
  detectSqliteRuntime: () => detectSqliteRuntime,
12857
+ getSqliteDatabasePath: () => getSqliteDatabasePath,
12825
12858
  getSqliteMemoryStats: () => getSqliteMemoryStats,
12859
+ guardSqliteTransformPass: () => guardSqliteTransformPass,
12826
12860
  isTransientSqliteError: () => isTransientSqliteError,
12827
12861
  loadSqliteModule: () => loadSqliteModule,
12828
12862
  prepareCachedStatement: () => prepareCachedStatement,
@@ -12883,6 +12917,8 @@ function installTransactionRouting(db, readonly) {
12883
12917
  configurable: true,
12884
12918
  writable: true,
12885
12919
  value: (sql) => {
12920
+ if (/^\s*(?:COMMIT|END)\b/i.test(sql))
12921
+ assertTransformWrite();
12886
12922
  if (/^\s*BEGIN\s+(?:IMMEDIATE|EXCLUSIVE)(?:\s+TRANSACTION)?\s*;?\s*$/i.test(sql)) {
12887
12923
  openWriterTransactions.delete(db);
12888
12924
  if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore())
@@ -13012,6 +13048,14 @@ function buildNodeSqliteDatabaseClass(DatabaseSync) {
13012
13048
  }
13013
13049
  return NodeSqliteDatabase;
13014
13050
  }
13051
+ function getSqliteDatabasePath(db) {
13052
+ for (const connection of trackedSqliteConnections.values()) {
13053
+ if (connection.reference.deref() === db) {
13054
+ return connection.filename === ":memory:" || connection.filename === "<buffer>" ? null : connection.filename;
13055
+ }
13056
+ }
13057
+ return null;
13058
+ }
13015
13059
  function pragmaValue(db, name) {
13016
13060
  const row = db.prepare(`PRAGMA ${name}`).get();
13017
13061
  if (!row)
@@ -13164,6 +13208,15 @@ function withSqliteTransformPass(operation) {
13164
13208
  function withoutSqliteTransformPass(operation) {
13165
13209
  return transformPassScope.run(undefined, operation);
13166
13210
  }
13211
+ function guardSqliteTransformPass(guard) {
13212
+ const lease = transformPassScope.getStore();
13213
+ if (lease)
13214
+ lease.guard = guard;
13215
+ }
13216
+ function assertTransformWrite() {
13217
+ if (!backgroundWriterScope.getStore())
13218
+ transformPassScope.getStore()?.guard?.assert();
13219
+ }
13167
13220
  function isTransientSqliteError(error) {
13168
13221
  if (!error || typeof error !== "object")
13169
13222
  return false;
@@ -13175,6 +13228,7 @@ function foregroundWaitLease() {
13175
13228
  return lease?.active && !admissionScope.getStore() && !backgroundWriterScope.getStore() ? lease : undefined;
13176
13229
  }
13177
13230
  function withAutocommitTimeout(db, operation) {
13231
+ assertTransformWrite();
13178
13232
  const lease = foregroundWaitLease();
13179
13233
  if (isInTransaction(db) || !lease && !backgroundWriterScope.getStore())
13180
13234
  return operation();
@@ -13195,6 +13249,7 @@ function withAutocommitTimeout(db, operation) {
13195
13249
  }
13196
13250
  }
13197
13251
  function acquireShort(db, acquire, site) {
13252
+ assertTransformWrite();
13198
13253
  const started = performance.now();
13199
13254
  const previous = pragmaNumber(db, "busy_timeout");
13200
13255
  const outer = writerAcquisitionScope.getStore();
@@ -13206,7 +13261,7 @@ function acquireShort(db, acquire, site) {
13206
13261
  let acquiredAt;
13207
13262
  try {
13208
13263
  const lease = foregroundWaitLease();
13209
- const timeout = lease ? Math.floor(lease.remainingWaitMs) : SHORT_BUSY_TIMEOUT_MS;
13264
+ const timeout = Math.floor(Math.min(lease ? lease.remainingWaitMs : SHORT_BUSY_TIMEOUT_MS, owner?.remainingMs?.() ?? lease?.guard?.remainingMs() ?? Infinity));
13210
13265
  db.exec(`PRAGMA busy_timeout=${timeout}`);
13211
13266
  acquire();
13212
13267
  acquiredAt = performance.now();
@@ -13279,29 +13334,53 @@ async function beginSqliteWriterAsync(db, site) {
13279
13334
  }
13280
13335
  }
13281
13336
  }
13282
- async function withAsyncPrivilegedWriter(db, operation) {
13337
+ async function withAsyncPrivilegedWriter(db, operation, options = {}) {
13283
13338
  const started = performance.now();
13339
+ const budgetMs = Math.max(0, Math.min(FOREGROUND_ACQUISITION_BUDGET_MS, options.budgetMs ?? Infinity));
13284
13340
  let attempts = 0;
13285
13341
  for (;; ) {
13342
+ options.signal?.throwIfAborted();
13343
+ options.assertCurrentPass?.();
13286
13344
  attempts++;
13345
+ let entered = false;
13287
13346
  try {
13288
13347
  const acquisition = {
13289
13348
  site: "privileged_writer",
13290
13349
  lane: "foreground",
13291
13350
  startedAt: started,
13292
13351
  attempts,
13293
- claimed: false
13352
+ claimed: false,
13353
+ remainingMs: () => Math.max(0, budgetMs - (performance.now() - started))
13294
13354
  };
13295
- return admissionScope.run(true, () => withSqliteTransformPass(() => writerAcquisitionScope.run(acquisition, () => withPrivilegedWriter(db, operation))));
13355
+ return admissionScope.run(true, () => withSqliteTransformPass(() => writerAcquisitionScope.run(acquisition, () => withPrivilegedWriter(db, () => {
13356
+ entered = true;
13357
+ return operation();
13358
+ }))));
13296
13359
  } catch (error) {
13297
- if (!(error instanceof SqliteAcquisitionBusyError))
13360
+ if (entered || !(error instanceof SqliteAcquisitionBusyError))
13298
13361
  throw error;
13362
+ options.beforeRetry?.(error);
13299
13363
  const elapsed = performance.now() - started;
13300
- if (elapsed >= FOREGROUND_ACQUISITION_BUDGET_MS) {
13364
+ if (elapsed >= budgetMs) {
13301
13365
  reportSqliteDiagnostic(`sqlite writer site=privileged_writer lane=foreground acquire_ms=${Math.round(elapsed)} attempts=${attempts} outcome=busy`);
13302
13366
  throw error;
13303
13367
  }
13304
- await new Promise((resolve) => setTimeout(resolve, Math.min(attempts === 1 ? 500 : 1000, FOREGROUND_ACQUISITION_BUDGET_MS - elapsed)));
13368
+ const backoff = attempts === 1 ? 500 : 1000;
13369
+ const delay = Math.min(options.jitter ? backoff * (0.8 + Math.random() * 0.4) : backoff, budgetMs - elapsed);
13370
+ await new Promise((resolve, reject) => {
13371
+ const abort = () => {
13372
+ clearTimeout(timer);
13373
+ options.signal?.removeEventListener("abort", abort);
13374
+ reject(options.signal?.reason ?? new Error("Writer wait aborted"));
13375
+ };
13376
+ const timer = setTimeout(() => {
13377
+ options.signal?.removeEventListener("abort", abort);
13378
+ resolve();
13379
+ }, delay);
13380
+ options.signal?.addEventListener("abort", abort, { once: true });
13381
+ if (options.signal?.aborted)
13382
+ abort();
13383
+ });
13305
13384
  }
13306
13385
  }
13307
13386
  }
@@ -29798,6 +29877,24 @@ var init_storage_tags = __esm(() => {
29798
29877
  var historian_tool_defaults_default;
29799
29878
  var init_historian_tool_defaults = __esm(() => {
29800
29879
  historian_tool_defaults_default = {
29880
+ ctx_note: "Note ${input.action} ${input.note_ids}: ${input.content} → ${output.truncate(160)}",
29881
+ ctx_memory: "Memory ${input.action} ${input.category} ${input.ids}: ${input.content} → ${output.truncate(160)}",
29882
+ todowrite: 'Todos (${input.todos.count}): ${input.todos.each("${status}: ${content}")}',
29883
+ question: 'Asked user: ${input.questions[*].question.join(" / ")} → ${output}',
29884
+ task: "Task ${input.subagent_type}: ${input.prompt.truncate(600)} → ${output.truncate(200)}",
29885
+ ask: 'Asked: ${input.question} [options: ${input.options.join(" / ")}] ${input.text}${input.resolution} → ${output.truncate(300)}',
29886
+ peer_send: "PM ${input.recipient}: ${input.message}",
29887
+ board: 'Board ${input.verb}${input.board_lane}: ${input.ops.each("${op} ${lane_label} ${item}${props.text}${lane.items} ${state}${props.state}${lane.status}${id}${title}")}',
29888
+ room: "Room ${input.action} ${input.room_label}: ${input.room_detail}",
29889
+ work: "Work ${input.action} ${input.title}${input.id}: ${input.prompt.truncate(600)}${input.notes.truncate(600)} → ${output.truncate(160)}",
29890
+ knowhow: "Looked up how-to: ${input.query}${input.id}"
29891
+ };
29892
+ });
29893
+
29894
+ // ../plugin/src/shared/historian-tool-preview-defaults.json
29895
+ var historian_tool_preview_defaults_default;
29896
+ var init_historian_tool_preview_defaults = __esm(() => {
29897
+ historian_tool_preview_defaults_default = {
29801
29898
  ctx_note: "Note ${input.action} ${input.note_ids}: ${input.content} → ${output.truncate(160)}",
29802
29899
  ctx_memory: "Memory ${input.action} ${input.category} ${input.ids}: ${input.content} → ${output.truncate(160)}",
29803
29900
  todowrite: 'Todos (${input.todos.count}): ${input.todos.each("${status}: ${content}")}',
@@ -29813,20 +29910,52 @@ var init_historian_tool_defaults = __esm(() => {
29813
29910
  });
29814
29911
 
29815
29912
  // ../plugin/src/shared/historian-tool-expansions.ts
29816
- function expandToolPart(part, overrides) {
29913
+ function builtinInput(tool, input) {
29914
+ if (!input || typeof input !== "object")
29915
+ return input;
29916
+ const i = input;
29917
+ if (tool === "peer_send") {
29918
+ const external = i.external;
29919
+ const recipient = i.reply_to_pmid ? `reply to ${i.reply_to_pmid}` : i.agent || i.agent_id ? `to ${i.agent || i.agent_id}` : external?.session_id || i.session_id ? `to session ${external?.session_id || i.session_id}` : "to unknown recipient";
29920
+ return { ...i, recipient };
29921
+ }
29922
+ if (tool === "room") {
29923
+ return {
29924
+ ...i,
29925
+ room_label: i.room_id || i.title,
29926
+ room_detail: i.action === "create" || i.action === "invite" ? `invitees: ${renderToolTemplate("${input.invitees}", i)}` : i.text
29927
+ };
29928
+ }
29929
+ if (tool === "board") {
29930
+ return {
29931
+ ...i,
29932
+ board_lane: i.lane ? ` ${i.lane}` : "",
29933
+ ops: Array.isArray(i.ops) ? i.ops.map((op) => op && typeof op === "object" ? {
29934
+ ...op,
29935
+ lane_label: typeof op.lane === "string" ? op.lane : op.lane?.title || op.lane?.id
29936
+ } : op) : i.ops
29937
+ };
29938
+ }
29939
+ return input;
29940
+ }
29941
+ function expandToolPart(part, overrides, legacyPreview = false) {
29817
29942
  if (!part || typeof part !== "object")
29818
29943
  return null;
29819
29944
  const p = part;
29820
29945
  if (p.type !== "tool" || typeof p.tool !== "string")
29821
29946
  return null;
29822
- const template = overrides && Object.hasOwn(overrides, p.tool) ? overrides[p.tool] : Object.hasOwn(historian_tool_defaults_default, p.tool) ? historian_tool_defaults_default[p.tool] : undefined;
29947
+ const builtinDefaults = legacyPreview ? historian_tool_preview_defaults_default : historian_tool_defaults_default;
29948
+ const overridden = overrides && Object.hasOwn(overrides, p.tool);
29949
+ const template = overridden ? overrides[p.tool] : Object.hasOwn(builtinDefaults, p.tool) ? builtinDefaults[p.tool] : undefined;
29823
29950
  if (typeof template !== "string")
29824
29951
  return null;
29825
29952
  const state = p.state;
29826
- return renderToolTemplate(template, state?.input, state?.output ?? state?.error);
29953
+ const input = overridden || legacyPreview ? state?.input : builtinInput(p.tool, state?.input);
29954
+ return renderToolTemplate(template, input, state?.output ?? state?.error, legacyPreview);
29827
29955
  }
29828
29956
  var init_historian_tool_expansions = __esm(() => {
29829
29957
  init_historian_tool_defaults();
29958
+ init_historian_tool_preview_defaults();
29830
29959
  init_historian_tool_template();
29831
29960
  });
29832
29961
 
@@ -30589,7 +30718,8 @@ var init_overflow_detection = __esm(() => {
30589
30718
  PREFIX_BOUND_THINKING_MODELS = [
30590
30719
  ["fable", 5, 1],
30591
30720
  ["opus", 5, 5],
30592
- ["sonnet", 5, 5]
30721
+ ["sonnet", 5, 5],
30722
+ ["haiku", 5, 5]
30593
30723
  ];
30594
30724
  PREFIX_BOUND_THINKING_PATTERN = new RegExp(`(?:^|[-_.:/])(?:${PREFIX_BOUND_THINKING_MODELS.map(([family, major, minor]) => `${family}[-_.]?${major}[-_.]${minor}`).join("|")})(?:$|[-_.:/@])`, "i");
30595
30725
  });
@@ -42622,6 +42752,9 @@ function mergeRawConfigs(base, override) {
42622
42752
  }
42623
42753
  function parsePiConfig(rawConfig, recoveredTopLevelKeys = []) {
42624
42754
  const preMigrationWarnings = [];
42755
+ if (Object.hasOwn(rawConfig, "clear_reasoning_age")) {
42756
+ preMigrationWarnings.push("clear_reasoning_age is deprecated and ignored. Magic Context now keeps reasoning up to a token budget, keep_reasoning_tokens (default 10,000). Remove the key, or set keep_reasoning_tokens to a token count.");
42757
+ }
42625
42758
  const configWithoutRemovedAgent = stripRemovedAgentConfig(rawConfig, preMigrationWarnings);
42626
42759
  const hasDeprecatedProtectedTags = Object.hasOwn(rawConfig, "protected_tags");
42627
42760
  if (hasDeprecatedProtectedTags) {
@@ -46797,6 +46930,20 @@ function formatUnresolvedCompartmentSession(session) {
46797
46930
  return `session=${session.sessionId} unresolved=${session.count}`;
46798
46931
  }
46799
46932
 
46933
+ // src/commands/reasoning-config-migration.ts
46934
+ function removeDeprecatedReasoningAge(config) {
46935
+ if (!Object.hasOwn(config, "clear_reasoning_age"))
46936
+ return [];
46937
+ const age = config.clear_reasoning_age;
46938
+ delete config.clear_reasoning_age;
46939
+ const messages = [
46940
+ "Removed deprecated clear_reasoning_age (reasoning is now kept up to a token budget, keep_reasoning_tokens, default 10,000)."
46941
+ ];
46942
+ if (typeof age === "number" && age < 50)
46943
+ messages.push("You had set a lower age to keep less reasoning; set keep_reasoning_tokens (for example 8000) to keep less than the default.");
46944
+ return messages;
46945
+ }
46946
+
46800
46947
  // src/commands/doctor-opencode.ts
46801
46948
  import { spawnSync as spawnSync10 } from "node:child_process";
46802
46949
  import { existsSync as existsSync45, mkdtempSync as mkdtempSync3, readFileSync as readFileSync28, rmSync as rmSync8 } from "node:fs";
@@ -47613,6 +47760,13 @@ async function runDoctor2(options = {}) {
47613
47760
  log3.success("Removed deprecated compaction_markers (always-on since v0.21.4)");
47614
47761
  fixed++;
47615
47762
  }
47763
+ const reasoningMessages = removeDeprecatedReasoningAge(mcConfig);
47764
+ if (reasoningMessages.length > 0) {
47765
+ mcChanged = true;
47766
+ fixed++;
47767
+ for (const message of reasoningMessages)
47768
+ log3.success(message);
47769
+ }
47616
47770
  for (const deadKey of ["auto_drop_tool_age", "drop_tool_structure"]) {
47617
47771
  if (deadKey in mcConfig) {
47618
47772
  delete mcConfig[deadKey];
@@ -49124,7 +49278,8 @@ async function runHealthChecks2(options) {
49124
49278
  const repairPlan = {
49125
49279
  addPackageEntry: false,
49126
49280
  writeUserConfig: false,
49127
- clearCachePaths: []
49281
+ clearCachePaths: [],
49282
+ reasoningAgeConfigPaths: []
49128
49283
  };
49129
49284
  const self = options.deps.selfVersion();
49130
49285
  const pi = options.deps.detectPiBinary();
@@ -49195,6 +49350,10 @@ async function runHealthChecks2(options) {
49195
49350
  continue;
49196
49351
  }
49197
49352
  const parsed = readMagicContextJsonc(path, label);
49353
+ if (!parsed.error && Object.hasOwn(parsed.value, "clear_reasoning_age")) {
49354
+ repairPlan.reasoningAgeConfigPaths.push(path);
49355
+ add2(results, "warn", `${label} clear_reasoning_age is deprecated and ignored; doctor removes it`);
49356
+ }
49198
49357
  if (parsed.error)
49199
49358
  add2(results, "fail", `${label} magic-context.jsonc is invalid JSONC: ${parsed.error}`);
49200
49359
  else
@@ -49450,6 +49609,21 @@ function writeDefaultMagicContextConfig(path) {
49450
49609
  }
49451
49610
  function repair2(plan, prompts) {
49452
49611
  let fixed = 0;
49612
+ for (const path of plan.reasoningAgeConfigPaths) {
49613
+ const content = readFileSync30(path, "utf8");
49614
+ const parsed = readJsonc2(path);
49615
+ if (parsed.error)
49616
+ continue;
49617
+ const messages = removeDeprecatedReasoningAge(parsed.value);
49618
+ if (messages.length === 0)
49619
+ continue;
49620
+ writeFileAtomic(path, removeJsoncValue(content, ["clear_reasoning_age"]), {
49621
+ ownerOnly: true
49622
+ });
49623
+ for (const message of messages)
49624
+ prompts.log.success(message);
49625
+ fixed++;
49626
+ }
49453
49627
  if (plan.addPackageEntry) {
49454
49628
  const settingsPath = getPiUserExtensionsPath();
49455
49629
  try {
@@ -49676,6 +49850,7 @@ var init_doctor_pi = __esm(async () => {
49676
49850
  init_tick_failure();
49677
49851
  init_embedding_synapse();
49678
49852
  init_data_path();
49853
+ init_jsonc_edit();
49679
49854
  init_jsonc_parser();
49680
49855
  init_config2();
49681
49856
  init_model_chain_health();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/magic-context",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Unified CLI for Magic Context — setup, doctor, and migration across OpenCode, Pi, and OMP",
5
5
  "keywords": [
6
6
  "opencode",