@cortexkit/opencode-magic-context 0.17.2 → 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 (63) 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/types.d.ts +1 -0
  15. package/dist/features/magic-context/types.d.ts.map +1 -1
  16. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +2 -0
  17. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  18. package/dist/hooks/magic-context/command-handler.d.ts +2 -0
  19. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  20. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +1 -0
  21. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +7 -0
  23. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/compartment-runner-types.d.ts +2 -0
  28. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/todo-view.d.ts +102 -0
  33. package/dist/hooks/magic-context/todo-view.d.ts.map +1 -0
  34. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +1 -0
  35. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/transform-message-helpers.d.ts +22 -0
  37. package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/transform.d.ts +2 -0
  40. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  41. package/dist/index.js +626 -178
  42. package/dist/plugin/dream-timer.d.ts.map +1 -1
  43. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  44. package/dist/plugin/rpc-handlers.d.ts +2 -1
  45. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  46. package/dist/shared/index.d.ts +1 -0
  47. package/dist/shared/index.d.ts.map +1 -1
  48. package/dist/shared/model-suggestion-retry.d.ts +37 -0
  49. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  50. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  51. package/dist/shared/resolve-fallbacks.d.ts +32 -0
  52. package/dist/shared/resolve-fallbacks.d.ts.map +1 -0
  53. package/dist/shared/tag-transcript.d.ts.map +1 -1
  54. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/shared/index.ts +1 -0
  57. package/src/shared/model-suggestion-retry.test.ts +251 -0
  58. package/src/shared/model-suggestion-retry.ts +194 -6
  59. package/src/shared/models-dev-cache.ts +7 -7
  60. package/src/shared/resolve-fallbacks.test.ts +136 -0
  61. package/src/shared/resolve-fallbacks.ts +76 -0
  62. package/src/shared/tag-transcript.ts +3 -2
  63. 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
@@ -160034,6 +160264,25 @@ var init_migrations = __esm(async () => {
160034
160264
  WHERE type = 'tool' AND tool_owner_message_id IS NULL;
160035
160265
  `);
160036
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
+ }
160037
160286
  }
160038
160287
  ];
160039
160288
  });
@@ -160516,6 +160765,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160516
160765
  note_nudge_trigger_message_id TEXT DEFAULT '',
160517
160766
  note_nudge_sticky_text TEXT DEFAULT '',
160518
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 '',
160519
160772
  is_subagent INTEGER DEFAULT 0,
160520
160773
  last_context_percentage REAL DEFAULT 0,
160521
160774
  last_input_tokens INTEGER DEFAULT 0,
@@ -160580,6 +160833,10 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
160580
160833
  ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
160581
160834
  ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
160582
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 ''");
160583
160840
  ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
160584
160841
  ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
160585
160842
  ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
@@ -160644,6 +160901,10 @@ function healNullTextColumns(db) {
160644
160901
  ["note_nudge_trigger_message_id", ""],
160645
160902
  ["note_nudge_sticky_text", ""],
160646
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", ""],
160647
160908
  ["system_prompt_hash", ""],
160648
160909
  ["stripped_placeholder_ids", ""],
160649
160910
  ["memory_block_cache", ""],
@@ -160749,7 +161010,7 @@ function isSessionMetaRow(row) {
160749
161010
  if (row === null || typeof row !== "object")
160750
161011
  return false;
160751
161012
  const r = row;
160752
- 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);
160753
161014
  }
160754
161015
  function getDefaultSessionMeta(sessionId) {
160755
161016
  return {
@@ -160769,7 +161030,8 @@ function getDefaultSessionMeta(sessionId) {
160769
161030
  systemPromptTokens: 0,
160770
161031
  conversationTokens: 0,
160771
161032
  toolCallTokens: 0,
160772
- clearedReasoningThroughTag: 0
161033
+ clearedReasoningThroughTag: 0,
161034
+ lastTodoState: ""
160773
161035
  };
160774
161036
  }
160775
161037
  function ensureSessionMetaRow(db, sessionId) {
@@ -160781,6 +161043,7 @@ function toSessionMeta(row) {
160781
161043
  const transformErrorRaw = typeof row.last_transform_error === "string" ? row.last_transform_error : "";
160782
161044
  const cacheTtlRaw = typeof row.cache_ttl === "string" && row.cache_ttl.length > 0 ? row.cache_ttl : "5m";
160783
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 : "";
160784
161047
  const numOrZero = (value) => typeof value === "number" ? value : 0;
160785
161048
  return {
160786
161049
  sessionId: row.session_id,
@@ -160799,7 +161062,8 @@ function toSessionMeta(row) {
160799
161062
  systemPromptTokens: numOrZero(row.system_prompt_tokens),
160800
161063
  conversationTokens: numOrZero(row.conversation_tokens),
160801
161064
  toolCallTokens: numOrZero(row.tool_call_tokens),
160802
- clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag)
161065
+ clearedReasoningThroughTag: numOrZero(row.cleared_reasoning_through_tag),
161066
+ lastTodoState: lastTodoStateRaw
160803
161067
  };
160804
161068
  }
160805
161069
  var META_COLUMNS, BOOLEAN_META_KEYS;
@@ -160820,7 +161084,8 @@ var init_storage_meta_shared = __esm(() => {
160820
161084
  systemPromptTokens: "system_prompt_tokens",
160821
161085
  conversationTokens: "conversation_tokens",
160822
161086
  toolCallTokens: "tool_call_tokens",
160823
- clearedReasoningThroughTag: "cleared_reasoning_through_tag"
161087
+ clearedReasoningThroughTag: "cleared_reasoning_through_tag",
161088
+ lastTodoState: "last_todo_state"
160824
161089
  };
160825
161090
  BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
160826
161091
  });
@@ -160856,6 +161121,12 @@ function isPersistedNoteNudgeRow(row) {
160856
161121
  const r = row;
160857
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";
160858
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
+ }
160859
161130
  function isPersistedHistorianFailureRow(row) {
160860
161131
  if (row === null || typeof row !== "object")
160861
161132
  return false;
@@ -160978,6 +161249,29 @@ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
160978
161249
  function clearPersistedNoteNudge(db, sessionId) {
160979
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);
160980
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
+ }
160981
161275
  function getNoteLastReadAt(db, sessionId) {
160982
161276
  try {
160983
161277
  const result = db.prepare("SELECT note_last_read_at FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -161130,7 +161424,7 @@ var init_resolve_subagent_fallback = __esm(async () => {
161130
161424
 
161131
161425
  // src/features/magic-context/storage-meta-session.ts
161132
161426
  function getOrCreateSessionMeta(db, sessionId) {
161133
- 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);
161134
161428
  if (isSessionMetaRow(result)) {
161135
161429
  return toSessionMeta(result);
161136
161430
  }
@@ -161704,9 +161998,7 @@ function loadModelsDevMetadataFromFile() {
161704
161998
  try {
161705
161999
  const configPath = getOpencodeConfigPath();
161706
162000
  if (configPath && existsSync12(configPath)) {
161707
- let raw = readFileSync8(configPath, "utf-8");
161708
- raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
161709
- const config2 = JSON.parse(raw);
162001
+ const config2 = parseJsonc(readFileSync8(configPath, "utf-8"));
161710
162002
  if (config2.provider && typeof config2.provider === "object") {
161711
162003
  for (const [providerId, provider2] of Object.entries(config2.provider)) {
161712
162004
  if (!provider2?.models || typeof provider2.models !== "object")
@@ -161778,6 +162070,7 @@ function getModelsDevContextLimit(providerID, modelID) {
161778
162070
  var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, recentlySeenApiSizes, oscillationLogged = false, fileCache = null, fileLastAttempt = 0;
161779
162071
  var init_models_dev_cache = __esm(() => {
161780
162072
  init_data_path();
162073
+ init_jsonc_parser();
161781
162074
  init_logger();
161782
162075
  RELOAD_INTERVAL_MS = 5 * 60 * 1000;
161783
162076
  recentlySeenApiSizes = new Set;
@@ -162125,7 +162418,8 @@ async function runHistorianPrompt(args) {
162125
162418
  timeoutMs,
162126
162419
  dumpLabel,
162127
162420
  modelOverride,
162128
- agentId = HISTORIAN_AGENT
162421
+ agentId = HISTORIAN_AGENT,
162422
+ fallbackModels
162129
162423
  } = args;
162130
162424
  let agentSessionId = null;
162131
162425
  try {
@@ -162152,7 +162446,11 @@ async function runHistorianPrompt(args) {
162152
162446
  ...modelOverride ? { model: modelOverride } : {},
162153
162447
  parts: [{ type: "text", text: prompt, synthetic: true }]
162154
162448
  }
162155
- }, { 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
+ });
162156
162454
  sessionLog(parentSessionId, `historian: prompt completed (attempt ${retryIndex + 1}/${MAX_HISTORIAN_RETRIES + 1})`);
162157
162455
  break;
162158
162456
  } catch (error51) {
@@ -163117,6 +163415,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
163117
163415
  dumpLabelBase: `partial-recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
163118
163416
  timeoutMs: historianTimeoutMs,
163119
163417
  fallbackModelId: deps.fallbackModelId,
163418
+ fallbackModels: deps.fallbackModels,
163120
163419
  twoPass: deps.historianTwoPass,
163121
163420
  callbacks: {
163122
163421
  onRepairRetry: async (error51) => {
@@ -164318,7 +164617,8 @@ async function runCompressorPass(args) {
164318
164617
  targetTokens,
164319
164618
  outputCount,
164320
164619
  outputDepth,
164321
- historianTimeoutMs
164620
+ historianTimeoutMs,
164621
+ fallbackModels
164322
164622
  } = args;
164323
164623
  const prompt = buildCompressorPrompt(compartments, currentTokens, targetTokens, outputDepth, outputCount);
164324
164624
  let agentSessionId = null;
@@ -164340,7 +164640,11 @@ async function runCompressorPass(args) {
164340
164640
  agent: HISTORIAN_AGENT2,
164341
164641
  parts: [{ type: "text", text: prompt, synthetic: true }]
164342
164642
  }
164343
- }, { timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS });
164643
+ }, {
164644
+ timeoutMs: historianTimeoutMs ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
164645
+ fallbackModels,
164646
+ callContext: "compressor"
164647
+ });
164344
164648
  const messagesResponse = await client.session.messages({
164345
164649
  path: { id: agentSessionId },
164346
164650
  query: { directory }
@@ -164520,6 +164824,7 @@ ${chunk.text}`, { stateFilePath });
164520
164824
  dumpLabelBase: `incremental-${sessionId}-${chunk.startIndex}-${chunk.endIndex}`,
164521
164825
  timeoutMs: historianTimeoutMs,
164522
164826
  fallbackModelId: deps.fallbackModelId,
164827
+ fallbackModels: deps.fallbackModels,
164523
164828
  twoPass: deps.historianTwoPass
164524
164829
  });
164525
164830
  if (!validatedPass.ok) {
@@ -164568,6 +164873,7 @@ No new compartments or facts were written. Check the historian model/output and
164568
164873
  directory: sessionDirectory,
164569
164874
  historyBudgetTokens: deps.historyBudgetTokens,
164570
164875
  historianTimeoutMs,
164876
+ fallbackModels: deps.fallbackModels,
164571
164877
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164572
164878
  maxMergeDepth: deps.compressorMaxMergeDepth
164573
164879
  });
@@ -164756,6 +165062,7 @@ Historian pass ${passCount + 1}, attempt ${passAttempt} started for messages ${c
164756
165062
  dumpLabelBase: `recomp-${sessionId}-${chunk.startIndex}-${chunk.endIndex}-pass-${passCount + 1}`,
164757
165063
  timeoutMs: historianTimeoutMs,
164758
165064
  fallbackModelId: deps.fallbackModelId,
165065
+ fallbackModels: deps.fallbackModels,
164759
165066
  twoPass: deps.historianTwoPass,
164760
165067
  callbacks: {
164761
165068
  onRepairRetry: async (error51) => {
@@ -164853,6 +165160,7 @@ Nothing was written.`;
164853
165160
  directory: sessionDirectory,
164854
165161
  historyBudgetTokens: deps.historyBudgetTokens,
164855
165162
  historianTimeoutMs,
165163
+ fallbackModels: deps.fallbackModels,
164856
165164
  minCompartmentRatio: deps.compressorMinCompartmentRatio,
164857
165165
  maxMergeDepth: deps.compressorMaxMergeDepth
164858
165166
  });
@@ -165722,6 +166030,7 @@ function buildDreamTaskPrompt(task, args) {
165722
166030
  init_shared();
165723
166031
  init_assistant_message_extractor();
165724
166032
  init_logger();
166033
+ init_resolve_fallbacks();
165725
166034
 
165726
166035
  // src/features/magic-context/sidekick/core.ts
165727
166036
  var SIDEKICK_SYSTEM_PROMPT = `You are Sidekick, a focused memory-retrieval subagent for an AI coding assistant.
@@ -165741,6 +166050,7 @@ function stripThinkingBlocks(text) {
165741
166050
 
165742
166051
  // src/features/magic-context/sidekick/agent.ts
165743
166052
  async function runSidekick(deps) {
166053
+ const fallbackModels = resolveFallbackChain(SIDEKICK_AGENT, deps.config.fallback_models);
165744
166054
  let agentSessionId = null;
165745
166055
  try {
165746
166056
  const createResponse = await deps.client.session.create({
@@ -165763,7 +166073,7 @@ async function runSidekick(deps) {
165763
166073
  system: deps.config.system_prompt?.trim() || deps.config.prompt?.trim() || SIDEKICK_SYSTEM_PROMPT,
165764
166074
  parts: [{ type: "text", text: deps.userMessage, synthetic: true }]
165765
166075
  }
165766
- }, { timeoutMs: deps.config.timeout_ms });
166076
+ }, { timeoutMs: deps.config.timeout_ms, fallbackModels, callContext: "sidekick" });
165767
166077
  const messagesResponse = await deps.client.session.messages({
165768
166078
  path: { id: agentSessionId },
165769
166079
  query: { directory: deps.sessionDirectory ?? deps.projectPath }
@@ -166387,7 +166697,6 @@ function createLiveSessionState() {
166387
166697
 
166388
166698
  // src/index.ts
166389
166699
  init_conflict_warning_hook();
166390
-
166391
166700
  // src/features/magic-context/dreamer/storage-dream-state.ts
166392
166701
  var getDreamStateStatements = new WeakMap;
166393
166702
  var setDreamStateStatements = new WeakMap;
@@ -166833,7 +167142,12 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166833
167142
  system: DREAMER_SYSTEM_PROMPT,
166834
167143
  parts: [{ type: "text", text: prompt, synthetic: true }]
166835
167144
  }
166836
- }, { 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
+ });
166837
167151
  const messagesResponse = await args.client.session.messages({
166838
167152
  path: { id: agentSessionId },
166839
167153
  query: { directory: args.sessionDirectory }
@@ -166892,7 +167206,8 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
166892
167206
  }
166893
167207
  return result;
166894
167208
  } catch (error51) {
166895
- 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);
166896
167211
  return result;
166897
167212
  } finally {
166898
167213
  clearInterval(leaseInterval);
@@ -166923,6 +167238,7 @@ function insertDreamRun(db, run) {
166923
167238
 
166924
167239
  // src/features/magic-context/dreamer/runner.ts
166925
167240
  var dreamProjectDirectories = new Map;
167241
+ var CIRCUIT_BREAKER_THRESHOLD = 3;
166926
167242
  function registerDreamProjectDirectory(projectIdentity, directory) {
166927
167243
  dreamProjectDirectories.set(projectIdentity, directory);
166928
167244
  }
@@ -166939,6 +167255,25 @@ function countNewIds(beforeIds, afterIds) {
166939
167255
  }
166940
167256
  return count;
166941
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
+ }
166942
167277
  function getOpenCodeDbPath2() {
166943
167278
  return join13(getDataDir(), "opencode", "opencode.db");
166944
167279
  }
@@ -167044,7 +167379,12 @@ async function identifyKeyFilesForSession(args) {
167044
167379
  system: KEY_FILES_SYSTEM_PROMPT,
167045
167380
  parts: [{ type: "text", text: prompt, synthetic: true }]
167046
167381
  }
167047
- }, { 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
+ });
167048
167388
  const messagesResponse = await args.client.session.messages({
167049
167389
  path: { id: agentSessionId },
167050
167390
  query: { directory: args.sessionDirectory }
@@ -167117,6 +167457,7 @@ async function identifyKeyFiles(args) {
167117
167457
  parentSessionId: args.parentSessionId,
167118
167458
  sessionDirectory: args.sessionDirectory,
167119
167459
  holderId: args.holderId,
167460
+ fallbackModels: args.fallbackModels,
167120
167461
  deadline: args.deadline,
167121
167462
  sessionId,
167122
167463
  config: args.config
@@ -167170,6 +167511,9 @@ async function runDream(args) {
167170
167511
  const deadline = startedAt + args.maxRuntimeMinutes * 60 * 1000;
167171
167512
  const lastDreamAt = getDreamState(args.db, `last_dream_at:${args.projectIdentity}`) ?? getDreamState(args.db, "last_dream_at");
167172
167513
  log(`[dreamer] last dream at: ${lastDreamAt ?? "never"} (project=${args.projectIdentity})`);
167514
+ let lastErrorSignature = null;
167515
+ let consecutiveSameErrorFailures = 0;
167516
+ let circuitBreakerTripped = false;
167173
167517
  try {
167174
167518
  for (const taskName of args.tasks) {
167175
167519
  if (Date.now() > deadline) {
@@ -167230,7 +167574,9 @@ async function runDream(args) {
167230
167574
  }
167231
167575
  }, {
167232
167576
  timeoutMs: args.taskTimeoutMinutes * 60 * 1000,
167233
- signal: taskAbortController.signal
167577
+ signal: taskAbortController.signal,
167578
+ fallbackModels: args.fallbackModels,
167579
+ callContext: `dreamer:${taskName}`
167234
167580
  });
167235
167581
  const messagesResponse = await args.client.session.messages({
167236
167582
  path: { id: agentSessionId },
@@ -167250,16 +167596,40 @@ async function runDream(args) {
167250
167596
  durationMs,
167251
167597
  result: taskResult
167252
167598
  });
167599
+ lastErrorSignature = null;
167600
+ consecutiveSameErrorFailures = 0;
167253
167601
  } catch (error51) {
167254
167602
  const durationMs = Date.now() - taskStartedAt;
167255
- const errorMsg = getErrorMessage(error51);
167256
- 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);
167257
167605
  result.tasks.push({
167258
167606
  name: taskName,
167259
167607
  durationMs,
167260
167608
  result: null,
167261
- error: errorMsg
167609
+ error: errorDescription.brief
167262
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
+ }
167263
167633
  } finally {
167264
167634
  clearInterval(leaseRenewalInterval);
167265
167635
  if (agentSessionId) {
@@ -167271,8 +167641,20 @@ async function runDream(args) {
167271
167641
  });
167272
167642
  }
167273
167643
  }
167644
+ if (circuitBreakerTripped) {
167645
+ break;
167646
+ }
167647
+ }
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
+ });
167274
167656
  }
167275
- if (args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
167657
+ if (!circuitBreakerTripped && args.experimentalUserMemories?.enabled && Date.now() <= deadline) {
167276
167658
  const umStart = Date.now();
167277
167659
  try {
167278
167660
  const reviewResult = await reviewUserMemories({
@@ -167282,7 +167664,8 @@ async function runDream(args) {
167282
167664
  sessionDirectory: args.sessionDirectory,
167283
167665
  holderId,
167284
167666
  deadline,
167285
- promotionThreshold: args.experimentalUserMemories.promotionThreshold
167667
+ promotionThreshold: args.experimentalUserMemories.promotionThreshold,
167668
+ fallbackModels: args.fallbackModels
167286
167669
  });
167287
167670
  const umOutput = `promoted=${reviewResult.promoted} merged=${reviewResult.merged} dismissed=${reviewResult.dismissed} consumed=${reviewResult.candidatesConsumed}`;
167288
167671
  if (reviewResult.promoted > 0 || reviewResult.merged > 0 || reviewResult.dismissed > 0) {
@@ -167294,16 +167677,17 @@ async function runDream(args) {
167294
167677
  result: umOutput
167295
167678
  });
167296
167679
  } catch (error51) {
167297
- log(`[dreamer] user-memory review failed: ${getErrorMessage(error51)}`);
167680
+ const errorDescription = describeError(error51);
167681
+ logWithStackHead(`[dreamer] user-memory review failed: ${errorDescription.brief}`, errorDescription.stackHead);
167298
167682
  result.tasks.push({
167299
167683
  name: "user memories",
167300
167684
  durationMs: Date.now() - umStart,
167301
167685
  result: "",
167302
- error: getErrorMessage(error51)
167686
+ error: errorDescription.brief
167303
167687
  });
167304
167688
  }
167305
167689
  }
167306
- if (Date.now() <= deadline) {
167690
+ if (!circuitBreakerTripped && Date.now() <= deadline) {
167307
167691
  try {
167308
167692
  await evaluateSmartNotes({
167309
167693
  db: args.db,
@@ -167313,13 +167697,15 @@ async function runDream(args) {
167313
167697
  sessionDirectory: args.sessionDirectory,
167314
167698
  holderId,
167315
167699
  deadline,
167316
- result
167700
+ result,
167701
+ fallbackModels: args.fallbackModels
167317
167702
  });
167318
167703
  } catch (error51) {
167319
- log(`[dreamer] smart note evaluation failed: ${getErrorMessage(error51)}`);
167704
+ const errorDescription = describeError(error51);
167705
+ logWithStackHead(`[dreamer] smart note evaluation failed: ${errorDescription.brief}`, errorDescription.stackHead);
167320
167706
  }
167321
167707
  }
167322
- if (args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
167708
+ if (!circuitBreakerTripped && args.experimentalPinKeyFiles?.enabled && Date.now() <= deadline) {
167323
167709
  const kfStart = Date.now();
167324
167710
  try {
167325
167711
  await identifyKeyFiles({
@@ -167330,7 +167716,8 @@ async function runDream(args) {
167330
167716
  sessionDirectory: args.sessionDirectory ?? args.projectIdentity,
167331
167717
  holderId,
167332
167718
  deadline,
167333
- config: args.experimentalPinKeyFiles
167719
+ config: args.experimentalPinKeyFiles,
167720
+ fallbackModels: args.fallbackModels
167334
167721
  });
167335
167722
  result.tasks.push({
167336
167723
  name: "key files",
@@ -167338,12 +167725,13 @@ async function runDream(args) {
167338
167725
  result: "completed"
167339
167726
  });
167340
167727
  } catch (error51) {
167341
- log(`[key-files] identification phase failed: ${getErrorMessage(error51)}`);
167728
+ const errorDescription = describeError(error51);
167729
+ logWithStackHead(`[key-files] identification phase failed: ${errorDescription.brief}`, errorDescription.stackHead);
167342
167730
  result.tasks.push({
167343
167731
  name: "key files",
167344
167732
  durationMs: Date.now() - kfStart,
167345
167733
  result: "",
167346
- error: getErrorMessage(error51)
167734
+ error: errorDescription.brief
167347
167735
  });
167348
167736
  }
167349
167737
  }
@@ -167378,7 +167766,13 @@ async function runDream(args) {
167378
167766
  smartNotesPending: result.smartNotesPending,
167379
167767
  memoryChanges: persistedMemoryChanges
167380
167768
  });
167381
- 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
+ ]);
167382
167776
  const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
167383
167777
  if (hasSuccessfulTask) {
167384
167778
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
@@ -167457,7 +167851,12 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167457
167851
  system: DREAMER_SYSTEM_PROMPT,
167458
167852
  parts: [{ type: "text", text: evaluationPrompt, synthetic: true }]
167459
167853
  }
167460
- }, { 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
+ });
167461
167860
  const messagesResponse = await args.client.session.messages({
167462
167861
  path: { id: agentSessionId },
167463
167862
  query: { directory: args.sessionDirectory ?? args.projectIdentity }
@@ -167516,15 +167915,15 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
167516
167915
  });
167517
167916
  } catch (error51) {
167518
167917
  const durationMs = Date.now() - taskStartedAt;
167519
- const errorMsg = getErrorMessage(error51);
167918
+ const errorDescription = describeError(error51);
167520
167919
  args.result.smartNotesSurfaced = 0;
167521
167920
  args.result.smartNotesPending = pendingNotes.length;
167522
- 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);
167523
167922
  args.result.tasks.push({
167524
167923
  name: "smart-notes",
167525
167924
  durationMs,
167526
167925
  result: null,
167527
- error: errorMsg
167926
+ error: errorDescription.brief
167528
167927
  });
167529
167928
  } finally {
167530
167929
  clearInterval(leaseInterval);
@@ -167557,7 +167956,8 @@ async function processDreamQueue(args) {
167557
167956
  maxRuntimeMinutes: args.maxRuntimeMinutes,
167558
167957
  sessionDirectory: projectDirectory,
167559
167958
  experimentalUserMemories: args.experimentalUserMemories,
167560
- experimentalPinKeyFiles: args.experimentalPinKeyFiles
167959
+ experimentalPinKeyFiles: args.experimentalPinKeyFiles,
167960
+ fallbackModels: args.fallbackModels
167561
167961
  });
167562
167962
  } catch (error51) {
167563
167963
  log(`[dreamer] runDream threw for ${entry.projectIdentity}: ${getErrorMessage(error51)}`);
@@ -168165,6 +168565,7 @@ function searchGitCommitsSync(db, projectPath, query, options) {
168165
168565
  init_embedding();
168166
168566
  init_project_identity();
168167
168567
  init_logger();
168568
+ init_resolve_fallbacks();
168168
168569
  await init_storage();
168169
168570
  var DREAM_TIMER_INTERVAL_MS = 15 * 60 * 1000;
168170
168571
  var activeTimer = null;
@@ -168251,7 +168652,8 @@ async function sweepProject(reg, origin) {
168251
168652
  maxRuntimeMinutes: reg.dreamerConfig.max_runtime_minutes,
168252
168653
  experimentalUserMemories: reg.experimentalUserMemories,
168253
168654
  experimentalPinKeyFiles: reg.experimentalPinKeyFiles,
168254
- projectIdentity: registrationIdentity
168655
+ projectIdentity: registrationIdentity,
168656
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, reg.dreamerConfig.fallback_models)
168255
168657
  });
168256
168658
  } catch (error51) {
168257
168659
  log(`[dreamer] timer-triggered queue processing failed for ${reg.directory}:`, error51);
@@ -168779,6 +169181,7 @@ function createTagger() {
168779
169181
  init_magic_context();
168780
169182
  init_project_identity();
168781
169183
  init_logger();
169184
+ init_resolve_fallbacks();
168782
169185
  await init_storage();
168783
169186
 
168784
169187
  // src/hooks/magic-context/command-handler.ts
@@ -169259,7 +169662,8 @@ Dreaming is not configured for this project.`, {});
169259
169662
  maxRuntimeMinutes: deps.dreamer.config.max_runtime_minutes,
169260
169663
  experimentalUserMemories: deps.dreamer.experimentalUserMemories,
169261
169664
  experimentalPinKeyFiles: deps.dreamer.experimentalPinKeyFiles,
169262
- projectIdentity: deps.dreamer.projectPath
169665
+ projectIdentity: deps.dreamer.projectPath,
169666
+ fallbackModels: deps.dreamer.fallbackModels
169263
169667
  });
169264
169668
  await deps.sendNotification(sessionId, result ? summarizeDreamResult(result) : "Dream queued, but another worker is already processing the queue.", {});
169265
169669
  throwSentinel("CTX-DREAM");
@@ -169555,113 +169959,8 @@ function clearSessionTracking(sessionId) {
169555
169959
  }
169556
169960
  }
169557
169961
 
169558
- // src/features/magic-context/overflow-detection.ts
169559
- var OVERFLOW_PATTERNS = [
169560
- /prompt is too long/i,
169561
- /input is too long for requested model/i,
169562
- /exceeds the context window/i,
169563
- /input token count.*exceeds the maximum/i,
169564
- /maximum prompt length is \d+/i,
169565
- /reduce the length of the messages/i,
169566
- /maximum context length is \d+ tokens/i,
169567
- /exceeds the limit of \d+/i,
169568
- /exceeds the available context size/i,
169569
- /greater than the context length/i,
169570
- /context window exceeds limit/i,
169571
- /exceeded model token limit/i,
169572
- /context[_ ]length[_ ]exceeded/i,
169573
- /request entity too large/i,
169574
- /context length is only \d+ tokens/i,
169575
- /input length.*exceeds.*context length/i,
169576
- /prompt too long; exceeded (?:max )?context length/i,
169577
- /too large for model with \d+ maximum context length/i,
169578
- /model_context_window_exceeded/i,
169579
- /context size has been exceeded/i
169580
- ];
169581
- var LIMIT_EXTRACTION_PATTERNS = [
169582
- /maximum prompt length is (\d+)/i,
169583
- /maximum context length is (\d+) tokens?/i,
169584
- /context length is only (\d+) tokens?/i,
169585
- /exceeds the limit of (\d+)/i,
169586
- /too large for model with (\d+) maximum context length/i,
169587
- /context size.*(\d+) tokens?/i,
169588
- /exceeds? the context length of (\d+)/i,
169589
- /max(?:imum)?.*context.*?(\d+)/i
169590
- ];
169591
- var MIN_PLAUSIBLE_LIMIT = 1024;
169592
- var MAX_PLAUSIBLE_LIMIT = 1e7;
169593
- function extractErrorMessage(error51) {
169594
- if (!error51)
169595
- return "";
169596
- if (typeof error51 === "string")
169597
- return error51;
169598
- if (typeof error51 === "object") {
169599
- const obj = error51;
169600
- const nested = obj.error;
169601
- if (nested && typeof nested.message === "string" && nested.message.length > 0) {
169602
- return nested.message;
169603
- }
169604
- }
169605
- if (error51 instanceof Error)
169606
- return error51.message;
169607
- if (typeof error51 === "object") {
169608
- const obj = error51;
169609
- if (typeof obj.message === "string")
169610
- return obj.message;
169611
- if (typeof obj.responseBody === "string")
169612
- return obj.responseBody;
169613
- try {
169614
- return JSON.stringify(error51);
169615
- } catch {
169616
- return String(error51);
169617
- }
169618
- }
169619
- return String(error51);
169620
- }
169621
- function detectOverflow(error51) {
169622
- const message = extractErrorMessage(error51);
169623
- if (!message) {
169624
- return { isOverflow: false };
169625
- }
169626
- const hasStatus413 = /\b413\b/.test(message) && /(entity|payload|context|prompt)/i.test(message);
169627
- let matched;
169628
- for (const pattern of OVERFLOW_PATTERNS) {
169629
- if (pattern.test(message)) {
169630
- matched = pattern;
169631
- break;
169632
- }
169633
- }
169634
- if (!matched && !hasStatus413) {
169635
- return { isOverflow: false };
169636
- }
169637
- const reportedLimit = parseReportedLimit(message);
169638
- return {
169639
- isOverflow: true,
169640
- reportedLimit,
169641
- matchedPattern: matched?.source
169642
- };
169643
- }
169644
- function parseReportedLimit(message) {
169645
- if (!message)
169646
- return;
169647
- for (const pattern of LIMIT_EXTRACTION_PATTERNS) {
169648
- const match = message.match(pattern);
169649
- if (!match)
169650
- continue;
169651
- const raw = match[1];
169652
- if (!raw)
169653
- continue;
169654
- const value = Number.parseInt(raw, 10);
169655
- if (!Number.isFinite(value))
169656
- continue;
169657
- if (value < MIN_PLAUSIBLE_LIMIT || value > MAX_PLAUSIBLE_LIMIT)
169658
- continue;
169659
- return value;
169660
- }
169661
- return;
169662
- }
169663
-
169664
169962
  // src/hooks/magic-context/event-handler.ts
169963
+ init_overflow_detection();
169665
169964
  init_storage_meta_persisted();
169666
169965
  init_logger();
169667
169966
  await __promiseAll([
@@ -170575,6 +170874,7 @@ async function runCompartmentPhase(args) {
170575
170874
  historianChunkTokens: args.historianChunkTokens,
170576
170875
  historyBudgetTokens: args.historyBudgetTokens,
170577
170876
  historianTimeoutMs: args.historianTimeoutMs,
170877
+ fallbackModels: args.fallbackModels,
170578
170878
  directory: args.compartmentDirectory,
170579
170879
  fallbackModelId: args.fallbackModelId,
170580
170880
  getNotificationParams: args.getNotificationParams,
@@ -170602,6 +170902,7 @@ async function runCompartmentPhase(args) {
170602
170902
  historianChunkTokens: args.historianChunkTokens,
170603
170903
  historyBudgetTokens: args.historyBudgetTokens,
170604
170904
  historianTimeoutMs: args.historianTimeoutMs,
170905
+ fallbackModels: args.fallbackModels,
170605
170906
  directory: args.compartmentDirectory,
170606
170907
  fallbackModelId: args.fallbackModelId,
170607
170908
  getNotificationParams: args.getNotificationParams,
@@ -170641,6 +170942,7 @@ async function runCompartmentPhase(args) {
170641
170942
  directory: args.compartmentDirectory,
170642
170943
  historyBudgetTokens: args.historyBudgetTokens,
170643
170944
  historianTimeoutMs: args.historianTimeoutMs,
170945
+ fallbackModels: args.fallbackModels,
170644
170946
  minCompartmentRatio: args.compressorMinCompartmentRatio,
170645
170947
  maxMergeDepth: args.compressorMaxMergeDepth
170646
170948
  }).then((compressed) => {
@@ -170738,6 +171040,46 @@ function countMessagesSinceLastUser(messages) {
170738
171040
  }
170739
171041
  return messagesSinceLastUser;
170740
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
+ }
170741
171083
  function appendReminderToUserMessage(message, reminder) {
170742
171084
  for (const part of message.parts) {
170743
171085
  if (!isTextPart(part)) {
@@ -172247,6 +172589,84 @@ function isVisibleNoteReadPart(part) {
172247
172589
  return false;
172248
172590
  }
172249
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
+
172250
172670
  // src/hooks/magic-context/transform-stage-logger.ts
172251
172671
  init_logger();
172252
172672
  function logTransformTiming(sessionId, stage, startMs, extra) {
@@ -172488,6 +172908,33 @@ async function runPostTransformPhase(args) {
172488
172908
  const anchoredMessageId = appendReminderToLatestUserMessage(args.messages, noteInstruction);
172489
172909
  markNoteNudgeDelivered(args.db, args.sessionId, noteInstruction, anchoredMessageId);
172490
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
+ }
172491
172938
  if (args.fullFeatureMode && args.autoSearch?.enabled && args.projectPath) {
172492
172939
  const visibleMemoryIds = getVisibleMemoryIds(args.db, args.sessionId) ?? undefined;
172493
172940
  try {
@@ -172719,6 +173166,7 @@ function createTransform(deps) {
172719
173166
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172720
173167
  historyBudgetTokens,
172721
173168
  historianTimeoutMs: deps.historianTimeoutMs,
173169
+ fallbackModels: deps.fallbackModels,
172722
173170
  directory: compartmentDirectory,
172723
173171
  fallbackModelId,
172724
173172
  getNotificationParams: () => notificationParams,
@@ -172888,6 +173336,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
172888
173336
  historianChunkTokens: deps.getHistorianChunkTokens?.() ?? 20000,
172889
173337
  historyBudgetTokens,
172890
173338
  historianTimeoutMs: deps.historianTimeoutMs,
173339
+ fallbackModels: deps.fallbackModels,
172891
173340
  compartmentDirectory,
172892
173341
  messages,
172893
173342
  pendingCompartmentInjection,
@@ -173728,9 +174177,17 @@ function createToolExecuteAfterHook(args) {
173728
174177
  if (typedInput.tool === "todowrite") {
173729
174178
  const todoArgs = typedInput.args;
173730
174179
  const todos = todoArgs?.todos;
173731
- if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
173732
- const sessionMeta = getOrCreateSessionMeta(args.db, typedInput.sessionID);
173733
- 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) {
173734
174191
  onNoteTrigger(args.db, typedInput.sessionID, "todos_complete");
173735
174192
  }
173736
174193
  }
@@ -173746,7 +174203,7 @@ function createToolExecuteAfterHook(args) {
173746
174203
  init_send_session_notification();
173747
174204
 
173748
174205
  // src/hooks/magic-context/system-prompt-hash.ts
173749
- import { createHash as createHash4 } from "node:crypto";
174206
+ import { createHash as createHash5 } from "node:crypto";
173750
174207
  import { existsSync as existsSync13, readFileSync as readFileSync9, realpathSync } from "node:fs";
173751
174208
  import { join as join22, resolve as resolve4, sep } from "node:path";
173752
174209
 
@@ -174051,7 +174508,7 @@ ${sections.join(`
174051
174508
  `);
174052
174509
  if (systemContent.length === 0)
174053
174510
  return;
174054
- const currentHash = createHash4("md5").update(systemContent).digest("hex");
174511
+ const currentHash = createHash5("md5").update(systemContent).digest("hex");
174055
174512
  if (!sessionMetaEarly) {
174056
174513
  return;
174057
174514
  }
@@ -174066,14 +174523,12 @@ ${sections.join(`
174066
174523
  } else if (previousHash === "" || previousHash === "0") {
174067
174524
  sessionLog(sessionId, `system prompt hash initialized: ${currentHash} (len=${systemContent.length})`);
174068
174525
  }
174069
- const systemPromptTokens = estimateTokens(systemContent);
174070
174526
  if (currentHash !== previousHash) {
174527
+ const systemPromptTokens = estimateTokens(systemContent);
174071
174528
  updateSessionMeta(deps.db, sessionId, {
174072
174529
  systemPromptHash: currentHash,
174073
174530
  systemPromptTokens
174074
174531
  });
174075
- } else if (Math.abs(sessionMeta.systemPromptTokens - systemPromptTokens) > 50) {
174076
- updateSessionMeta(deps.db, sessionId, { systemPromptTokens });
174077
174532
  }
174078
174533
  if (isCacheBusting) {
174079
174534
  deps.systemPromptRefreshSessions.delete(sessionId);
@@ -174135,6 +174590,7 @@ function createMagicContextHook(deps) {
174135
174590
  }
174136
174591
  let lastScheduleCheckMs = 0;
174137
174592
  const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
174593
+ const historianFallbackModels = resolveFallbackChain(HISTORIAN_AGENT, deps.config.historian?.fallback_models);
174138
174594
  const nudgePlacements = createNudgePlacementStore(db);
174139
174595
  const historyRefreshSessions = deps.liveSessionState?.historyRefreshSessions ?? new Set;
174140
174596
  const systemPromptRefreshSessions = deps.liveSessionState?.systemPromptRefreshSessions ?? new Set;
@@ -174194,6 +174650,7 @@ function createMagicContextHook(deps) {
174194
174650
  executeThresholdPercentage: deps.config.execute_threshold_percentage,
174195
174651
  executeThresholdTokens: deps.config.execute_threshold_tokens,
174196
174652
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
174653
+ fallbackModels: historianFallbackModels,
174197
174654
  getNotificationParams: (sessionId) => getLiveNotificationParams(sessionId, liveModelBySession, variantBySession, agentBySession),
174198
174655
  getModelKey: (sessionId) => {
174199
174656
  const model = liveModelBySession.get(sessionId);
@@ -174271,7 +174728,8 @@ function createMagicContextHook(deps) {
174271
174728
  enabled: true,
174272
174729
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174273
174730
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174274
- } : undefined
174731
+ } : undefined,
174732
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer?.fallback_models)
174275
174733
  }).catch((error51) => {
174276
174734
  log("[dreamer] scheduled queue processing failed:", error51);
174277
174735
  });
@@ -174305,6 +174763,7 @@ function createMagicContextHook(deps) {
174305
174763
  sessionId,
174306
174764
  historianChunkTokens: getHistorianChunkTokens(),
174307
174765
  historianTimeoutMs: deps.config.historian_timeout_ms ?? DEFAULT_HISTORIAN_TIMEOUT_MS,
174766
+ fallbackModels: historianFallbackModels,
174308
174767
  directory: deps.directory,
174309
174768
  fallbackModelId: (() => {
174310
174769
  const model = resolveLiveModel(sessionId);
@@ -174344,7 +174803,8 @@ function createMagicContextHook(deps) {
174344
174803
  enabled: true,
174345
174804
  token_budget: deps.config.dreamer.pin_key_files.token_budget,
174346
174805
  min_reads: deps.config.dreamer.pin_key_files.min_reads
174347
- } : undefined
174806
+ } : undefined,
174807
+ fallbackModels: resolveFallbackChain(DREAMER_AGENT, deps.config.dreamer.fallback_models)
174348
174808
  } : undefined
174349
174809
  });
174350
174810
  const systemPromptHash = createSystemPromptHashHandler({
@@ -175006,19 +175466,7 @@ function buildStatusDetail(db, sessionId, directory, modelKey, config2, liveSess
175006
175466
  }
175007
175467
  detail.nextNudgeAfter = detail.lastNudgeTokens + detail.nudgeInterval;
175008
175468
  try {
175009
- const compartments = db.prepare("SELECT content, title, start_message, end_message FROM compartments WHERE session_id = ?").all(sessionId);
175010
- const facts = db.prepare("SELECT content FROM session_facts WHERE session_id = ?").all(sessionId);
175011
- let histTokens = 0;
175012
- for (const c of compartments) {
175013
- histTokens += estimateTokens(`<compartment start="${c.start_message}" end="${c.end_message}" title="${c.title}">
175014
- ${c.content}
175015
- </compartment>
175016
- `);
175017
- }
175018
- for (const f of facts) {
175019
- histTokens += estimateTokens(`* ${f.content}
175020
- `);
175021
- }
175469
+ const histTokens = base.compartmentTokens + base.factTokens;
175022
175470
  detail.historyBlockTokens = histTokens;
175023
175471
  if (detail.contextLimit > 0) {
175024
175472
  const budget = Math.floor(detail.contextLimit * (Math.min(detail.executeThreshold, 80) / 100) * detail.historyBudgetPercentage);
@@ -175191,7 +175639,7 @@ function normalizeLimit2(limit) {
175191
175639
  return Math.max(1, Math.floor(limit));
175192
175640
  }
175193
175641
  function getAllowedActions(deps) {
175194
- const allowed = deps.allowedActions?.length ? deps.allowedActions : [...CTX_MEMORY_DREAMER_ACTIONS];
175642
+ const allowed = deps.allowedActions?.length ? deps.allowedActions : ["write", "delete"];
175195
175643
  return [...allowed];
175196
175644
  }
175197
175645
  function normalizeCategory(category) {
@@ -175297,7 +175745,7 @@ function createCtxMemoryTool(deps) {
175297
175745
  return tool2({
175298
175746
  description: CTX_MEMORY_DESCRIPTION,
175299
175747
  args: {
175300
- 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"),
175301
175749
  content: tool2.schema.string().optional().describe("Memory content (required for write, update, merge)"),
175302
175750
  category: tool2.schema.string().optional().describe("Memory category (required for write, optional filter for list, optional override for merge)"),
175303
175751
  id: tool2.schema.number().optional().describe("Memory ID (required for delete, update, archive)"),
@@ -176063,11 +176511,11 @@ import { createServer } from "node:http";
176063
176511
  import { dirname as dirname6 } from "node:path";
176064
176512
 
176065
176513
  // src/shared/rpc-utils.ts
176066
- import { createHash as createHash5 } from "node:crypto";
176514
+ import { createHash as createHash6 } from "node:crypto";
176067
176515
  import { join as join23 } from "node:path";
176068
176516
  function projectHash(directory) {
176069
176517
  const normalized = directory.replace(/\/+$/, "");
176070
- return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
176518
+ return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
176071
176519
  }
176072
176520
  function rpcPortFilePath(storageDir, directory) {
176073
176521
  return join23(storageDir, "rpc", projectHash(directory), "port");