@cortexkit/opencode-magic-context 0.17.1 → 0.18.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 (65) hide show
  1. package/dist/features/magic-context/dreamer/runner.d.ts +15 -0
  2. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  3. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  4. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  5. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  6. package/dist/features/magic-context/storage-meta-persisted.d.ts +14 -0
  7. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  8. package/dist/features/magic-context/storage-meta-shared.d.ts +1 -0
  9. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  10. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  11. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  12. package/dist/features/magic-context/storage.d.ts +1 -1
  13. package/dist/features/magic-context/storage.d.ts.map +1 -1
  14. package/dist/features/magic-context/tool-definition-tokens.d.ts +11 -0
  15. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  16. package/dist/features/magic-context/types.d.ts +1 -0
  17. package/dist/features/magic-context/types.d.ts.map +1 -1
  18. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +2 -0
  19. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  20. package/dist/hooks/magic-context/command-handler.d.ts +2 -0
  21. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +1 -0
  23. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +7 -0
  25. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/compartment-runner-types.d.ts +2 -0
  30. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/todo-view.d.ts +102 -0
  35. package/dist/hooks/magic-context/todo-view.d.ts.map +1 -0
  36. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +1 -0
  37. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/transform-message-helpers.d.ts +22 -0
  39. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/transform.d.ts +2 -0
  42. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  43. package/dist/index.js +660 -185
  44. package/dist/plugin/dream-timer.d.ts.map +1 -1
  45. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  46. package/dist/plugin/rpc-handlers.d.ts +2 -1
  47. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  48. package/dist/shared/index.d.ts +1 -0
  49. package/dist/shared/index.d.ts.map +1 -1
  50. package/dist/shared/model-suggestion-retry.d.ts +37 -0
  51. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  52. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  53. package/dist/shared/resolve-fallbacks.d.ts +32 -0
  54. package/dist/shared/resolve-fallbacks.d.ts.map +1 -0
  55. package/dist/shared/tag-transcript.d.ts.map +1 -1
  56. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/shared/index.ts +1 -0
  59. package/src/shared/model-suggestion-retry.test.ts +251 -0
  60. package/src/shared/model-suggestion-retry.ts +194 -6
  61. package/src/shared/models-dev-cache.ts +7 -7
  62. package/src/shared/resolve-fallbacks.test.ts +136 -0
  63. package/src/shared/resolve-fallbacks.ts +76 -0
  64. package/src/shared/tag-transcript.ts +3 -2
  65. package/src/tui/index.tsx +114 -18
package/dist/index.js CHANGED
@@ -15122,6 +15122,161 @@ var init_model_requirements = __esm(() => {
15122
15122
  };
15123
15123
  });
15124
15124
 
15125
+ // src/features/magic-context/overflow-detection.ts
15126
+ function extractErrorMessage(error51) {
15127
+ if (!error51)
15128
+ return "";
15129
+ if (typeof error51 === "string")
15130
+ return error51;
15131
+ if (typeof error51 === "object") {
15132
+ const obj = error51;
15133
+ const nested = obj.error;
15134
+ if (nested && typeof nested.message === "string" && nested.message.length > 0) {
15135
+ return nested.message;
15136
+ }
15137
+ }
15138
+ if (error51 instanceof Error)
15139
+ return error51.message;
15140
+ if (typeof error51 === "object") {
15141
+ const obj = error51;
15142
+ if (typeof obj.message === "string")
15143
+ return obj.message;
15144
+ if (typeof obj.responseBody === "string")
15145
+ return obj.responseBody;
15146
+ try {
15147
+ return JSON.stringify(error51);
15148
+ } catch {
15149
+ return String(error51);
15150
+ }
15151
+ }
15152
+ return String(error51);
15153
+ }
15154
+ function detectOverflow(error51) {
15155
+ const message = extractErrorMessage(error51);
15156
+ if (!message) {
15157
+ return { isOverflow: false };
15158
+ }
15159
+ const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
15160
+ let matched;
15161
+ for (const pattern of OVERFLOW_PATTERNS) {
15162
+ if (pattern.test(message)) {
15163
+ matched = pattern;
15164
+ break;
15165
+ }
15166
+ }
15167
+ if (!matched && !hasStatus413) {
15168
+ return { isOverflow: false };
15169
+ }
15170
+ const reportedLimit = parseReportedLimit(message);
15171
+ return {
15172
+ isOverflow: true,
15173
+ reportedLimit,
15174
+ matchedPattern: matched?.source
15175
+ };
15176
+ }
15177
+ function parseReportedLimit(message) {
15178
+ if (!message)
15179
+ return;
15180
+ for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
15181
+ const match = message.match(pattern);
15182
+ if (!match)
15183
+ continue;
15184
+ const raw = match[1];
15185
+ if (!raw)
15186
+ continue;
15187
+ const value = Number.parseInt(raw, 10);
15188
+ if (!Number.isFinite(value))
15189
+ continue;
15190
+ if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
15191
+ continue;
15192
+ return value;
15193
+ }
15194
+ return;
15195
+ }
15196
+ var OVERFLOW_PATTERNS, LIMIT_EXTRACTION_PATTERNS, MIN_PLAUSIBLE_LIMIT = 1024, MAX_PLAUSIBLE_LIMIT = 1e7;
15197
+ var init_overflow_detection = __esm(() => {
15198
+ OVERFLOW_PATTERNS = [
15199
+ /prompt is too long/i,
15200
+ /input is too long for requested model/i,
15201
+ /exceeds the context window/i,
15202
+ /input token count.*exceeds the maximum/i,
15203
+ /maximum prompt length is \d+/i,
15204
+ /reduce the length of the messages/i,
15205
+ /maximum context length is \d+ tokens/i,
15206
+ /exceeds the limit of \d+/i,
15207
+ /exceeds the available context size/i,
15208
+ /greater than the context length/i,
15209
+ /context window exceeds limit/i,
15210
+ /exceeded model token limit/i,
15211
+ /context[_ ]length[_ ]exceeded/i,
15212
+ /request entity too large/i,
15213
+ /context length is only \d+ tokens/i,
15214
+ /input length.*exceeds.*context length/i,
15215
+ /prompt too long; exceeded (?:max )?context length/i,
15216
+ /too large for model with \d+ maximum context length/i,
15217
+ /model_context_window_exceeded/i,
15218
+ /context size has been exceeded/i
15219
+ ];
15220
+ LIMIT_EXTRACTION_PATTERNS = [
15221
+ /maximum prompt length is (\d+)/i,
15222
+ /maximum context length is (\d+) tokens?/i,
15223
+ /context length is only (\d+) tokens?/i,
15224
+ /exceeds the limit of (\d+)/i,
15225
+ /too large for model with (\d+) maximum context length/i,
15226
+ /context size.*(\d+) tokens?/i,
15227
+ /exceeds? the context length of (\d+)/i,
15228
+ /max(?:imum)?.*context.*?(\d+)/i
15229
+ ];
15230
+ });
15231
+
15232
+ // src/shared/resolve-fallbacks.ts
15233
+ function resolveFallbackChain(agentName, userFallbacks) {
15234
+ const userList = normalizeUserFallbacks(userFallbacks);
15235
+ if (userList.length > 0) {
15236
+ return dedupe(userList.filter(isValidModelSpec));
15237
+ }
15238
+ const builtin = getAgentFallbackModels(agentName);
15239
+ if (!builtin || builtin.length === 0)
15240
+ return [];
15241
+ return dedupe(builtin.filter(isValidModelSpec));
15242
+ }
15243
+ function normalizeUserFallbacks(userFallbacks) {
15244
+ if (!userFallbacks)
15245
+ return [];
15246
+ if (typeof userFallbacks === "string") {
15247
+ const trimmed = userFallbacks.trim();
15248
+ return trimmed ? [trimmed] : [];
15249
+ }
15250
+ return userFallbacks.map((s) => s.trim()).filter((s) => s.length > 0);
15251
+ }
15252
+ function isValidModelSpec(spec) {
15253
+ const slash = spec.indexOf("/");
15254
+ return slash > 0 && slash < spec.length - 1;
15255
+ }
15256
+ function dedupe(list) {
15257
+ const seen = new Set;
15258
+ const out = [];
15259
+ for (const item of list) {
15260
+ if (seen.has(item))
15261
+ continue;
15262
+ seen.add(item);
15263
+ out.push(item);
15264
+ }
15265
+ return out;
15266
+ }
15267
+ function parseProviderModel(spec) {
15268
+ const slash = spec.indexOf("/");
15269
+ if (slash < 1 || slash >= spec.length - 1)
15270
+ return null;
15271
+ return {
15272
+ providerID: spec.slice(0, slash).trim(),
15273
+ modelID: spec.slice(slash + 1).trim()
15274
+ };
15275
+ }
15276
+ var init_resolve_fallbacks = __esm(() => {
15277
+ init_model_requirements();
15278
+ });
15279
+
15125
15280
  // src/shared/model-suggestion-retry.ts
15126
15281
  function extractMessage(error51) {
15127
15282
  if (typeof error51 === "string")
@@ -15177,6 +15332,9 @@ function parseModelSuggestion(error51) {
15177
15332
  };
15178
15333
  }
15179
15334
  async function promptWithTimeout(client, args, timeoutMs, signal) {
15335
+ if (signal?.aborted) {
15336
+ throw new Error("prompt aborted by external signal");
15337
+ }
15180
15338
  const controller = new AbortController;
15181
15339
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
15182
15340
  const onExternalAbort = () => controller.abort();
@@ -15199,16 +15357,39 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
15199
15357
  signal?.removeEventListener("abort", onExternalAbort);
15200
15358
  }
15201
15359
  }
15202
- async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15203
- const timeoutMs = options.timeoutMs ?? 300000;
15360
+ function isNonRetryable(error51, externalSignal) {
15361
+ if (externalSignal?.aborted)
15362
+ return true;
15363
+ if (error51 instanceof Error) {
15364
+ if (error51.name === "AbortError")
15365
+ return true;
15366
+ if (error51.message === "prompt aborted by external signal")
15367
+ return true;
15368
+ if (/^prompt timed out after \d+ms$/.test(error51.message))
15369
+ return true;
15370
+ }
15371
+ if (detectOverflow(error51).isOverflow)
15372
+ return true;
15373
+ return false;
15374
+ }
15375
+ function shortErr(error51) {
15376
+ if (error51 instanceof Error) {
15377
+ return error51.name && error51.name !== "Error" ? `${error51.name}: ${error51.message}` : error51.message;
15378
+ }
15379
+ return extractMessage(error51);
15380
+ }
15381
+ async function attemptOnce(client, args, timeoutMs, signal, callContext, label) {
15204
15382
  try {
15205
- await promptWithTimeout(client, args, timeoutMs, options.signal);
15383
+ await promptWithTimeout(client, args, timeoutMs, signal);
15384
+ return;
15206
15385
  } catch (error51) {
15386
+ if (isNonRetryable(error51, signal))
15387
+ throw error51;
15207
15388
  const suggestion = parseModelSuggestion(error51);
15208
15389
  if (!suggestion || !args.body.model) {
15209
15390
  throw error51;
15210
15391
  }
15211
- log("[model-suggestion-retry] Model not found, retrying with suggestion", {
15392
+ log(`[${callContext}] ${label}: model not found, retrying with suggestion`, {
15212
15393
  original: `${suggestion.providerID}/${suggestion.modelID}`,
15213
15394
  suggested: suggestion.suggestion
15214
15395
  });
@@ -15221,11 +15402,59 @@ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15221
15402
  modelID: suggestion.suggestion
15222
15403
  }
15223
15404
  }
15224
- }, timeoutMs, options.signal);
15405
+ }, timeoutMs, signal);
15225
15406
  }
15226
15407
  }
15408
+ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
15409
+ const timeoutMs = options.timeoutMs ?? 300000;
15410
+ const callContext = options.callContext ?? "subagent";
15411
+ const fallbacks = options.fallbackModels ?? [];
15412
+ const explicitPrimaryLabel = args.body.model?.providerID && args.body.model.modelID ? `${args.body.model.providerID}/${args.body.model.modelID}` : "primary";
15413
+ let lastError = null;
15414
+ try {
15415
+ await attemptOnce(client, args, timeoutMs, options.signal, callContext, explicitPrimaryLabel);
15416
+ return;
15417
+ } catch (error51) {
15418
+ lastError = error51;
15419
+ if (isNonRetryable(error51, options.signal))
15420
+ throw error51;
15421
+ if (fallbacks.length === 0) {
15422
+ throw error51;
15423
+ }
15424
+ log(`[${callContext}] primary (${explicitPrimaryLabel}) failed: ${shortErr(error51)}; trying ${fallbacks.length} fallback(s)`);
15425
+ }
15426
+ for (let i = 0;i < fallbacks.length; i += 1) {
15427
+ const parsed = parseProviderModel(fallbacks[i]);
15428
+ if (!parsed) {
15429
+ log(`[${callContext}] skipping invalid fallback spec: ${fallbacks[i]}`);
15430
+ continue;
15431
+ }
15432
+ const label = `${parsed.providerID}/${parsed.modelID}`;
15433
+ const attemptArgs = {
15434
+ ...args,
15435
+ body: { ...args.body, model: parsed }
15436
+ };
15437
+ try {
15438
+ await attemptOnce(client, attemptArgs, timeoutMs, options.signal, callContext, label);
15439
+ log(`[${callContext}] fallback succeeded with ${label} (attempt ${i + 2}/${fallbacks.length + 1})`);
15440
+ return;
15441
+ } catch (error51) {
15442
+ lastError = error51;
15443
+ if (isNonRetryable(error51, options.signal))
15444
+ throw error51;
15445
+ const remaining = fallbacks.length - i - 1;
15446
+ if (remaining > 0) {
15447
+ log(`[${callContext}] ${label} failed: ${shortErr(error51)}; ${remaining} fallback(s) left`);
15448
+ }
15449
+ }
15450
+ }
15451
+ log(`[${callContext}] all models exhausted; tried: ${[explicitPrimaryLabel, ...fallbacks].join(", ")}; last error: ${shortErr(lastError)}`);
15452
+ throw lastError ?? new Error("All fallback models failed");
15453
+ }
15227
15454
  var init_model_suggestion_retry = __esm(() => {
15455
+ init_overflow_detection();
15228
15456
  init_logger();
15457
+ init_resolve_fallbacks();
15229
15458
  });
15230
15459
 
15231
15460
  // src/shared/normalize-sdk-response.ts
@@ -15257,6 +15486,7 @@ var init_shared = __esm(() => {
15257
15486
  init_logger();
15258
15487
  init_model_requirements();
15259
15488
  init_model_suggestion_retry();
15489
+ init_resolve_fallbacks();
15260
15490
  });
15261
15491
 
15262
15492
  // src/shared/record-type-guard.ts
@@ -147913,8 +148143,20 @@ function keyFor(providerID, modelID, agentName) {
147913
148143
  const agent = agentName && agentName.length > 0 ? agentName : "default";
147914
148144
  return `${providerID}/${modelID}/${agent}`;
147915
148145
  }
148146
+ function fingerprintFor(description, parameters) {
148147
+ const descLen = description.length;
148148
+ if (parameters === undefined)
148149
+ return `${descLen}:none`;
148150
+ if (parameters === null)
148151
+ return `${descLen}:null`;
148152
+ if (typeof parameters !== "object")
148153
+ return `${descLen}:${typeof parameters}`;
148154
+ const keys = Object.keys(parameters);
148155
+ return `${descLen}:obj:${keys.length}:${keys.sort().join(",")}`;
148156
+ }
147916
148157
  function setDatabase(db) {
147917
148158
  persistenceDb = db;
148159
+ cachedInsertStmt = null;
147918
148160
  }
147919
148161
  function loadToolDefinitionMeasurements(db) {
147920
148162
  let rows = [];
@@ -147937,6 +148179,10 @@ function recordToolDefinition(providerID, modelID, agentName, toolID, descriptio
147937
148179
  if (!providerID || !modelID || !toolID)
147938
148180
  return;
147939
148181
  const key = keyFor(providerID, modelID, agentName);
148182
+ const fp = fingerprintFor(description ?? "", parameters);
148183
+ let innerFp = fingerprints.get(key);
148184
+ if (innerFp && innerFp.get(toolID) === fp)
148185
+ return;
147940
148186
  let paramsText = "";
147941
148187
  try {
147942
148188
  paramsText = parameters === undefined ? "" : JSON.stringify(parameters);
@@ -147950,13 +148196,23 @@ function recordToolDefinition(providerID, modelID, agentName, toolID, descriptio
147950
148196
  measurements.set(key, inner);
147951
148197
  }
147952
148198
  inner.set(toolID, tokens);
148199
+ if (!innerFp) {
148200
+ innerFp = new Map;
148201
+ fingerprints.set(key, innerFp);
148202
+ }
148203
+ innerFp.set(toolID, fp);
147953
148204
  if (persistenceDb) {
147954
148205
  try {
147955
148206
  const agent = agentName && agentName.length > 0 ? agentName : "default";
147956
- persistenceDb.prepare(`INSERT OR REPLACE INTO tool_definition_measurements
148207
+ if (!cachedInsertStmt) {
148208
+ cachedInsertStmt = persistenceDb.prepare(`INSERT OR REPLACE INTO tool_definition_measurements
147957
148209
  (provider_id, model_id, agent_name, tool_id, token_count, recorded_at)
147958
- VALUES (?, ?, ?, ?, ?, ?)`).run(providerID, modelID, agent, toolID, tokens, Date.now());
147959
- } catch {}
148210
+ VALUES (?, ?, ?, ?, ?, ?)`);
148211
+ }
148212
+ cachedInsertStmt.run(providerID, modelID, agent, toolID, tokens, Date.now());
148213
+ } catch {
148214
+ cachedInsertStmt = null;
148215
+ }
147960
148216
  }
147961
148217
  }
147962
148218
  function getMeasuredToolDefinitionTokens(providerID, modelID, agentName) {
@@ -147970,10 +148226,11 @@ function getMeasuredToolDefinitionTokens(providerID, modelID, agentName) {
147970
148226
  total += tokens;
147971
148227
  return total;
147972
148228
  }
147973
- var measurements, persistenceDb = null;
148229
+ var measurements, fingerprints, persistenceDb = null, cachedInsertStmt = null;
147974
148230
  var init_tool_definition_tokens = __esm(() => {
147975
148231
  init_read_session_formatting();
147976
148232
  measurements = new Map;
148233
+ fingerprints = new Map;
147977
148234
  });
147978
148235
 
147979
148236
  // ../../node_modules/.bun/esprima@4.0.1/node_modules/esprima/dist/esprima.js
@@ -160007,6 +160264,25 @@ var init_migrations = __esm(async () => {
160007
160264
  WHERE type = 'tool' AND tool_owner_message_id IS NULL;
160008
160265
  `);
160009
160266
  }
160267
+ },
160268
+ {
160269
+ version: 11,
160270
+ description: "Add todo state synthesis columns to session_meta",
160271
+ up: (db) => {
160272
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160273
+ if (!cols.some((c) => c.name === "last_todo_state")) {
160274
+ db.exec("ALTER TABLE session_meta ADD COLUMN last_todo_state TEXT DEFAULT ''");
160275
+ }
160276
+ if (!cols.some((c) => c.name === "todo_synthetic_call_id")) {
160277
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_call_id TEXT DEFAULT ''");
160278
+ }
160279
+ if (!cols.some((c) => c.name === "todo_synthetic_anchor_message_id")) {
160280
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_anchor_message_id TEXT DEFAULT ''");
160281
+ }
160282
+ if (!cols.some((c) => c.name === "todo_synthetic_state_json")) {
160283
+ db.exec("ALTER TABLE session_meta ADD COLUMN todo_synthetic_state_json TEXT DEFAULT ''");
160284
+ }
160285
+ }
160010
160286
  }
160011
160287
  ];
160012
160288
  });
@@ -160489,6 +160765,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160489
160765
  note_nudge_trigger_message_id TEXT DEFAULT '',
160490
160766
  note_nudge_sticky_text TEXT DEFAULT '',
160491
160767
  note_nudge_sticky_message_id TEXT DEFAULT '',
160768
+ last_todo_state TEXT DEFAULT '',
160769
+ todo_synthetic_call_id TEXT DEFAULT '',
160770
+ todo_synthetic_anchor_message_id TEXT DEFAULT '',
160771
+ todo_synthetic_state_json TEXT DEFAULT '',
160492
160772
  is_subagent INTEGER DEFAULT 0,
160493
160773
  last_context_percentage REAL DEFAULT 0,
160494
160774
  last_input_tokens INTEGER DEFAULT 0,
@@ -160553,6 +160833,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160553
160833
  ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
160554
160834
  ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
160555
160835
  ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
160836
+ ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
160837
+ ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
160838
+ ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
160839
+ ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
160556
160840
  ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
160557
160841
  ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
160558
160842
  ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
@@ -160617,6 +160901,10 @@ function healNullTextColumns(db) {
160617
160901
  ["note_nudge_trigger_message_id", ""],
160618
160902
  ["note_nudge_sticky_text", ""],
160619
160903
  ["note_nudge_sticky_message_id", ""],
160904
+ ["last_todo_state", ""],
160905
+ ["todo_synthetic_call_id", ""],
160906
+ ["todo_synthetic_anchor_message_id", ""],
160907
+ ["todo_synthetic_state_json", ""],
160620
160908
  ["system_prompt_hash", ""],
160621
160909
  ["stripped_placeholder_ids", ""],
160622
160910
  ["memory_block_cache", ""],
@@ -160722,7 +161010,7 @@ function isSessionMetaRow(row) {
160722
161010
  if (row === null || typeof row !== "object")
160723
161011
  return false;
160724
161012
  const r = row;
160725
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag);
161013
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && isStringOrNull(r.cache_ttl) && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && isStringOrNull(r.last_nudge_band) && isStringOrNull(r.last_transform_error) && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && isNumberOrNull(r.times_execute_threshold_reached) && isNumberOrNull(r.compartment_in_progress) && (r.system_prompt_hash === null || typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && isNumberOrNull(r.system_prompt_tokens) && isNumberOrNull(r.conversation_tokens) && isNumberOrNull(r.tool_call_tokens) && isNumberOrNull(r.cleared_reasoning_through_tag) && isStringOrNull(r.last_todo_state);
160726
161014
  }
160727
161015
  function getDefaultSessionMeta(sessionId) {
160728
161016
  return {
@@ -160742,7 +161030,8 @@ function getDefaultSessionMeta(sessionId) {
160742
161030
  systemPromptTokens: 0,
160743
161031
  conversationTokens: 0,
160744
161032
  toolCallTokens: 0,
160745
- clearedReasoningThroughTag: 0
161033
+ clearedReasoningThroughTag: 0,
161034
+ lastTodoState: ""
160746
161035
  };
160747
161036
  }
160748
161037
  function ensureSessionMetaRow(db, sessionId) {
@@ -160754,6 +161043,7 @@ function toSessionMeta(row) {
160754
161043
  const transformErrorRaw = typeof row.last_transform_error === "string" ? row.last_transform_error : "";
160755
161044
  const cacheTtlRaw = typeof row.cache_ttl === "string" && row.cache_ttl.length > 0 ? row.cache_ttl : "5m";
160756
161045
  const systemPromptHashRaw = row.system_prompt_hash == null ? "" : row.system_prompt_hash;
161046
+ const lastTodoStateRaw = typeof row.last_todo_state === "string" ? row.last_todo_state : "";
160757
161047
  const numOrZero = (value) => typeof value === "number" ? value : 0;
160758
161048
  return {
160759
161049
  sessionId: row.session_id,
@@ -160772,7 +161062,8 @@ function toSessionMeta(row) {
160772
161062
  systemPromptTokens: numOrZero(row.system_prompt_tokens),
160773
161063
  conversationTokens: numOrZero(row.conversation_tokens),
160774
161064
  toolCallTokens: numOrZero(row.tool_call_tokens),
160775
- clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag)
161065
+ clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag),
161066
+ lastTodoState: lastTodoStateRaw
160776
161067
  };
160777
161068
  }
160778
161069
  var META_COLUMNS, BOOLEAN_META_KEYS;
@@ -160793,7 +161084,8 @@ var init_storage_meta_shared = __esm(() => {
160793
161084
  systemPromptTokens: "system_prompt_tokens",
160794
161085
  conversationTokens: "conversation_tokens",
160795
161086
  toolCallTokens: "tool_call_tokens",
160796
- clearedReasoningThroughTag: "cleared_reasoning_through_tag"
161087
+ clearedReasoningThroughTag: "cleared_reasoning_through_tag",
161088
+ lastTodoState: "last_todo_state"
160797
161089
  };
160798
161090
  BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
160799
161091
  });
@@ -160829,6 +161121,12 @@ function isPersistedNoteNudgeRow(row) {
160829
161121
  const r = row;
160830
161122
  return typeof r.note_nudge_trigger_pending === "number" && typeof r.note_nudge_trigger_message_id === "string" && typeof r.note_nudge_sticky_text === "string" && typeof r.note_nudge_sticky_message_id === "string";
160831
161123
  }
161124
+ function isPersistedTodoSyntheticAnchorRow(row) {
161125
+ if (row === null || typeof row !== "object")
161126
+ return false;
161127
+ const r = row;
161128
+ return typeof r.todo_synthetic_call_id === "string" && typeof r.todo_synthetic_anchor_message_id === "string" && typeof r.todo_synthetic_state_json === "string";
161129
+ }
160832
161130
  function isPersistedHistorianFailureRow(row) {
160833
161131
  if (row === null || typeof row !== "object")
160834
161132
  return false;
@@ -160951,6 +161249,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
160951
161249
  function clearPersistedNoteNudge(db, sessionId) {
160952
161250
  db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 0, note_nudge_trigger_message_id = '', note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(sessionId);
160953
161251
  }
161252
+ function getPersistedTodoSyntheticAnchor(db, sessionId) {
161253
+ const result = db.prepare("SELECT todo_synthetic_call_id, todo_synthetic_anchor_message_id, todo_synthetic_state_json FROM session_meta WHERE session_id = ?").get(sessionId);
161254
+ if (!isPersistedTodoSyntheticAnchorRow(result)) {
161255
+ return null;
161256
+ }
161257
+ if (result.todo_synthetic_call_id.length === 0 || result.todo_synthetic_anchor_message_id.length === 0) {
161258
+ return null;
161259
+ }
161260
+ return {
161261
+ callId: result.todo_synthetic_call_id,
161262
+ messageId: result.todo_synthetic_anchor_message_id,
161263
+ stateJson: result.todo_synthetic_state_json
161264
+ };
161265
+ }
161266
+ function setPersistedTodoSyntheticAnchor(db, sessionId, callId, messageId, stateJson) {
161267
+ db.transaction(() => {
161268
+ ensureSessionMetaRow(db, sessionId);
161269
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = ?, todo_synthetic_anchor_message_id = ?, todo_synthetic_state_json = ? WHERE session_id = ?").run(callId, messageId, stateJson, sessionId);
161270
+ })();
161271
+ }
161272
+ function clearPersistedTodoSyntheticAnchor(db, sessionId) {
161273
+ db.prepare("UPDATE session_meta SET todo_synthetic_call_id = '', todo_synthetic_anchor_message_id = '', todo_synthetic_state_json = '' WHERE session_id = ?").run(sessionId);
161274
+ }
160954
161275
  function getNoteLastReadAt(db, sessionId) {
160955
161276
  try {
160956
161277
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -161103,7 +161424,7 @@ var init_resolve_subagent_fallback = __esm(async () => {
161103
161424
 
161104
161425
  // src/features/magic-context/storage-meta-session.ts
161105
161426
  function getOrCreateSessionMeta(db, sessionId) {
161106
- const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?").get(sessionId);
161427
+ const result = db.prepare("SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, conversation_tokens, tool_call_tokens, cleared_reasoning_through_tag, last_todo_state FROM session_meta WHERE session_id = ?").get(sessionId);
161107
161428
  if (isSessionMetaRow(result)) {
161108
161429
  return toSessionMeta(result);
161109
161430
  }
@@ -161677,9 +161998,7 @@ function loadModelsDevMetadataFromFile() {
161677
161998
  try {
161678
161999
  const configPath = getOpencodeConfigPath();
161679
162000
  if (configPath && existsSync12(configPath)) {
161680
- let raw = readFileSync8(configPath, "utf-8");
161681
- raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
161682
- const config2 = JSON.parse(raw);
162001
+ const config2 = parseJsonc(readFileSync8(configPath, "utf-8"));
161683
162002
  if (config2.provider && typeof config2.provider === "object") {
161684
162003
  for (const [providerId, provider2] of Object.entries(config2.provider)) {
161685
162004
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -161751,6 +162070,7 @@ function getModelsDevContextLimit(providerID, modelID) {
161751
162070
  var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, recentlySeenApiSizes, oscillationLogged = false, fileCache = null, fileLastAttempt = 0;
161752
162071
  var init_models_dev_cache = __esm(() => {
161753
162072
  init_data_path();
162073
+ init_jsonc_parser();
161754
162074
  init_logger();
161755
162075
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
161756
162076
  recentlySeenApiSizes = new Set;
@@ -162098,7 +162418,8 @@ async function runHistorianPrompt(args) {
162098
162418
  timeoutMs,
162099
162419
  dumpLabel,
162100
162420
  modelOverride,
162101
- agentId = HISTORIAN_AGENT
162421
+ agentId = HISTORIAN_AGENT,
162422
+ fallbackModels
162102
162423
  } = args;
162103
162424
  let agentSessionId = null;
162104
162425
  try {
@@ -162125,7 +162446,11 @@ async function runHistorianPrompt(args) {
162125
162446
  ...modelOverride ? { model: modelOverride } : {},
162126
162447
  parts: [{ type: "text", text: prompt, synthetic: true }]
162127
162448
  }
162128
- }, { timeoutMs: timeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
162449
+ }, {
162450
+ timeoutMs: timeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
162451
+ fallbackModels: modelOverride ? undefined : fallbackModels,
162452
+ callContext: agentId === HISTORIAN_EDITOR_AGENT ? "historian:editor" : "historian"
162453
+ });
162129
162454
  sessionLog(parentSessionId, `historian: prompt completed (attempt ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES + 1})`);
162130
162455
  break;
162131
162456
  } catch (error51) {
@@ -163090,6 +163415,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
163090
163415
  dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
163091
163416
  timeoutMs: historianTimeoutMs,
163092
163417
  fallbackModelId: deps.fallbackModelId,
163418
+ fallbackModels: deps.fallbackModels,
163093
163419
  twoPass: deps.historianTwoPass,
163094
163420
  callbacks: {
163095
163421
  onRepairRetry: async (error51) => {
@@ -164291,7 +164617,8 @@ async function runCompressorPass(args) {
164291
164617
  targetTokens,
164292
164618
  outputCount,
164293
164619
  outputDepth,
164294
- historianTimeoutMs
164620
+ historianTimeoutMs,
164621
+ fallbackModels
164295
164622
  } = args;
164296
164623
  const prompt = buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount);
164297
164624
  let agentSessionId = null;
@@ -164313,7 +164640,11 @@ async function runCompressorPass(args) {
164313
164640
  agent: HISTORIAN_AGENT2,
164314
164641
  parts: [{ type: "text", text: prompt, synthetic: true }]
164315
164642
  }
164316
- }, { timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
164643
+ }, {
164644
+ timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
164645
+ fallbackModels,
164646
+ callContext: "compressor"
164647
+ });
164317
164648
  const messagesResponse = await client.session.messages({
164318
164649
  path: { id: agentSessionId },
164319
164650
  query: { directory }
@@ -164493,6 +164824,7 @@ ${chunk.text}`, { stateFilePath });
164493
164824
  dumpLabelBase: `incremental-${sessionId}-${chunk.startIndex}-${chunk.endIndex}`,
164494
164825
  timeoutMs: historianTimeoutMs,
164495
164826
  fallbackModelId: deps.fallbackModelId,
164827
+ fallbackModels: deps.fallbackModels,
164496
164828
  twoPass: deps.historianTwoPass
164497
164829
  });
164498
164830
  if (!validatedPass.ok) {
@@ -164541,6 +164873,7 @@ No new compartments or facts were written. Check the historian model/output and
164541
164873
  directory: sessionDirectory,
164542
164874
  historyBudgetTokens: deps.historyBudgetTokens,
164543
164875
  historianTimeoutMs,
164876
+ fallbackModels: deps.fallbackModels,
164544
164877
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164545
164878
  maxMergeDepth: deps.compressorMaxMergeDepth
164546
164879
  });
@@ -164729,6 +165062,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
164729
165062
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
164730
165063
  timeoutMs: historianTimeoutMs,
164731
165064
  fallbackModelId: deps.fallbackModelId,
165065
+ fallbackModels: deps.fallbackModels,
164732
165066
  twoPass: deps.historianTwoPass,
164733
165067
  callbacks: {
164734
165068
  onRepairRetry: async (error51) => {
@@ -164826,6 +165160,7 @@ Nothing was written.`;
164826
165160
  directory: sessionDirectory,
164827
165161
  historyBudgetTokens: deps.historyBudgetTokens,
164828
165162
  historianTimeoutMs,
165163
+ fallbackModels: deps.fallbackModels,
164829
165164
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164830
165165
  maxMergeDepth: deps.compressorMaxMergeDepth
164831
165166
  });
@@ -165695,6 +166030,7 @@ function buildDreamTaskPrompt(task, args) {
165695
166030
  init_shared();
165696
166031
  init_assistant_message_extractor();
165697
166032
  init_logger();
166033
+ init_resolve_fallbacks();
165698
166034
 
165699
166035
  // src/features/magic-context/sidekick/core.ts
165700
166036
  var SIDEKICK_SYSTEM_PROMPT = `You are Sidekick, a focused memory-retrieval subagent for an AI coding assistant.
@@ -165714,6 +166050,7 @@ function stripThinkingBlocks(text) {
165714
166050
 
165715
166051
  // src/features/magic-context/sidekick/agent.ts
165716
166052
  async function runSidekick(deps) {
166053
+ const fallbackModels = resolveFallbackChain(SIDEKICK_AGENT, deps.config.fallback_models);
165717
166054
  let agentSessionId = null;
165718
166055
  try {
165719
166056
  const createResponse = await deps.client.session.create({
@@ -165736,7 +166073,7 @@ async function runSidekick(deps) {
165736
166073
  system: deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT,
165737
166074
  parts: [{ type: "text", text: deps.userMessage, synthetic: true }]
165738
166075
  }
165739
- }, { timeoutMs: deps.config.timeout_ms });
166076
+ }, { timeoutMs: deps.config.timeout_ms, fallbackModels, callContext: "sidekick" });
165740
166077
  const messagesResponse = await deps.client.session.messages({
165741
166078
  path: { id: agentSessionId },
165742
166079
  query: { directory: deps.sessionDirectory ?? deps.projectPath }
@@ -166360,7 +166697,6 @@ function createLiveSessionState() {
166360
166697
 
166361
166698
  // src/index.ts
166362
166699
  init_conflict_warning_hook();
166363
-
166364
166700
  // src/features/magic-context/dreamer/storage-dream-state.ts
166365
166701
  var getDreamStateStatements = new WeakMap;
166366
166702
  var setDreamStateStatements = new WeakMap;
@@ -166806,7 +167142,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166806
167142
  system: DREAMER_SYSTEM_PROMPT,
166807
167143
  parts: [{ type: "text", text: prompt, synthetic: true }]
166808
167144
  }
166809
- }, { timeoutMs: Math.min(remainingMs, 5 * 60 * 1000), signal: abortController.signal });
167145
+ }, {
167146
+ timeoutMs: Math.min(remainingMs, 5 * 60 * 1000),
167147
+ signal: abortController.signal,
167148
+ fallbackModels: args.fallbackModels,
167149
+ callContext: "dreamer:user-memories"
167150
+ });
166810
167151
  const messagesResponse = await args.client.session.messages({
166811
167152
  path: { id: agentSessionId },
166812
167153
  query: { directory: args.sessionDirectory }
@@ -166865,7 +167206,8 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166865
167206
  }
166866
167207
  return result;
166867
167208
  } catch (error51) {
166868
- log(`[dreamer] user-memories: review failed: ${getErrorMessage(error51)}`);
167209
+ const errorDescription = describeError(error51);
167210
+ log(`[dreamer] user-memories: review failed: ${errorDescription.brief}`, errorDescription.stackHead ? { stackHead: errorDescription.stackHead } : undefined);
166869
167211
  return result;
166870
167212
  } finally {
166871
167213
  clearInterval(leaseInterval);
@@ -166896,6 +167238,7 @@ function insertDreamRun(db, run) {
166896
167238
 
166897
167239
  // src/features/magic-context/dreamer/runner.ts
166898
167240
  var dreamProjectDirectories = new Map;
167241
+ var CIRCUIT_BREAKER_THRESHOLD = 3;
166899
167242
  function registerDreamProjectDirectory(projectIdentity, directory) {
166900
167243
  dreamProjectDirectories.set(projectIdentity, directory);
166901
167244
  }
@@ -166912,6 +167255,25 @@ function countNewIds(beforeIds, afterIds) {
166912
167255
  }
166913
167256
  return count;
166914
167257
  }
167258
+ function getCircuitBreakerSignature(error51, brief) {
167259
+ if (error51 instanceof Error && error51.name && error51.name !== "Error") {
167260
+ return error51.name;
167261
+ }
167262
+ const namedError = error51;
167263
+ if (namedError && typeof namedError === "object" && typeof namedError.name === "string" && namedError.name.length > 0 && namedError.name !== "Error") {
167264
+ return namedError.name;
167265
+ }
167266
+ return brief.split(":")[0]?.trim().split(/\s+/)[0] || brief || "unknown";
167267
+ }
167268
+ function shouldSkipCircuitBreaker(error51, brief) {
167269
+ const namedError = error51;
167270
+ const name2 = error51 instanceof Error ? error51.name : namedError && typeof namedError === "object" && typeof namedError.name === "string" ? namedError.name : "";
167271
+ const combined = `${name2} ${brief}`.toLowerCase();
167272
+ return name2 === "AbortError" || combined.includes("lease");
167273
+ }
167274
+ function logWithStackHead(message, stackHead) {
167275
+ log(message, stackHead ? { stackHead } : undefined);
167276
+ }
166915
167277
  function getOpenCodeDbPath2() {
166916
167278
  return join13(getDataDir(), "opencode", "opencode.db");
166917
167279
  }
@@ -167017,7 +167379,12 @@ async function identifyKeyFilesForSession(args) {
167017
167379
  system: KEY_FILES_SYSTEM_PROMPT,
167018
167380
  parts: [{ type: "text", text: prompt, synthetic: true }]
167019
167381
  }
167020
- }, { timeoutMs: Math.min(remainingMs, 300000), signal: abortController.signal });
167382
+ }, {
167383
+ timeoutMs: Math.min(remainingMs, 300000),
167384
+ signal: abortController.signal,
167385
+ fallbackModels: args.fallbackModels,
167386
+ callContext: `dreamer:key-files:${args.sessionId.slice(0, 12)}`
167387
+ });
167021
167388
  const messagesResponse = await args.client.session.messages({
167022
167389
  path: { id: agentSessionId },
167023
167390
  query: { directory: args.sessionDirectory }
@@ -167090,6 +167457,7 @@ async function identifyKeyFiles(args) {
167090
167457
  parentSessionId: args.parentSessionId,
167091
167458
  sessionDirectory: args.sessionDirectory,
167092
167459
  holderId: args.holderId,
167460
+ fallbackModels: args.fallbackModels,
167093
167461
  deadline: args.deadline,
167094
167462
  sessionId,
167095
167463
  config: args.config
@@ -167143,6 +167511,9 @@ async function runDream(args) {
167143
167511
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
167144
167512
  const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`) ?? getDreamState(args.db, "last_dream_at");
167145
167513
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
167514
+ let lastErrorSignature = null;
167515
+ let consecutiveSameErrorFailures = 0;
167516
+ let circuitBreakerTripped = false;
167146
167517
  try {
167147
167518
  for (const taskName of args.tasks) {
167148
167519
  if (Date.now() > deadline) {
@@ -167203,7 +167574,9 @@ async function runDream(args) {
167203
167574
  }
167204
167575
  }, {
167205
167576
  timeoutMs: args.taskTimeoutMinutes * 60 * 1000,
167206
- signal: taskAbortController.signal
167577
+ signal: taskAbortController.signal,
167578
+ fallbackModels: args.fallbackModels,
167579
+ callContext: `dreamer:${taskName}`
167207
167580
  });
167208
167581
  const messagesResponse = await args.client.session.messages({
167209
167582
  path: { id: agentSessionId },
@@ -167223,16 +167596,40 @@ async function runDream(args) {
167223
167596
  durationMs,
167224
167597
  result: taskResult
167225
167598
  });
167599
+ lastErrorSignature = null;
167600
+ consecutiveSameErrorFailures = 0;
167226
167601
  } catch (error51) {
167227
167602
  const durationMs = Date.now() - taskStartedAt;
167228
- const errorMsg = getErrorMessage(error51);
167229
- log(`[dreamer] task ${taskName}: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorMsg}`);
167603
+ const errorDescription = describeError(error51);
167604
+ logWithStackHead(`[dreamer] task ${taskName}: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorDescription.brief}`, errorDescription.stackHead);
167230
167605
  result.tasks.push({
167231
167606
  name: taskName,
167232
167607
  durationMs,
167233
167608
  result: null,
167234
- error: errorMsg
167609
+ error: errorDescription.brief
167235
167610
  });
167611
+ if (shouldSkipCircuitBreaker(error51, errorDescription.brief)) {
167612
+ lastErrorSignature = null;
167613
+ consecutiveSameErrorFailures = 0;
167614
+ } else {
167615
+ const signature = getCircuitBreakerSignature(error51, errorDescription.brief);
167616
+ if (signature === lastErrorSignature) {
167617
+ consecutiveSameErrorFailures += 1;
167618
+ } else {
167619
+ lastErrorSignature = signature;
167620
+ consecutiveSameErrorFailures = 1;
167621
+ }
167622
+ if (consecutiveSameErrorFailures >= CIRCUIT_BREAKER_THRESHOLD) {
167623
+ circuitBreakerTripped = true;
167624
+ log(`[dreamer] circuit breaker: ${consecutiveSameErrorFailures} consecutive ${signature} failures — aborting remaining tasks`);
167625
+ result.tasks.push({
167626
+ name: "circuit-breaker",
167627
+ durationMs: 0,
167628
+ result: "",
167629
+ error: `Aborted remaining tasks: ${consecutiveSameErrorFailures} consecutive ${signature} failures. Configure dreamer model/fallback_models in magic-context.jsonc.`
167630
+ });
167631
+ }
167632
+ }
167236
167633
  } finally {
167237
167634
  clearInterval(leaseRenewalInterval);
167238
167635
  if (agentSessionId) {
@@ -167244,8 +167641,20 @@ async function runDream(args) {
167244
167641
  });
167245
167642
  }
167246
167643
  }
167644
+ if (circuitBreakerTripped) {
167645
+ break;
167646
+ }
167247
167647
  }
167248
- if (args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
167648
+ if (circuitBreakerTripped) {
167649
+ log("[dreamer] circuit breaker: skipping post-task phases");
167650
+ result.tasks.push({
167651
+ name: "post-task-phases",
167652
+ durationMs: 0,
167653
+ result: "",
167654
+ error: "Skipped post-task phases after circuit breaker tripped; configure dreamer model/fallback_models in magic-context.jsonc."
167655
+ });
167656
+ }
167657
+ if (!circuitBreakerTripped && args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
167249
167658
  const umStart = Date.now();
167250
167659
  try {
167251
167660
  const reviewResult = await reviewUserMemories({
@@ -167255,7 +167664,8 @@ async function runDream(args) {
167255
167664
  sessionDirectory: args.sessionDirectory,
167256
167665
  holderId,
167257
167666
  deadline,
167258
- promotionThreshold: args.experimentalUserMemories.promotionThreshold
167667
+ promotionThreshold: args.experimentalUserMemories.promotionThreshold,
167668
+ fallbackModels: args.fallbackModels
167259
167669
  });
167260
167670
  const umOutput = `promoted=${reviewResult.promoted} merged=${reviewResult.merged} dismissed=${reviewResult.dismissed} consumed=${reviewResult.candidatesConsumed}`;
167261
167671
  if (reviewResult.promoted > 0 || reviewResult.merged > 0 || reviewResult.dismissed > 0) {
@@ -167267,16 +167677,17 @@ async function runDream(args) {
167267
167677
  result: umOutput
167268
167678
  });
167269
167679
  } catch (error51) {
167270
- log(`[dreamer] user-memory review failed: ${getErrorMessage(error51)}`);
167680
+ const errorDescription = describeError(error51);
167681
+ logWithStackHead(`[dreamer] user-memory review failed: ${errorDescription.brief}`, errorDescription.stackHead);
167271
167682
  result.tasks.push({
167272
167683
  name: "user memories",
167273
167684
  durationMs: Date.now() - umStart,
167274
167685
  result: "",
167275
- error: getErrorMessage(error51)
167686
+ error: errorDescription.brief
167276
167687
  });
167277
167688
  }
167278
167689
  }
167279
- if (Date.now() <= deadline) {
167690
+ if (!circuitBreakerTripped && Date.now() <= deadline) {
167280
167691
  try {
167281
167692
  await evaluateSmartNotes({
167282
167693
  db: args.db,
@@ -167286,13 +167697,15 @@ async function runDream(args) {
167286
167697
  sessionDirectory: args.sessionDirectory,
167287
167698
  holderId,
167288
167699
  deadline,
167289
- result
167700
+ result,
167701
+ fallbackModels: args.fallbackModels
167290
167702
  });
167291
167703
  } catch (error51) {
167292
- log(`[dreamer] smart note evaluation failed: ${getErrorMessage(error51)}`);
167704
+ const errorDescription = describeError(error51);
167705
+ logWithStackHead(`[dreamer] smart note evaluation failed: ${errorDescription.brief}`, errorDescription.stackHead);
167293
167706
  }
167294
167707
  }
167295
- if (args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
167708
+ if (!circuitBreakerTripped && args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
167296
167709
  const kfStart = Date.now();
167297
167710
  try {
167298
167711
  await identifyKeyFiles({
@@ -167303,7 +167716,8 @@ async function runDream(args) {
167303
167716
  sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
167304
167717
  holderId,
167305
167718
  deadline,
167306
- config: args.experimentalPinKeyFiles
167719
+ config: args.experimentalPinKeyFiles,
167720
+ fallbackModels: args.fallbackModels
167307
167721
  });
167308
167722
  result.tasks.push({
167309
167723
  name: "key files",
@@ -167311,12 +167725,13 @@ async function runDream(args) {
167311
167725
  result: "completed"
167312
167726
  });
167313
167727
  } catch (error51) {
167314
- log(`[key-files] identification phase failed: ${getErrorMessage(error51)}`);
167728
+ const errorDescription = describeError(error51);
167729
+ logWithStackHead(`[key-files] identification phase failed: ${errorDescription.brief}`, errorDescription.stackHead);
167315
167730
  result.tasks.push({
167316
167731
  name: "key files",
167317
167732
  durationMs: Date.now() - kfStart,
167318
167733
  result: "",
167319
- error: getErrorMessage(error51)
167734
+ error: errorDescription.brief
167320
167735
  });
167321
167736
  }
167322
167737
  }
@@ -167351,7 +167766,13 @@ async function runDream(args) {
167351
167766
  smartNotesPending: result.smartNotesPending,
167352
167767
  memoryChanges: persistedMemoryChanges
167353
167768
  });
167354
- const POST_TASK_NAMES = new Set(["smart-notes", "user memories", "key files"]);
167769
+ const POST_TASK_NAMES = new Set([
167770
+ "smart-notes",
167771
+ "user memories",
167772
+ "key files",
167773
+ "post-task-phases",
167774
+ "circuit-breaker"
167775
+ ]);
167355
167776
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
167356
167777
  if (hasSuccessfulTask) {
167357
167778
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
@@ -167430,7 +167851,12 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167430
167851
  system: DREAMER_SYSTEM_PROMPT,
167431
167852
  parts: [{ type: "text", text: evaluationPrompt, synthetic: true }]
167432
167853
  }
167433
- }, { timeoutMs: Math.min(remainingMs, 300000), signal: abortController.signal });
167854
+ }, {
167855
+ timeoutMs: Math.min(remainingMs, 300000),
167856
+ signal: abortController.signal,
167857
+ fallbackModels: args.fallbackModels,
167858
+ callContext: "dreamer:smart-notes"
167859
+ });
167434
167860
  const messagesResponse = await args.client.session.messages({
167435
167861
  path: { id: agentSessionId },
167436
167862
  query: { directory: args.sessionDirectory ?? args.projectIdentity }
@@ -167489,15 +167915,15 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167489
167915
  });
167490
167916
  } catch (error51) {
167491
167917
  const durationMs = Date.now() - taskStartedAt;
167492
- const errorMsg = getErrorMessage(error51);
167918
+ const errorDescription = describeError(error51);
167493
167919
  args.result.smartNotesSurfaced = 0;
167494
167920
  args.result.smartNotesPending = pendingNotes.length;
167495
- log(`[dreamer] smart notes: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorMsg}`);
167921
+ logWithStackHead(`[dreamer] smart notes: failed after ${(durationMs / 1000).toFixed(1)}s — ${errorDescription.brief}`, errorDescription.stackHead);
167496
167922
  args.result.tasks.push({
167497
167923
  name: "smart-notes",
167498
167924
  durationMs,
167499
167925
  result: null,
167500
- error: errorMsg
167926
+ error: errorDescription.brief
167501
167927
  });
167502
167928
  } finally {
167503
167929
  clearInterval(leaseInterval);
@@ -167530,7 +167956,8 @@ async function processDreamQueue(args) {
167530
167956
  maxRuntimeMinutes: args.maxRuntimeMinutes,
167531
167957
  sessionDirectory: projectDirectory,
167532
167958
  experimentalUserMemories: args.experimentalUserMemories,
167533
- experimentalPinKeyFiles: args.experimentalPinKeyFiles
167959
+ experimentalPinKeyFiles: args.experimentalPinKeyFiles,
167960
+ fallbackModels: args.fallbackModels
167534
167961
  });
167535
167962
  } catch (error51) {
167536
167963
  log(`[dreamer] runDream threw for ${entry.projectIdentity}: ${getErrorMessage(error51)}`);
@@ -168138,6 +168565,7 @@ function searchGitCommitsSync(db, projectPath, query, options) {
168138
168565
  init_embedding();
168139
168566
  init_project_identity();
168140
168567
  init_logger();
168568
+ init_resolve_fallbacks();
168141
168569
  await init_storage();
168142
168570
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
168143
168571
  var activeTimer = null;
@@ -168224,7 +168652,8 @@ async function sweepProject(reg, origin) {
168224
168652
  maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
168225
168653
  experimentalUserMemories: reg.experimentalUserMemories,
168226
168654
  experimentalPinKeyFiles: reg.experimentalPinKeyFiles,
168227
- projectIdentity: registrationIdentity
168655
+ projectIdentity: registrationIdentity,
168656
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, reg.dreamerConfig.fallback_models)
168228
168657
  });
168229
168658
  } catch (error51) {
168230
168659
  log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error51);
@@ -168752,6 +169181,7 @@ function createTagger() {
168752
169181
  init_magic_context();
168753
169182
  init_project_identity();
168754
169183
  init_logger();
169184
+ init_resolve_fallbacks();
168755
169185
  await init_storage();
168756
169186
 
168757
169187
  // src/hooks/magic-context/command-handler.ts
@@ -169232,7 +169662,8 @@ Dreaming is not configured for this project.`, {});
169232
169662
  maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes,
169233
169663
  experimentalUserMemories: deps.dreamer.experimentalUserMemories,
169234
169664
  experimentalPinKeyFiles: deps.dreamer.experimentalPinKeyFiles,
169235
- projectIdentity: deps.dreamer.projectPath
169665
+ projectIdentity: deps.dreamer.projectPath,
169666
+ fallbackModels: deps.dreamer.fallbackModels
169236
169667
  });
169237
169668
  await deps.sendNotification(sessionId, result ? summarizeDreamResult(result) : "Dream queued, but another worker is already processing the queue.", {});
169238
169669
  throwSentinel("CTX-DREAM");
@@ -169528,113 +169959,8 @@ function clearSessionTracking(sessionId) {
169528
169959
  }
169529
169960
  }
169530
169961
 
169531
- // src/features/magic-context/overflow-detection.ts
169532
- var OVERFLOW_PATTERNS = [
169533
- /prompt is too long/i,
169534
- /input is too long for requested model/i,
169535
- /exceeds the context window/i,
169536
- /input token count.*exceeds the maximum/i,
169537
- /maximum prompt length is \d+/i,
169538
- /reduce the length of the messages/i,
169539
- /maximum context length is \d+ tokens/i,
169540
- /exceeds the limit of \d+/i,
169541
- /exceeds the available context size/i,
169542
- /greater than the context length/i,
169543
- /context window exceeds limit/i,
169544
- /exceeded model token limit/i,
169545
- /context[_ ]length[_ ]exceeded/i,
169546
- /request entity too large/i,
169547
- /context length is only \d+ tokens/i,
169548
- /input length.*exceeds.*context length/i,
169549
- /prompt too long; exceeded (?:max )?context length/i,
169550
- /too large for model with \d+ maximum context length/i,
169551
- /model_context_window_exceeded/i,
169552
- /context size has been exceeded/i
169553
- ];
169554
- var LIMIT_EXTRACTION_PATTERNS = [
169555
- /maximum prompt length is (\d+)/i,
169556
- /maximum context length is (\d+) tokens?/i,
169557
- /context length is only (\d+) tokens?/i,
169558
- /exceeds the limit of (\d+)/i,
169559
- /too large for model with (\d+) maximum context length/i,
169560
- /context size.*(\d+) tokens?/i,
169561
- /exceeds? the context length of (\d+)/i,
169562
- /max(?:imum)?.*context.*?(\d+)/i
169563
- ];
169564
- var MIN_PLAUSIBLE_LIMIT = 1024;
169565
- var MAX_PLAUSIBLE_LIMIT = 1e7;
169566
- function extractErrorMessage(error51) {
169567
- if (!error51)
169568
- return "";
169569
- if (typeof error51 === "string")
169570
- return error51;
169571
- if (typeof error51 === "object") {
169572
- const obj = error51;
169573
- const nested = obj.error;
169574
- if (nested && typeof nested.message === "string" && nested.message.length > 0) {
169575
- return nested.message;
169576
- }
169577
- }
169578
- if (error51 instanceof Error)
169579
- return error51.message;
169580
- if (typeof error51 === "object") {
169581
- const obj = error51;
169582
- if (typeof obj.message === "string")
169583
- return obj.message;
169584
- if (typeof obj.responseBody === "string")
169585
- return obj.responseBody;
169586
- try {
169587
- return JSON.stringify(error51);
169588
- } catch {
169589
- return String(error51);
169590
- }
169591
- }
169592
- return String(error51);
169593
- }
169594
- function detectOverflow(error51) {
169595
- const message = extractErrorMessage(error51);
169596
- if (!message) {
169597
- return { isOverflow: false };
169598
- }
169599
- const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
169600
- let matched;
169601
- for (const pattern of OVERFLOW_PATTERNS) {
169602
- if (pattern.test(message)) {
169603
- matched = pattern;
169604
- break;
169605
- }
169606
- }
169607
- if (!matched && !hasStatus413) {
169608
- return { isOverflow: false };
169609
- }
169610
- const reportedLimit = parseReportedLimit(message);
169611
- return {
169612
- isOverflow: true,
169613
- reportedLimit,
169614
- matchedPattern: matched?.source
169615
- };
169616
- }
169617
- function parseReportedLimit(message) {
169618
- if (!message)
169619
- return;
169620
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
169621
- const match = message.match(pattern);
169622
- if (!match)
169623
- continue;
169624
- const raw = match[1];
169625
- if (!raw)
169626
- continue;
169627
- const value = Number.parseInt(raw, 10);
169628
- if (!Number.isFinite(value))
169629
- continue;
169630
- if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
169631
- continue;
169632
- return value;
169633
- }
169634
- return;
169635
- }
169636
-
169637
169962
  // src/hooks/magic-context/event-handler.ts
169963
+ init_overflow_detection();
169638
169964
  init_storage_meta_persisted();
169639
169965
  init_logger();
169640
169966
  await __promiseAll([
@@ -170548,6 +170874,7 @@ async function runCompartmentPhase(args) {
170548
170874
  historianChunkTokens: args.historianChunkTokens,
170549
170875
  historyBudgetTokens: args.historyBudgetTokens,
170550
170876
  historianTimeoutMs: args.historianTimeoutMs,
170877
+ fallbackModels: args.fallbackModels,
170551
170878
  directory: args.compartmentDirectory,
170552
170879
  fallbackModelId: args.fallbackModelId,
170553
170880
  getNotificationParams: args.getNotificationParams,
@@ -170575,6 +170902,7 @@ async function runCompartmentPhase(args) {
170575
170902
  historianChunkTokens: args.historianChunkTokens,
170576
170903
  historyBudgetTokens: args.historyBudgetTokens,
170577
170904
  historianTimeoutMs: args.historianTimeoutMs,
170905
+ fallbackModels: args.fallbackModels,
170578
170906
  directory: args.compartmentDirectory,
170579
170907
  fallbackModelId: args.fallbackModelId,
170580
170908
  getNotificationParams: args.getNotificationParams,
@@ -170614,6 +170942,7 @@ async function runCompartmentPhase(args) {
170614
170942
  directory: args.compartmentDirectory,
170615
170943
  historyBudgetTokens: args.historyBudgetTokens,
170616
170944
  historianTimeoutMs: args.historianTimeoutMs,
170945
+ fallbackModels: args.fallbackModels,
170617
170946
  minCompartmentRatio: args.compressorMinCompartmentRatio,
170618
170947
  maxMergeDepth: args.compressorMaxMergeDepth
170619
170948
  }).then((compressed) => {
@@ -170711,6 +171040,46 @@ function countMessagesSinceLastUser(messages) {
170711
171040
  }
170712
171041
  return messagesSinceLastUser;
170713
171042
  }
171043
+ function injectToolPartIntoLatestAssistant(messages, part) {
171044
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
171045
+ const message = messages[index];
171046
+ if (message.info.role !== "assistant")
171047
+ continue;
171048
+ if (typeof message.info.id !== "string")
171049
+ continue;
171050
+ if (hasToolPartWithCallId(message, part.callID)) {
171051
+ return message.info.id;
171052
+ }
171053
+ message.parts.push(part);
171054
+ return message.info.id;
171055
+ }
171056
+ return null;
171057
+ }
171058
+ function injectToolPartIntoAssistantById(messages, messageId, part) {
171059
+ for (const message of messages) {
171060
+ if (message.info.id !== messageId)
171061
+ continue;
171062
+ if (message.info.role !== "assistant")
171063
+ continue;
171064
+ if (hasToolPartWithCallId(message, part.callID))
171065
+ return true;
171066
+ message.parts.push(part);
171067
+ return true;
171068
+ }
171069
+ return false;
171070
+ }
171071
+ function hasToolPartWithCallId(message, callId) {
171072
+ for (const part of message.parts) {
171073
+ if (part === null || typeof part !== "object")
171074
+ continue;
171075
+ const p = part;
171076
+ if (p.type !== "tool")
171077
+ continue;
171078
+ if (p.callID === callId)
171079
+ return true;
171080
+ }
171081
+ return false;
171082
+ }
170714
171083
  function appendReminderToUserMessage(message, reminder) {
170715
171084
  for (const part of message.parts) {
170716
171085
  if (!isTextPart(part)) {
@@ -172140,7 +172509,7 @@ function extractToolInfo(part) {
172140
172509
  return null;
172141
172510
  }
172142
172511
  function buildToolFingerprints(messages) {
172143
- const fingerprints = new Map;
172512
+ const fingerprints2 = new Map;
172144
172513
  for (const message of messages) {
172145
172514
  if (message.info.role !== "assistant")
172146
172515
  continue;
@@ -172158,11 +172527,11 @@ function buildToolFingerprints(messages) {
172158
172527
  try {
172159
172528
  const fingerprint = `${ownerMsgId}:${info.toolName}:${JSON.stringify(info.args)}`;
172160
172529
  const compositeKey = `${ownerMsgId}\x00${callId}`;
172161
- fingerprints.set(compositeKey, fingerprint);
172530
+ fingerprints2.set(compositeKey, fingerprint);
172162
172531
  } catch {}
172163
172532
  }
172164
172533
  }
172165
- return fingerprints;
172534
+ return fingerprints2;
172166
172535
  }
172167
172536
  function extractCallId(part) {
172168
172537
  if (part.type === "tool" && typeof part.callID === "string")
@@ -172220,6 +172589,84 @@ function isVisibleNoteReadPart(part) {
172220
172589
  return false;
172221
172590
  }
172222
172591
 
172592
+ // src/hooks/magic-context/todo-view.ts
172593
+ import { createHash as createHash4 } from "node:crypto";
172594
+ var TERMINAL_STATUSES = new Set(["completed", "cancelled"]);
172595
+ var TITLE_DONE_STATUSES = new Set(["completed"]);
172596
+ var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
172597
+ function normalizeTodoStateJson(todos) {
172598
+ if (!Array.isArray(todos))
172599
+ return null;
172600
+ const normalized = [];
172601
+ for (const todo of todos) {
172602
+ if (!isTodoItem(todo))
172603
+ return null;
172604
+ normalized.push({
172605
+ content: todo.content,
172606
+ status: todo.status,
172607
+ priority: todo.priority
172608
+ });
172609
+ }
172610
+ return JSON.stringify(normalized);
172611
+ }
172612
+ function buildSyntheticTodoPart(stateJson) {
172613
+ const todos = parseTodoState(stateJson);
172614
+ if (todos === null || todos.length === 0)
172615
+ return null;
172616
+ if (todos.every((t) => TERMINAL_STATUSES.has(t.status)))
172617
+ return null;
172618
+ const callID = computeSyntheticCallId(stateJson);
172619
+ const activeCount = todos.filter((t) => !TITLE_DONE_STATUSES.has(t.status)).length;
172620
+ const output = JSON.stringify(todos, null, 2);
172621
+ const ts = 0;
172622
+ return {
172623
+ type: "tool",
172624
+ callID,
172625
+ tool: "todowrite",
172626
+ state: {
172627
+ status: "completed",
172628
+ input: { todos },
172629
+ output,
172630
+ title: `${activeCount} todos`,
172631
+ metadata: { todos, truncated: false },
172632
+ time: { start: ts, end: ts }
172633
+ },
172634
+ syntheticTodoMarker: true
172635
+ };
172636
+ }
172637
+ function computeSyntheticCallId(stateJson) {
172638
+ const hash2 = createHash4("sha256").update(stateJson).digest("hex").slice(0, 16);
172639
+ return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
172640
+ }
172641
+ function parseTodoState(stateJson) {
172642
+ if (stateJson.length === 0)
172643
+ return null;
172644
+ try {
172645
+ const parsed = JSON.parse(stateJson);
172646
+ if (!Array.isArray(parsed))
172647
+ return null;
172648
+ const result = [];
172649
+ for (const item of parsed) {
172650
+ if (!isTodoItem(item))
172651
+ continue;
172652
+ result.push({
172653
+ content: item.content,
172654
+ status: item.status,
172655
+ priority: item.priority
172656
+ });
172657
+ }
172658
+ return result;
172659
+ } catch {
172660
+ return null;
172661
+ }
172662
+ }
172663
+ function isTodoItem(value) {
172664
+ if (value === null || typeof value !== "object")
172665
+ return false;
172666
+ const todo = value;
172667
+ return typeof todo.content === "string" && typeof todo.status === "string" && typeof todo.priority === "string";
172668
+ }
172669
+
172223
172670
  // src/hooks/magic-context/transform-stage-logger.ts
172224
172671
  init_logger();
172225
172672
  function logTransformTiming(sessionId, stage, startMs, extra) {
@@ -172461,6 +172908,33 @@ async function runPostTransformPhase(args) {
172461
172908
  const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
172462
172909
  markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
172463
172910
  }
172911
+ if (args.fullFeatureMode) {
172912
+ const persistedAnchor = getPersistedTodoSyntheticAnchor(args.db, args.sessionId);
172913
+ if (isCacheBustingPass) {
172914
+ const part = buildSyntheticTodoPart(args.sessionMeta.lastTodoState);
172915
+ if (part === null) {
172916
+ if (persistedAnchor) {
172917
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
172918
+ }
172919
+ } else if (persistedAnchor && persistedAnchor.callId === part.callID && injectToolPartIntoAssistantById(args.messages, persistedAnchor.messageId, part)) {
172920
+ if (persistedAnchor.stateJson.length === 0) {
172921
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, persistedAnchor.callId, persistedAnchor.messageId, args.sessionMeta.lastTodoState);
172922
+ }
172923
+ } else {
172924
+ const anchoredMessageId = injectToolPartIntoLatestAssistant(args.messages, part);
172925
+ if (anchoredMessageId) {
172926
+ setPersistedTodoSyntheticAnchor(args.db, args.sessionId, part.callID, anchoredMessageId, args.sessionMeta.lastTodoState);
172927
+ } else if (persistedAnchor) {
172928
+ clearPersistedTodoSyntheticAnchor(args.db, args.sessionId);
172929
+ }
172930
+ }
172931
+ } else if (persistedAnchor && persistedAnchor.stateJson.length > 0) {
172932
+ const part = buildSyntheticTodoPart(persistedAnchor.stateJson);
172933
+ if (part !== null && part.callID === persistedAnchor.callId) {
172934
+ injectToolPartIntoAssistantById(args.messages, persistedAnchor.messageId, part);
172935
+ }
172936
+ }
172937
+ }
172464
172938
  if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
172465
172939
  const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
172466
172940
  try {
@@ -172692,6 +173166,7 @@ function createTransform(deps) {
172692
173166
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172693
173167
  historyBudgetTokens,
172694
173168
  historianTimeoutMs: deps.historianTimeoutMs,
173169
+ fallbackModels: deps.fallbackModels,
172695
173170
  directory: compartmentDirectory,
172696
173171
  fallbackModelId,
172697
173172
  getNotificationParams: () => notificationParams,
@@ -172861,6 +173336,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172861
173336
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172862
173337
  historyBudgetTokens,
172863
173338
  historianTimeoutMs: deps.historianTimeoutMs,
173339
+ fallbackModels: deps.fallbackModels,
172864
173340
  compartmentDirectory,
172865
173341
  messages,
172866
173342
  pendingCompartmentInjection,
@@ -173701,9 +174177,17 @@ function createToolExecuteAfterHook(args) {
173701
174177
  if (typedInput.tool === "todowrite") {
173702
174178
  const todoArgs = typedInput.args;
173703
174179
  const todos = todoArgs?.todos;
173704
- if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
173705
- const sessionMeta = getOrCreateSessionMeta(args.db, typedInput.sessionID);
173706
- if (!sessionMeta.isSubagent) {
174180
+ const sessionMeta = Array.isArray(todos) ? getOrCreateSessionMeta(args.db, typedInput.sessionID) : null;
174181
+ if (sessionMeta && !sessionMeta.isSubagent) {
174182
+ const normalizedTodos = normalizeTodoStateJson(todos);
174183
+ if (normalizedTodos !== null) {
174184
+ updateSessionMeta(args.db, typedInput.sessionID, {
174185
+ lastTodoState: normalizedTodos
174186
+ });
174187
+ }
174188
+ }
174189
+ if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => typeof t === "object" && t !== null && (t.status === "completed" || t.status === "cancelled"))) {
174190
+ if (sessionMeta && !sessionMeta.isSubagent) {
173707
174191
  onNoteTrigger(args.db, typedInput.sessionID, "todos_complete");
173708
174192
  }
173709
174193
  }
@@ -173719,7 +174203,7 @@ function createToolExecuteAfterHook(args) {
173719
174203
  init_send_session_notification();
173720
174204
 
173721
174205
  // src/hooks/magic-context/system-prompt-hash.ts
173722
- import { createHash as createHash4 } from "node:crypto";
174206
+ import { createHash as createHash5 } from "node:crypto";
173723
174207
  import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync } from "node:fs";
173724
174208
  import { join as join22, resolve as resolve4, sep } from "node:path";
173725
174209
 
@@ -174024,7 +174508,7 @@ ${sections.join(`
174024
174508
  `);
174025
174509
  if (systemContent.length === 0)
174026
174510
  return;
174027
- const currentHash = createHash4("md5").update(systemContent).digest("hex");
174511
+ const currentHash = createHash5("md5").update(systemContent).digest("hex");
174028
174512
  if (!sessionMetaEarly) {
174029
174513
  return;
174030
174514
  }
@@ -174039,14 +174523,12 @@ ${sections.join(`
174039
174523
  } else if (previousHash === "" || previousHash === "0") {
174040
174524
  sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${systemContent.length})`);
174041
174525
  }
174042
- const systemPromptTokens = estimateTokens(systemContent);
174043
174526
  if (currentHash !== previousHash) {
174527
+ const systemPromptTokens = estimateTokens(systemContent);
174044
174528
  updateSessionMeta(deps.db, sessionId, {
174045
174529
  systemPromptHash: currentHash,
174046
174530
  systemPromptTokens
174047
174531
  });
174048
- } else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
174049
- updateSessionMeta(deps.db, sessionId, { systemPromptTokens });
174050
174532
  }
174051
174533
  if (isCacheBusting) {
174052
174534
  deps.systemPromptRefreshSessions.delete(sessionId);
@@ -174108,6 +174590,7 @@ function createMagicContextHook(deps) {
174108
174590
  }
174109
174591
  let lastScheduleCheckMs = 0;
174110
174592
  const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
174593
+ const historianFallbackModels = resolveFallbackChain(HISTORIAN_AGENT, deps.config.historian?.fallback_models);
174111
174594
  const nudgePlacements = createNudgePlacementStore(db);
174112
174595
  const historyRefreshSessions = deps.liveSessionState?.historyRefreshSessions ?? new Set;
174113
174596
  const systemPromptRefreshSessions = deps.liveSessionState?.systemPromptRefreshSessions ?? new Set;
@@ -174167,6 +174650,7 @@ function createMagicContextHook(deps) {
174167
174650
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
174168
174651
  executeThresholdTokens: deps.config.execute_threshold_tokens,
174169
174652
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
174653
+ fallbackModels: historianFallbackModels,
174170
174654
  getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
174171
174655
  getModelKey: (sessionId) => {
174172
174656
  const model = liveModelBySession.get(sessionId);
@@ -174244,7 +174728,8 @@ function createMagicContextHook(deps) {
174244
174728
  enabled: true,
174245
174729
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174246
174730
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174247
- } : undefined
174731
+ } : undefined,
174732
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer?.fallback_models)
174248
174733
  }).catch((error51) => {
174249
174734
  log("[dreamer] scheduled queue processing failed:", error51);
174250
174735
  });
@@ -174278,6 +174763,7 @@ function createMagicContextHook(deps) {
174278
174763
  sessionId,
174279
174764
  historianChunkTokens: getHistorianChunkTokens(),
174280
174765
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
174766
+ fallbackModels: historianFallbackModels,
174281
174767
  directory: deps.directory,
174282
174768
  fallbackModelId: (() => {
174283
174769
  const model = resolveLiveModel(sessionId);
@@ -174317,7 +174803,8 @@ function createMagicContextHook(deps) {
174317
174803
  enabled: true,
174318
174804
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174319
174805
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174320
- } : undefined
174806
+ } : undefined,
174807
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer.fallback_models)
174321
174808
  } : undefined
174322
174809
  });
174323
174810
  const systemPromptHash = createSystemPromptHashHandler({
@@ -174979,19 +175466,7 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
174979
175466
  }
174980
175467
  detail.nextNudgeAfter = detail.lastNudgeTokens + detail.nudgeInterval;
174981
175468
  try {
174982
- const compartments = db.prepare("SELECT content, title, start_message, end_message FROM compartments WHERE session_id = ?").all(sessionId);
174983
- const facts = db.prepare("SELECT content FROM session_facts WHERE session_id = ?").all(sessionId);
174984
- let histTokens = 0;
174985
- for (const c of compartments) {
174986
- histTokens += estimateTokens(`<compartment start="${c.start_message}" end="${c.end_message}" title="${c.title}">
174987
- ${c.content}
174988
- </compartment>
174989
- `);
174990
- }
174991
- for (const f of facts) {
174992
- histTokens += estimateTokens(`* ${f.content}
174993
- `);
174994
- }
175469
+ const histTokens = base.compartmentTokens + base.factTokens;
174995
175470
  detail.historyBlockTokens = histTokens;
174996
175471
  if (detail.contextLimit > 0) {
174997
175472
  const budget = Math.floor(detail.contextLimit * (Math.min(detail.executeThreshold, 80) / 100) * detail.historyBudgetPercentage);
@@ -175164,7 +175639,7 @@ function normalizeLimit2(limit) {
175164
175639
  return Math.max(1, Math.floor(limit));
175165
175640
  }
175166
175641
  function getAllowedActions(deps) {
175167
- const allowed = deps.allowedActions?.length ? deps.allowedActions : [...CTX_MEMORY_DREAMER_ACTIONS];
175642
+ const allowed = deps.allowedActions?.length ? deps.allowedActions : ["write", "delete"];
175168
175643
  return [...allowed];
175169
175644
  }
175170
175645
  function normalizeCategory(category) {
@@ -175270,7 +175745,7 @@ function createCtxMemoryTool(deps) {
175270
175745
  return tool2({
175271
175746
  description: CTX_MEMORY_DESCRIPTION,
175272
175747
  args: {
175273
- action: tool2.schema.enum(allowedActions).describe("Action to perform on memories"),
175748
+ action: tool2.schema.enum([...CTX_MEMORY_DREAMER_ACTIONS]).describe("Action to perform on memories"),
175274
175749
  content: tool2.schema.string().optional().describe("Memory content (required for write, update, merge)"),
175275
175750
  category: tool2.schema.string().optional().describe("Memory category (required for write, optional filter for list, optional override for merge)"),
175276
175751
  id: tool2.schema.number().optional().describe("Memory ID (required for delete, update, archive)"),
@@ -176036,11 +176511,11 @@ import { createServer } from "node:http";
176036
176511
  import { dirname as dirname6 } from "node:path";
176037
176512
 
176038
176513
  // src/shared/rpc-utils.ts
176039
- import { createHash as createHash5 } from "node:crypto";
176514
+ import { createHash as createHash6 } from "node:crypto";
176040
176515
  import { join as join23 } from "node:path";
176041
176516
  function projectHash(directory) {
176042
176517
  const normalized = directory.replace(/\/+$/, "");
176043
- return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
176518
+ return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
176044
176519
  }
176045
176520
  function rpcPortFilePath(storageDir, directory) {
176046
176521
  return join23(storageDir, "rpc", projectHash(directory), "port");