@cortexkit/opencode-magic-context 0.2.10 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +17 -4
  2. package/dist/agents/magic-context-prompt.d.ts +1 -1
  3. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  4. package/dist/cli/setup.d.ts.map +1 -1
  5. package/dist/cli.js +38 -3
  6. package/dist/config/schema/magic-context.d.ts +3 -0
  7. package/dist/config/schema/magic-context.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
  9. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  10. package/dist/features/magic-context/message-index.d.ts +1 -0
  11. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  12. package/dist/features/magic-context/search.d.ts.map +1 -1
  13. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  14. package/dist/features/magic-context/storage-meta-persisted.d.ts +5 -0
  15. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  17. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-smart-notes.d.ts +24 -0
  19. package/dist/features/magic-context/storage-smart-notes.d.ts.map +1 -0
  20. package/dist/features/magic-context/storage-tags.d.ts +2 -0
  21. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage.d.ts +4 -2
  23. package/dist/features/magic-context/storage.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/event-payloads.d.ts +6 -1
  26. package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
  27. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/note-nudger.d.ts +4 -2
  30. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/nudge-placement-store.d.ts +3 -1
  32. package/dist/hooks/magic-context/nudge-placement-store.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/system-prompt-hash.d.ts +5 -0
  34. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  36. package/dist/index.js +1658 -1035
  37. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  38. package/dist/plugin/tool-registry.d.ts.map +1 -1
  39. package/dist/tools/ctx-note/constants.d.ts +1 -1
  40. package/dist/tools/ctx-note/constants.d.ts.map +1 -1
  41. package/dist/tools/ctx-note/tools.d.ts +2 -0
  42. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  43. package/dist/tools/ctx-note/types.d.ts +3 -1
  44. package/dist/tools/ctx-note/types.d.ts.map +1 -1
  45. package/package.json +4 -3
  46. package/LICENSE +0 -21
  47. package/scripts/install.sh +0 -35
package/dist/index.js CHANGED
@@ -144,7 +144,7 @@ function detectConfigFile(basePath) {
144
144
  return { format: "none", path: jsoncPath };
145
145
  }
146
146
 
147
- // node_modules/zod/v4/classic/external.js
147
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
148
148
  var exports_external = {};
149
149
  __export(exports_external, {
150
150
  xor: () => xor,
@@ -385,7 +385,7 @@ __export(exports_external, {
385
385
  $brand: () => $brand
386
386
  });
387
387
 
388
- // node_modules/zod/v4/core/index.js
388
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/index.js
389
389
  var exports_core2 = {};
390
390
  __export(exports_core2, {
391
391
  version: () => version,
@@ -663,7 +663,7 @@ __export(exports_core2, {
663
663
  $ZodAny: () => $ZodAny
664
664
  });
665
665
 
666
- // node_modules/zod/v4/core/core.js
666
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
667
667
  var NEVER = Object.freeze({
668
668
  status: "aborted"
669
669
  });
@@ -739,7 +739,7 @@ function config(newConfig) {
739
739
  Object.assign(globalConfig, newConfig);
740
740
  return globalConfig;
741
741
  }
742
- // node_modules/zod/v4/core/util.js
742
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/util.js
743
743
  var exports_util = {};
744
744
  __export(exports_util, {
745
745
  unwrapMessage: () => unwrapMessage,
@@ -1413,7 +1413,7 @@ class Class {
1413
1413
  constructor(..._args) {}
1414
1414
  }
1415
1415
 
1416
- // node_modules/zod/v4/core/errors.js
1416
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/errors.js
1417
1417
  var initializer = (inst, def) => {
1418
1418
  inst.name = "$ZodError";
1419
1419
  Object.defineProperty(inst, "_zod", {
@@ -1550,7 +1550,7 @@ function prettifyError(error) {
1550
1550
  `);
1551
1551
  }
1552
1552
 
1553
- // node_modules/zod/v4/core/parse.js
1553
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/parse.js
1554
1554
  var _parse = (_Err) => (schema, value, _ctx, _params) => {
1555
1555
  const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
1556
1556
  const result = schema._zod.run({ value, issues: [] }, ctx);
@@ -1637,7 +1637,7 @@ var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
1637
1637
  return _safeParseAsync(_Err)(schema, value, _ctx);
1638
1638
  };
1639
1639
  var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync($ZodRealError);
1640
- // node_modules/zod/v4/core/regexes.js
1640
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/regexes.js
1641
1641
  var exports_regexes = {};
1642
1642
  __export(exports_regexes, {
1643
1643
  xid: () => xid,
@@ -1794,7 +1794,7 @@ var sha512_hex = /^[0-9a-fA-F]{128}$/;
1794
1794
  var sha512_base64 = /* @__PURE__ */ fixedBase64(86, "==");
1795
1795
  var sha512_base64url = /* @__PURE__ */ fixedBase64url(86);
1796
1796
 
1797
- // node_modules/zod/v4/core/checks.js
1797
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/checks.js
1798
1798
  var $ZodCheck = /* @__PURE__ */ $constructor("$ZodCheck", (inst, def) => {
1799
1799
  var _a;
1800
1800
  inst._zod ?? (inst._zod = {});
@@ -2341,7 +2341,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (ins
2341
2341
  };
2342
2342
  });
2343
2343
 
2344
- // node_modules/zod/v4/core/doc.js
2344
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/doc.js
2345
2345
  class Doc {
2346
2346
  constructor(args = []) {
2347
2347
  this.content = [];
@@ -2379,14 +2379,14 @@ class Doc {
2379
2379
  }
2380
2380
  }
2381
2381
 
2382
- // node_modules/zod/v4/core/versions.js
2382
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/versions.js
2383
2383
  var version = {
2384
2384
  major: 4,
2385
2385
  minor: 3,
2386
2386
  patch: 6
2387
2387
  };
2388
2388
 
2389
- // node_modules/zod/v4/core/schemas.js
2389
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/schemas.js
2390
2390
  var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
2391
2391
  var _a;
2392
2392
  inst ?? (inst = {});
@@ -4348,7 +4348,7 @@ function handleRefineResult(result, payload, input, inst) {
4348
4348
  payload.issues.push(issue(_iss));
4349
4349
  }
4350
4350
  }
4351
- // node_modules/zod/v4/locales/index.js
4351
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/index.js
4352
4352
  var exports_locales = {};
4353
4353
  __export(exports_locales, {
4354
4354
  zhTW: () => zh_TW_default,
@@ -4402,7 +4402,7 @@ __export(exports_locales, {
4402
4402
  ar: () => ar_default
4403
4403
  });
4404
4404
 
4405
- // node_modules/zod/v4/locales/ar.js
4405
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ar.js
4406
4406
  var error = () => {
4407
4407
  const Sizable = {
4408
4408
  string: { unit: "\u062D\u0631\u0641", verb: "\u0623\u0646 \u064A\u062D\u0648\u064A" },
@@ -4508,7 +4508,7 @@ function ar_default() {
4508
4508
  localeError: error()
4509
4509
  };
4510
4510
  }
4511
- // node_modules/zod/v4/locales/az.js
4511
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/az.js
4512
4512
  var error2 = () => {
4513
4513
  const Sizable = {
4514
4514
  string: { unit: "simvol", verb: "olmal\u0131d\u0131r" },
@@ -4613,7 +4613,7 @@ function az_default() {
4613
4613
  localeError: error2()
4614
4614
  };
4615
4615
  }
4616
- // node_modules/zod/v4/locales/be.js
4616
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/be.js
4617
4617
  function getBelarusianPlural(count, one, few, many) {
4618
4618
  const absCount = Math.abs(count);
4619
4619
  const lastDigit = absCount % 10;
@@ -4769,7 +4769,7 @@ function be_default() {
4769
4769
  localeError: error3()
4770
4770
  };
4771
4771
  }
4772
- // node_modules/zod/v4/locales/bg.js
4772
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/bg.js
4773
4773
  var error4 = () => {
4774
4774
  const Sizable = {
4775
4775
  string: { unit: "\u0441\u0438\u043C\u0432\u043E\u043B\u0430", verb: "\u0434\u0430 \u0441\u044A\u0434\u044A\u0440\u0436\u0430" },
@@ -4889,7 +4889,7 @@ function bg_default() {
4889
4889
  localeError: error4()
4890
4890
  };
4891
4891
  }
4892
- // node_modules/zod/v4/locales/ca.js
4892
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ca.js
4893
4893
  var error5 = () => {
4894
4894
  const Sizable = {
4895
4895
  string: { unit: "car\xE0cters", verb: "contenir" },
@@ -4996,7 +4996,7 @@ function ca_default() {
4996
4996
  localeError: error5()
4997
4997
  };
4998
4998
  }
4999
- // node_modules/zod/v4/locales/cs.js
4999
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/cs.js
5000
5000
  var error6 = () => {
5001
5001
  const Sizable = {
5002
5002
  string: { unit: "znak\u016F", verb: "m\xEDt" },
@@ -5107,7 +5107,7 @@ function cs_default() {
5107
5107
  localeError: error6()
5108
5108
  };
5109
5109
  }
5110
- // node_modules/zod/v4/locales/da.js
5110
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/da.js
5111
5111
  var error7 = () => {
5112
5112
  const Sizable = {
5113
5113
  string: { unit: "tegn", verb: "havde" },
@@ -5222,7 +5222,7 @@ function da_default() {
5222
5222
  localeError: error7()
5223
5223
  };
5224
5224
  }
5225
- // node_modules/zod/v4/locales/de.js
5225
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/de.js
5226
5226
  var error8 = () => {
5227
5227
  const Sizable = {
5228
5228
  string: { unit: "Zeichen", verb: "zu haben" },
@@ -5330,7 +5330,7 @@ function de_default() {
5330
5330
  localeError: error8()
5331
5331
  };
5332
5332
  }
5333
- // node_modules/zod/v4/locales/en.js
5333
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/en.js
5334
5334
  var error9 = () => {
5335
5335
  const Sizable = {
5336
5336
  string: { unit: "characters", verb: "to have" },
@@ -5436,7 +5436,7 @@ function en_default() {
5436
5436
  localeError: error9()
5437
5437
  };
5438
5438
  }
5439
- // node_modules/zod/v4/locales/eo.js
5439
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/eo.js
5440
5440
  var error10 = () => {
5441
5441
  const Sizable = {
5442
5442
  string: { unit: "karaktrojn", verb: "havi" },
@@ -5545,7 +5545,7 @@ function eo_default() {
5545
5545
  localeError: error10()
5546
5546
  };
5547
5547
  }
5548
- // node_modules/zod/v4/locales/es.js
5548
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/es.js
5549
5549
  var error11 = () => {
5550
5550
  const Sizable = {
5551
5551
  string: { unit: "caracteres", verb: "tener" },
@@ -5677,7 +5677,7 @@ function es_default() {
5677
5677
  localeError: error11()
5678
5678
  };
5679
5679
  }
5680
- // node_modules/zod/v4/locales/fa.js
5680
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fa.js
5681
5681
  var error12 = () => {
5682
5682
  const Sizable = {
5683
5683
  string: { unit: "\u06A9\u0627\u0631\u0627\u06A9\u062A\u0631", verb: "\u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F" },
@@ -5791,7 +5791,7 @@ function fa_default() {
5791
5791
  localeError: error12()
5792
5792
  };
5793
5793
  }
5794
- // node_modules/zod/v4/locales/fi.js
5794
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fi.js
5795
5795
  var error13 = () => {
5796
5796
  const Sizable = {
5797
5797
  string: { unit: "merkki\xE4", subject: "merkkijonon" },
@@ -5903,7 +5903,7 @@ function fi_default() {
5903
5903
  localeError: error13()
5904
5904
  };
5905
5905
  }
5906
- // node_modules/zod/v4/locales/fr.js
5906
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr.js
5907
5907
  var error14 = () => {
5908
5908
  const Sizable = {
5909
5909
  string: { unit: "caract\xE8res", verb: "avoir" },
@@ -6011,7 +6011,7 @@ function fr_default() {
6011
6011
  localeError: error14()
6012
6012
  };
6013
6013
  }
6014
- // node_modules/zod/v4/locales/fr-CA.js
6014
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr-CA.js
6015
6015
  var error15 = () => {
6016
6016
  const Sizable = {
6017
6017
  string: { unit: "caract\xE8res", verb: "avoir" },
@@ -6118,7 +6118,7 @@ function fr_CA_default() {
6118
6118
  localeError: error15()
6119
6119
  };
6120
6120
  }
6121
- // node_modules/zod/v4/locales/he.js
6121
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/he.js
6122
6122
  var error16 = () => {
6123
6123
  const TypeNames = {
6124
6124
  string: { label: "\u05DE\u05D7\u05E8\u05D5\u05D6\u05EA", gender: "f" },
@@ -6311,7 +6311,7 @@ function he_default() {
6311
6311
  localeError: error16()
6312
6312
  };
6313
6313
  }
6314
- // node_modules/zod/v4/locales/hu.js
6314
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hu.js
6315
6315
  var error17 = () => {
6316
6316
  const Sizable = {
6317
6317
  string: { unit: "karakter", verb: "legyen" },
@@ -6419,7 +6419,7 @@ function hu_default() {
6419
6419
  localeError: error17()
6420
6420
  };
6421
6421
  }
6422
- // node_modules/zod/v4/locales/hy.js
6422
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hy.js
6423
6423
  function getArmenianPlural(count, one, many) {
6424
6424
  return Math.abs(count) === 1 ? one : many;
6425
6425
  }
@@ -6566,7 +6566,7 @@ function hy_default() {
6566
6566
  localeError: error18()
6567
6567
  };
6568
6568
  }
6569
- // node_modules/zod/v4/locales/id.js
6569
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/id.js
6570
6570
  var error19 = () => {
6571
6571
  const Sizable = {
6572
6572
  string: { unit: "karakter", verb: "memiliki" },
@@ -6672,7 +6672,7 @@ function id_default() {
6672
6672
  localeError: error19()
6673
6673
  };
6674
6674
  }
6675
- // node_modules/zod/v4/locales/is.js
6675
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/is.js
6676
6676
  var error20 = () => {
6677
6677
  const Sizable = {
6678
6678
  string: { unit: "stafi", verb: "a\xF0 hafa" },
@@ -6781,7 +6781,7 @@ function is_default() {
6781
6781
  localeError: error20()
6782
6782
  };
6783
6783
  }
6784
- // node_modules/zod/v4/locales/it.js
6784
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/it.js
6785
6785
  var error21 = () => {
6786
6786
  const Sizable = {
6787
6787
  string: { unit: "caratteri", verb: "avere" },
@@ -6889,7 +6889,7 @@ function it_default() {
6889
6889
  localeError: error21()
6890
6890
  };
6891
6891
  }
6892
- // node_modules/zod/v4/locales/ja.js
6892
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ja.js
6893
6893
  var error22 = () => {
6894
6894
  const Sizable = {
6895
6895
  string: { unit: "\u6587\u5B57", verb: "\u3067\u3042\u308B" },
@@ -6996,7 +6996,7 @@ function ja_default() {
6996
6996
  localeError: error22()
6997
6997
  };
6998
6998
  }
6999
- // node_modules/zod/v4/locales/ka.js
6999
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ka.js
7000
7000
  var error23 = () => {
7001
7001
  const Sizable = {
7002
7002
  string: { unit: "\u10E1\u10D8\u10DB\u10D1\u10DD\u10DA\u10DD", verb: "\u10E3\u10DC\u10D3\u10D0 \u10E8\u10D4\u10D8\u10EA\u10D0\u10D5\u10D3\u10D4\u10E1" },
@@ -7108,7 +7108,7 @@ function ka_default() {
7108
7108
  localeError: error23()
7109
7109
  };
7110
7110
  }
7111
- // node_modules/zod/v4/locales/km.js
7111
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/km.js
7112
7112
  var error24 = () => {
7113
7113
  const Sizable = {
7114
7114
  string: { unit: "\u178F\u17BD\u17A2\u1780\u17D2\u179F\u179A", verb: "\u1782\u17BD\u179A\u1798\u17B6\u1793" },
@@ -7219,11 +7219,11 @@ function km_default() {
7219
7219
  };
7220
7220
  }
7221
7221
 
7222
- // node_modules/zod/v4/locales/kh.js
7222
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/kh.js
7223
7223
  function kh_default() {
7224
7224
  return km_default();
7225
7225
  }
7226
- // node_modules/zod/v4/locales/ko.js
7226
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ko.js
7227
7227
  var error25 = () => {
7228
7228
  const Sizable = {
7229
7229
  string: { unit: "\uBB38\uC790", verb: "to have" },
@@ -7334,7 +7334,7 @@ function ko_default() {
7334
7334
  localeError: error25()
7335
7335
  };
7336
7336
  }
7337
- // node_modules/zod/v4/locales/lt.js
7337
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/lt.js
7338
7338
  var capitalizeFirstCharacter = (text) => {
7339
7339
  return text.charAt(0).toUpperCase() + text.slice(1);
7340
7340
  };
@@ -7537,7 +7537,7 @@ function lt_default() {
7537
7537
  localeError: error26()
7538
7538
  };
7539
7539
  }
7540
- // node_modules/zod/v4/locales/mk.js
7540
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/mk.js
7541
7541
  var error27 = () => {
7542
7542
  const Sizable = {
7543
7543
  string: { unit: "\u0437\u043D\u0430\u0446\u0438", verb: "\u0434\u0430 \u0438\u043C\u0430\u0430\u0442" },
@@ -7646,7 +7646,7 @@ function mk_default() {
7646
7646
  localeError: error27()
7647
7647
  };
7648
7648
  }
7649
- // node_modules/zod/v4/locales/ms.js
7649
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ms.js
7650
7650
  var error28 = () => {
7651
7651
  const Sizable = {
7652
7652
  string: { unit: "aksara", verb: "mempunyai" },
@@ -7753,7 +7753,7 @@ function ms_default() {
7753
7753
  localeError: error28()
7754
7754
  };
7755
7755
  }
7756
- // node_modules/zod/v4/locales/nl.js
7756
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/nl.js
7757
7757
  var error29 = () => {
7758
7758
  const Sizable = {
7759
7759
  string: { unit: "tekens", verb: "heeft" },
@@ -7863,7 +7863,7 @@ function nl_default() {
7863
7863
  localeError: error29()
7864
7864
  };
7865
7865
  }
7866
- // node_modules/zod/v4/locales/no.js
7866
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/no.js
7867
7867
  var error30 = () => {
7868
7868
  const Sizable = {
7869
7869
  string: { unit: "tegn", verb: "\xE5 ha" },
@@ -7971,7 +7971,7 @@ function no_default() {
7971
7971
  localeError: error30()
7972
7972
  };
7973
7973
  }
7974
- // node_modules/zod/v4/locales/ota.js
7974
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ota.js
7975
7975
  var error31 = () => {
7976
7976
  const Sizable = {
7977
7977
  string: { unit: "harf", verb: "olmal\u0131d\u0131r" },
@@ -8080,7 +8080,7 @@ function ota_default() {
8080
8080
  localeError: error31()
8081
8081
  };
8082
8082
  }
8083
- // node_modules/zod/v4/locales/ps.js
8083
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ps.js
8084
8084
  var error32 = () => {
8085
8085
  const Sizable = {
8086
8086
  string: { unit: "\u062A\u0648\u06A9\u064A", verb: "\u0648\u0644\u0631\u064A" },
@@ -8194,7 +8194,7 @@ function ps_default() {
8194
8194
  localeError: error32()
8195
8195
  };
8196
8196
  }
8197
- // node_modules/zod/v4/locales/pl.js
8197
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pl.js
8198
8198
  var error33 = () => {
8199
8199
  const Sizable = {
8200
8200
  string: { unit: "znak\xF3w", verb: "mie\u0107" },
@@ -8303,7 +8303,7 @@ function pl_default() {
8303
8303
  localeError: error33()
8304
8304
  };
8305
8305
  }
8306
- // node_modules/zod/v4/locales/pt.js
8306
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pt.js
8307
8307
  var error34 = () => {
8308
8308
  const Sizable = {
8309
8309
  string: { unit: "caracteres", verb: "ter" },
@@ -8411,7 +8411,7 @@ function pt_default() {
8411
8411
  localeError: error34()
8412
8412
  };
8413
8413
  }
8414
- // node_modules/zod/v4/locales/ru.js
8414
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ru.js
8415
8415
  function getRussianPlural(count, one, few, many) {
8416
8416
  const absCount = Math.abs(count);
8417
8417
  const lastDigit = absCount % 10;
@@ -8567,7 +8567,7 @@ function ru_default() {
8567
8567
  localeError: error35()
8568
8568
  };
8569
8569
  }
8570
- // node_modules/zod/v4/locales/sl.js
8570
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sl.js
8571
8571
  var error36 = () => {
8572
8572
  const Sizable = {
8573
8573
  string: { unit: "znakov", verb: "imeti" },
@@ -8676,7 +8676,7 @@ function sl_default() {
8676
8676
  localeError: error36()
8677
8677
  };
8678
8678
  }
8679
- // node_modules/zod/v4/locales/sv.js
8679
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sv.js
8680
8680
  var error37 = () => {
8681
8681
  const Sizable = {
8682
8682
  string: { unit: "tecken", verb: "att ha" },
@@ -8786,7 +8786,7 @@ function sv_default() {
8786
8786
  localeError: error37()
8787
8787
  };
8788
8788
  }
8789
- // node_modules/zod/v4/locales/ta.js
8789
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ta.js
8790
8790
  var error38 = () => {
8791
8791
  const Sizable = {
8792
8792
  string: { unit: "\u0B8E\u0BB4\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1\u0B95\u0BCD\u0B95\u0BB3\u0BCD", verb: "\u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BBF\u0BB0\u0BC1\u0B95\u0BCD\u0B95 \u0BB5\u0BC7\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD" },
@@ -8896,7 +8896,7 @@ function ta_default() {
8896
8896
  localeError: error38()
8897
8897
  };
8898
8898
  }
8899
- // node_modules/zod/v4/locales/th.js
8899
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/th.js
8900
8900
  var error39 = () => {
8901
8901
  const Sizable = {
8902
8902
  string: { unit: "\u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23", verb: "\u0E04\u0E27\u0E23\u0E21\u0E35" },
@@ -9006,7 +9006,7 @@ function th_default() {
9006
9006
  localeError: error39()
9007
9007
  };
9008
9008
  }
9009
- // node_modules/zod/v4/locales/tr.js
9009
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/tr.js
9010
9010
  var error40 = () => {
9011
9011
  const Sizable = {
9012
9012
  string: { unit: "karakter", verb: "olmal\u0131" },
@@ -9111,7 +9111,7 @@ function tr_default() {
9111
9111
  localeError: error40()
9112
9112
  };
9113
9113
  }
9114
- // node_modules/zod/v4/locales/uk.js
9114
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uk.js
9115
9115
  var error41 = () => {
9116
9116
  const Sizable = {
9117
9117
  string: { unit: "\u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432", verb: "\u043C\u0430\u0442\u0438\u043C\u0435" },
@@ -9220,11 +9220,11 @@ function uk_default() {
9220
9220
  };
9221
9221
  }
9222
9222
 
9223
- // node_modules/zod/v4/locales/ua.js
9223
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ua.js
9224
9224
  function ua_default() {
9225
9225
  return uk_default();
9226
9226
  }
9227
- // node_modules/zod/v4/locales/ur.js
9227
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ur.js
9228
9228
  var error42 = () => {
9229
9229
  const Sizable = {
9230
9230
  string: { unit: "\u062D\u0631\u0648\u0641", verb: "\u06C1\u0648\u0646\u0627" },
@@ -9334,7 +9334,7 @@ function ur_default() {
9334
9334
  localeError: error42()
9335
9335
  };
9336
9336
  }
9337
- // node_modules/zod/v4/locales/uz.js
9337
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uz.js
9338
9338
  var error43 = () => {
9339
9339
  const Sizable = {
9340
9340
  string: { unit: "belgi", verb: "bo\u2018lishi kerak" },
@@ -9443,7 +9443,7 @@ function uz_default() {
9443
9443
  localeError: error43()
9444
9444
  };
9445
9445
  }
9446
- // node_modules/zod/v4/locales/vi.js
9446
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/vi.js
9447
9447
  var error44 = () => {
9448
9448
  const Sizable = {
9449
9449
  string: { unit: "k\xFD t\u1EF1", verb: "c\xF3" },
@@ -9551,7 +9551,7 @@ function vi_default() {
9551
9551
  localeError: error44()
9552
9552
  };
9553
9553
  }
9554
- // node_modules/zod/v4/locales/zh-CN.js
9554
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-CN.js
9555
9555
  var error45 = () => {
9556
9556
  const Sizable = {
9557
9557
  string: { unit: "\u5B57\u7B26", verb: "\u5305\u542B" },
@@ -9660,7 +9660,7 @@ function zh_CN_default() {
9660
9660
  localeError: error45()
9661
9661
  };
9662
9662
  }
9663
- // node_modules/zod/v4/locales/zh-TW.js
9663
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-TW.js
9664
9664
  var error46 = () => {
9665
9665
  const Sizable = {
9666
9666
  string: { unit: "\u5B57\u5143", verb: "\u64C1\u6709" },
@@ -9767,7 +9767,7 @@ function zh_TW_default() {
9767
9767
  localeError: error46()
9768
9768
  };
9769
9769
  }
9770
- // node_modules/zod/v4/locales/yo.js
9770
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/yo.js
9771
9771
  var error47 = () => {
9772
9772
  const Sizable = {
9773
9773
  string: { unit: "\xE0mi", verb: "n\xED" },
@@ -9874,7 +9874,7 @@ function yo_default() {
9874
9874
  localeError: error47()
9875
9875
  };
9876
9876
  }
9877
- // node_modules/zod/v4/core/registries.js
9877
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/registries.js
9878
9878
  var _a;
9879
9879
  var $output = Symbol("ZodOutput");
9880
9880
  var $input = Symbol("ZodInput");
@@ -9924,7 +9924,7 @@ function registry() {
9924
9924
  }
9925
9925
  (_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
9926
9926
  var globalRegistry = globalThis.__zod_globalRegistry;
9927
- // node_modules/zod/v4/core/api.js
9927
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/api.js
9928
9928
  function _string(Class2, params) {
9929
9929
  return new Class2({
9930
9930
  type: "string",
@@ -10844,7 +10844,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
10844
10844
  const inst = new Class2(def);
10845
10845
  return inst;
10846
10846
  }
10847
- // node_modules/zod/v4/core/to-json-schema.js
10847
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/to-json-schema.js
10848
10848
  function initializeContext(params) {
10849
10849
  let target = params?.target ?? "draft-2020-12";
10850
10850
  if (target === "draft-4")
@@ -11189,7 +11189,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
11189
11189
  extractDefs(ctx, schema);
11190
11190
  return finalize(ctx, schema);
11191
11191
  };
11192
- // node_modules/zod/v4/core/json-schema-processors.js
11192
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-processors.js
11193
11193
  var formatMap = {
11194
11194
  guid: "uuid",
11195
11195
  url: "uri",
@@ -11734,7 +11734,7 @@ function toJSONSchema(input, params) {
11734
11734
  extractDefs(ctx, input);
11735
11735
  return finalize(ctx, input);
11736
11736
  }
11737
- // node_modules/zod/v4/core/json-schema-generator.js
11737
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-generator.js
11738
11738
  class JSONSchemaGenerator {
11739
11739
  get metadataRegistry() {
11740
11740
  return this.ctx.metadataRegistry;
@@ -11793,9 +11793,9 @@ class JSONSchemaGenerator {
11793
11793
  return plainResult;
11794
11794
  }
11795
11795
  }
11796
- // node_modules/zod/v4/core/json-schema.js
11796
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema.js
11797
11797
  var exports_json_schema = {};
11798
- // node_modules/zod/v4/classic/schemas.js
11798
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
11799
11799
  var exports_schemas2 = {};
11800
11800
  __export(exports_schemas2, {
11801
11801
  xor: () => xor,
@@ -11964,7 +11964,7 @@ __export(exports_schemas2, {
11964
11964
  ZodAny: () => ZodAny
11965
11965
  });
11966
11966
 
11967
- // node_modules/zod/v4/classic/checks.js
11967
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/checks.js
11968
11968
  var exports_checks2 = {};
11969
11969
  __export(exports_checks2, {
11970
11970
  uppercase: () => _uppercase,
@@ -11998,7 +11998,7 @@ __export(exports_checks2, {
11998
11998
  endsWith: () => _endsWith
11999
11999
  });
12000
12000
 
12001
- // node_modules/zod/v4/classic/iso.js
12001
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/iso.js
12002
12002
  var exports_iso = {};
12003
12003
  __export(exports_iso, {
12004
12004
  time: () => time2,
@@ -12039,7 +12039,7 @@ function duration2(params) {
12039
12039
  return _isoDuration(ZodISODuration, params);
12040
12040
  }
12041
12041
 
12042
- // node_modules/zod/v4/classic/errors.js
12042
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/errors.js
12043
12043
  var initializer2 = (inst, issues) => {
12044
12044
  $ZodError.init(inst, issues);
12045
12045
  inst.name = "ZodError";
@@ -12074,7 +12074,7 @@ var ZodRealError = $constructor("ZodError", initializer2, {
12074
12074
  Parent: Error
12075
12075
  });
12076
12076
 
12077
- // node_modules/zod/v4/classic/parse.js
12077
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/parse.js
12078
12078
  var parse3 = /* @__PURE__ */ _parse(ZodRealError);
12079
12079
  var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
12080
12080
  var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -12088,7 +12088,7 @@ var safeDecode2 = /* @__PURE__ */ _safeDecode(ZodRealError);
12088
12088
  var safeEncodeAsync2 = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
12089
12089
  var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
12090
12090
 
12091
- // node_modules/zod/v4/classic/schemas.js
12091
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
12092
12092
  var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
12093
12093
  $ZodType.init(inst, def);
12094
12094
  Object.assign(inst["~standard"], {
@@ -13164,7 +13164,7 @@ function json(params) {
13164
13164
  function preprocess(fn, schema) {
13165
13165
  return pipe(transform(fn), schema);
13166
13166
  }
13167
- // node_modules/zod/v4/classic/compat.js
13167
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/compat.js
13168
13168
  var ZodIssueCode = {
13169
13169
  invalid_type: "invalid_type",
13170
13170
  too_big: "too_big",
@@ -13188,7 +13188,7 @@ function getErrorMap() {
13188
13188
  }
13189
13189
  var ZodFirstPartyTypeKind;
13190
13190
  (function(ZodFirstPartyTypeKind2) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
13191
- // node_modules/zod/v4/classic/from-json-schema.js
13191
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/from-json-schema.js
13192
13192
  var z = {
13193
13193
  ...exports_schemas2,
13194
13194
  ...exports_checks2,
@@ -13649,7 +13649,7 @@ function fromJSONSchema(schema, params) {
13649
13649
  };
13650
13650
  return convertSchema(schema, ctx);
13651
13651
  }
13652
- // node_modules/zod/v4/classic/coerce.js
13652
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/coerce.js
13653
13653
  var exports_coerce = {};
13654
13654
  __export(exports_coerce, {
13655
13655
  string: () => string3,
@@ -13674,7 +13674,7 @@ function date4(params) {
13674
13674
  return _coercedDate(ZodDate, params);
13675
13675
  }
13676
13676
 
13677
- // node_modules/zod/v4/classic/external.js
13677
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
13678
13678
  config(en_default());
13679
13679
  // src/features/magic-context/defaults.ts
13680
13680
  var DEFAULT_PROTECTED_TAGS = 20;
@@ -13731,7 +13731,8 @@ var DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.objec
13731
13731
  schedule: exports_external.string().default("02:00-06:00"),
13732
13732
  max_runtime_minutes: exports_external.number().min(10).default(120),
13733
13733
  tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
13734
- task_timeout_minutes: exports_external.number().min(5).default(20)
13734
+ task_timeout_minutes: exports_external.number().min(5).default(20),
13735
+ inject_docs: exports_external.boolean().default(true)
13735
13736
  }));
13736
13737
  var SidekickConfigSchema = AgentOverrideConfigSchema.extend({
13737
13738
  enabled: exports_external.boolean().default(false),
@@ -13788,7 +13789,7 @@ var MagicContextConfigSchema = exports_external.object({
13788
13789
  exports_external.number().min(35).max(95),
13789
13790
  exports_external.object({ default: exports_external.number().min(35).max(95) }).catchall(exports_external.number().min(35).max(95))
13790
13791
  ]).default(DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE),
13791
- protected_tags: exports_external.number().min(1).max(20).optional(),
13792
+ protected_tags: exports_external.number().min(1).max(100).optional(),
13792
13793
  auto_drop_tool_age: exports_external.number().min(10).default(100),
13793
13794
  clear_reasoning_age: exports_external.number().min(10).default(50),
13794
13795
  iteration_nudge_threshold: exports_external.number().min(5).default(15),
@@ -15036,6 +15037,8 @@ function releaseLease(db, holderId) {
15036
15037
  function enqueueDream(db, projectIdentity, reason) {
15037
15038
  const now = Date.now();
15038
15039
  return db.transaction(() => {
15040
+ const staleThresholdMs = 10 * 60 * 1000;
15041
+ db.run("DELETE FROM dream_queue WHERE project_path = ? AND started_at IS NOT NULL AND started_at < ?", [projectIdentity, now - staleThresholdMs]);
15039
15042
  const existing = db.query("SELECT id FROM dream_queue WHERE project_path = ?").get(projectIdentity);
15040
15043
  if (existing) {
15041
15044
  return null;
@@ -15098,6 +15101,60 @@ function getErrorMessage(error48) {
15098
15101
  return error48 instanceof Error ? error48.message : String(error48);
15099
15102
  }
15100
15103
 
15104
+ // src/features/magic-context/storage-smart-notes.ts
15105
+ function isSmartNoteRow(row) {
15106
+ if (row === null || typeof row !== "object")
15107
+ return false;
15108
+ const r = row;
15109
+ return typeof r.id === "number" && typeof r.project_path === "string" && typeof r.content === "string" && typeof r.surface_condition === "string" && typeof r.status === "string" && typeof r.created_at === "number" && typeof r.updated_at === "number";
15110
+ }
15111
+ function toSmartNote(row) {
15112
+ return {
15113
+ id: row.id,
15114
+ projectPath: row.project_path,
15115
+ content: row.content,
15116
+ surfaceCondition: row.surface_condition,
15117
+ status: row.status,
15118
+ createdSessionId: row.created_session_id && row.created_session_id.length > 0 ? row.created_session_id : null,
15119
+ createdAt: row.created_at,
15120
+ updatedAt: row.updated_at,
15121
+ lastCheckedAt: row.last_checked_at,
15122
+ readyAt: row.ready_at,
15123
+ readyReason: row.ready_reason && row.ready_reason.length > 0 ? row.ready_reason : null
15124
+ };
15125
+ }
15126
+ function addSmartNote(db, projectPath, content, surfaceCondition, sessionId) {
15127
+ const now = Date.now();
15128
+ const result = db.prepare("INSERT INTO smart_notes (project_path, content, surface_condition, status, created_session_id, created_at, updated_at) VALUES (?, ?, ?, 'pending', ?, ?, ?) RETURNING *").get(projectPath, content, surfaceCondition, sessionId ?? null, now, now);
15129
+ if (!isSmartNoteRow(result)) {
15130
+ throw new Error("[smart-notes] failed to insert smart note");
15131
+ }
15132
+ return toSmartNote(result);
15133
+ }
15134
+ function getSmartNotes(db, projectPath, status) {
15135
+ const query = status ? "SELECT * FROM smart_notes WHERE project_path = ? AND status = ? ORDER BY created_at ASC" : "SELECT * FROM smart_notes WHERE project_path = ? AND status != 'dismissed' ORDER BY created_at ASC";
15136
+ const params = status ? [projectPath, status] : [projectPath];
15137
+ return db.prepare(query).all(...params).filter(isSmartNoteRow).map(toSmartNote);
15138
+ }
15139
+ function getPendingSmartNotes(db, projectPath) {
15140
+ return getSmartNotes(db, projectPath, "pending");
15141
+ }
15142
+ function getReadySmartNotes(db, projectPath) {
15143
+ return getSmartNotes(db, projectPath, "ready");
15144
+ }
15145
+ function markSmartNoteReady(db, noteId, readyReason) {
15146
+ const now = Date.now();
15147
+ db.prepare("UPDATE smart_notes SET status = 'ready', ready_at = ?, ready_reason = ?, updated_at = ?, last_checked_at = ? WHERE id = ?").run(now, readyReason ?? null, now, now, noteId);
15148
+ }
15149
+ function markSmartNoteChecked(db, noteId) {
15150
+ const now = Date.now();
15151
+ db.prepare("UPDATE smart_notes SET last_checked_at = ?, updated_at = ? WHERE id = ?").run(now, now, noteId);
15152
+ }
15153
+ function dismissSmartNote(db, noteId) {
15154
+ const result = db.prepare("UPDATE smart_notes SET status = 'dismissed', updated_at = ? WHERE id = ?").run(Date.now(), noteId);
15155
+ return result.changes > 0;
15156
+ }
15157
+
15101
15158
  // src/features/magic-context/dreamer/runner.ts
15102
15159
  var dreamProjectDirectories = new Map;
15103
15160
  function registerDreamProjectDirectory(projectIdentity, directory) {
@@ -15247,12 +15304,28 @@ async function runDream(args) {
15247
15304
  }
15248
15305
  }
15249
15306
  }
15307
+ if (Date.now() <= deadline) {
15308
+ try {
15309
+ await evaluateSmartNotes({
15310
+ db: args.db,
15311
+ client: args.client,
15312
+ projectIdentity: args.projectIdentity,
15313
+ parentSessionId,
15314
+ sessionDirectory: args.sessionDirectory,
15315
+ holderId,
15316
+ deadline,
15317
+ result
15318
+ });
15319
+ } catch (error48) {
15320
+ log(`[dreamer] smart note evaluation failed: ${getErrorMessage(error48)}`);
15321
+ }
15322
+ }
15250
15323
  } finally {
15251
15324
  releaseLease(args.db, holderId);
15252
15325
  log(`[dreamer] lease released: ${holderId}`);
15253
15326
  }
15254
15327
  result.finishedAt = Date.now();
15255
- const hasSuccessfulTask = result.tasks.some((t) => !t.error);
15328
+ const hasSuccessfulTask = result.tasks.some((t) => !t.error && t.name !== "smart-notes");
15256
15329
  if (hasSuccessfulTask) {
15257
15330
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
15258
15331
  setDreamState(args.db, "last_dream_at", String(result.finishedAt));
@@ -15263,6 +15336,141 @@ async function runDream(args) {
15263
15336
  log(`[dreamer] dream run finished in ${totalDuration}s: ${succeeded} succeeded, ${failed} failed`);
15264
15337
  return result;
15265
15338
  }
15339
+ async function evaluateSmartNotes(args) {
15340
+ const pendingNotes = getPendingSmartNotes(args.db, args.projectIdentity);
15341
+ if (pendingNotes.length === 0) {
15342
+ log("[dreamer] smart notes: no pending notes to evaluate");
15343
+ return;
15344
+ }
15345
+ log(`[dreamer] smart notes: evaluating ${pendingNotes.length} pending note(s)`);
15346
+ const noteDescriptions = pendingNotes.map((n) => `- Note #${n.id}: "${n.content}"
15347
+ Condition: ${n.surfaceCondition}`).join(`
15348
+ `);
15349
+ const evaluationPrompt = `You are evaluating smart note conditions for the magic-context system.
15350
+
15351
+ For each note below, determine whether its surface condition has been met.
15352
+ You have access to tools like GitHub CLI (gh), web search, and the local codebase to verify conditions.
15353
+
15354
+ ## Pending Smart Notes
15355
+
15356
+ ${noteDescriptions}
15357
+
15358
+ ## Instructions
15359
+
15360
+ 1. Check each condition using the tools available to you.
15361
+ 2. Be conservative \u2014 only mark a condition as met when you have clear evidence.
15362
+ 3. Respond with a JSON array of results:
15363
+
15364
+ \`\`\`json
15365
+ [
15366
+ { "id": <note_id>, "met": true/false, "reason": "brief explanation" }
15367
+ ]
15368
+ \`\`\`
15369
+
15370
+ Only include notes whose conditions you could definitively evaluate. Skip notes where you cannot determine the status (they will be re-evaluated next run).`;
15371
+ const taskStartedAt = Date.now();
15372
+ let agentSessionId = null;
15373
+ const abortController = new AbortController;
15374
+ const leaseInterval = setInterval(() => {
15375
+ try {
15376
+ if (!renewLease(args.db, args.holderId)) {
15377
+ log("[dreamer] smart notes: lease renewal failed \u2014 aborting");
15378
+ abortController.abort();
15379
+ }
15380
+ } catch {
15381
+ abortController.abort();
15382
+ }
15383
+ }, 60000);
15384
+ try {
15385
+ const createResponse = await args.client.session.create({
15386
+ body: {
15387
+ ...args.parentSessionId ? { parentID: args.parentSessionId } : {},
15388
+ title: "magic-context-dream-smart-notes"
15389
+ },
15390
+ query: { directory: args.sessionDirectory ?? args.projectIdentity }
15391
+ });
15392
+ const created = normalizeSDKResponse(createResponse, null, { preferResponseOnMissingData: true });
15393
+ agentSessionId = typeof created?.id === "string" ? created.id : null;
15394
+ if (!agentSessionId)
15395
+ throw new Error("Could not create smart note evaluation session.");
15396
+ log(`[dreamer] smart notes: child session created ${agentSessionId}`);
15397
+ const remainingMs = Math.max(0, args.deadline - Date.now());
15398
+ await promptSyncWithModelSuggestionRetry(args.client, {
15399
+ path: { id: agentSessionId },
15400
+ query: { directory: args.sessionDirectory ?? args.projectIdentity },
15401
+ body: {
15402
+ agent: DREAMER_AGENT,
15403
+ system: DREAMER_SYSTEM_PROMPT,
15404
+ parts: [{ type: "text", text: evaluationPrompt }]
15405
+ }
15406
+ }, { timeoutMs: Math.min(remainingMs, 5 * 60 * 1000), signal: abortController.signal });
15407
+ const messagesResponse = await args.client.session.messages({
15408
+ path: { id: agentSessionId },
15409
+ query: { directory: args.sessionDirectory ?? args.projectIdentity }
15410
+ });
15411
+ const messages = normalizeSDKResponse(messagesResponse, [], {
15412
+ preferResponseOnMissingData: true
15413
+ });
15414
+ const output = extractLatestAssistantText(messages);
15415
+ if (!output)
15416
+ throw new Error("Smart note evaluation returned no output.");
15417
+ const jsonMatch = output.match(/\[[\s\S]*?\]/);
15418
+ if (!jsonMatch) {
15419
+ log("[dreamer] smart notes: no JSON array found in output, skipping");
15420
+ for (const note of pendingNotes)
15421
+ markSmartNoteChecked(args.db, note.id);
15422
+ return;
15423
+ }
15424
+ const evaluations = JSON.parse(jsonMatch[0]);
15425
+ let surfaced = 0;
15426
+ let checked = 0;
15427
+ for (const evaluation of evaluations) {
15428
+ if (typeof evaluation.id !== "number")
15429
+ continue;
15430
+ const note = pendingNotes.find((n) => n.id === evaluation.id);
15431
+ if (!note)
15432
+ continue;
15433
+ if (evaluation.met) {
15434
+ markSmartNoteReady(args.db, note.id, evaluation.reason);
15435
+ surfaced++;
15436
+ log(`[dreamer] smart notes: #${note.id} condition MET \u2014 "${evaluation.reason ?? "condition satisfied"}"`);
15437
+ } else {
15438
+ markSmartNoteChecked(args.db, note.id);
15439
+ checked++;
15440
+ }
15441
+ }
15442
+ for (const note of pendingNotes) {
15443
+ if (!evaluations.some((e) => e.id === note.id)) {
15444
+ markSmartNoteChecked(args.db, note.id);
15445
+ }
15446
+ }
15447
+ const durationMs = Date.now() - taskStartedAt;
15448
+ log(`[dreamer] smart notes: evaluated ${pendingNotes.length} notes in ${(durationMs / 1000).toFixed(1)}s \u2014 ${surfaced} surfaced, ${checked} still pending`);
15449
+ args.result.tasks.push({
15450
+ name: "smart-notes",
15451
+ durationMs,
15452
+ result: `${surfaced} surfaced, ${checked} still pending`
15453
+ });
15454
+ } catch (error48) {
15455
+ const durationMs = Date.now() - taskStartedAt;
15456
+ const errorMsg = getErrorMessage(error48);
15457
+ log(`[dreamer] smart notes: failed after ${(durationMs / 1000).toFixed(1)}s \u2014 ${errorMsg}`);
15458
+ args.result.tasks.push({
15459
+ name: "smart-notes",
15460
+ durationMs,
15461
+ result: null,
15462
+ error: errorMsg
15463
+ });
15464
+ } finally {
15465
+ clearInterval(leaseInterval);
15466
+ if (agentSessionId) {
15467
+ await args.client.session.delete({
15468
+ path: { id: agentSessionId },
15469
+ query: { directory: args.sessionDirectory ?? args.projectIdentity }
15470
+ }).catch(() => {});
15471
+ }
15472
+ }
15473
+ }
15266
15474
  var MAX_LEASE_RETRIES = 3;
15267
15475
  async function processDreamQueue(args) {
15268
15476
  const maxRuntimeMs = args.maxRuntimeMinutes * 60 * 1000;
@@ -15363,9 +15571,20 @@ function checkScheduleAndEnqueue(db, schedule) {
15363
15571
  }
15364
15572
  return enqueued;
15365
15573
  }
15366
- // src/features/magic-context/storage-db.ts
15574
+ // src/shared/internal-initiator-marker.ts
15575
+ var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
15576
+
15577
+ // src/shared/system-directive.ts
15578
+ var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: MAGIC-CONTEXT";
15579
+ function isSystemDirective(text) {
15580
+ return text.trimStart().startsWith(SYSTEM_DIRECTIVE_PREFIX);
15581
+ }
15582
+ function removeSystemReminders(text) {
15583
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
15584
+ }
15585
+
15586
+ // src/hooks/magic-context/read-session-db.ts
15367
15587
  import { Database } from "bun:sqlite";
15368
- import { mkdirSync } from "fs";
15369
15588
  import { join as join5 } from "path";
15370
15589
 
15371
15590
  // src/shared/data-path.ts
@@ -15378,979 +15597,1049 @@ function getOpenCodeStorageDir() {
15378
15597
  return path2.join(getDataDir(), "opencode", "storage");
15379
15598
  }
15380
15599
 
15381
- // src/features/magic-context/storage-db.ts
15382
- var databases = new Map;
15383
- var FALLBACK_DATABASE_KEY = "__fallback__:memory:";
15384
- var persistenceByDatabase = new WeakMap;
15385
- var persistenceErrorByDatabase = new WeakMap;
15386
- function resolveDatabasePath() {
15387
- const dbDir = join5(getOpenCodeStorageDir(), "plugin", "magic-context");
15388
- return { dbDir, dbPath: join5(dbDir, "context.db") };
15600
+ // src/hooks/magic-context/read-session-db.ts
15601
+ function getOpenCodeDbPath() {
15602
+ return join5(getDataDir(), "opencode", "opencode.db");
15603
+ }
15604
+ var cachedReadOnlyDb = null;
15605
+ function closeCachedReadOnlyDb() {
15606
+ if (!cachedReadOnlyDb) {
15607
+ return;
15608
+ }
15609
+ try {
15610
+ cachedReadOnlyDb.db.close(false);
15611
+ } catch (error48) {
15612
+ log("[magic-context] failed to close cached OpenCode read-only DB:", error48);
15613
+ } finally {
15614
+ cachedReadOnlyDb = null;
15615
+ }
15616
+ }
15617
+ function getReadOnlySessionDb() {
15618
+ const dbPath = getOpenCodeDbPath();
15619
+ if (cachedReadOnlyDb?.path === dbPath) {
15620
+ return cachedReadOnlyDb.db;
15621
+ }
15622
+ closeCachedReadOnlyDb();
15623
+ const db = new Database(dbPath, { readonly: true });
15624
+ cachedReadOnlyDb = { path: dbPath, db };
15625
+ return db;
15626
+ }
15627
+ function withReadOnlySessionDb(fn) {
15628
+ return fn(getReadOnlySessionDb());
15629
+ }
15630
+ function getRawSessionMessageCountFromDb(db, sessionId) {
15631
+ const row = db.prepare("SELECT COUNT(*) as count FROM message WHERE session_id = ?").get(sessionId);
15632
+ return typeof row?.count === "number" ? row.count : 0;
15389
15633
  }
15390
- function initializeDatabase(db) {
15391
- db.run("PRAGMA journal_mode=WAL");
15392
- db.run("PRAGMA busy_timeout=5000");
15393
- db.run("PRAGMA foreign_keys=ON");
15394
- db.run(`
15395
- CREATE TABLE IF NOT EXISTS tags (
15396
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15397
- session_id TEXT,
15398
- message_id TEXT,
15399
- type TEXT,
15400
- status TEXT DEFAULT 'active',
15401
- byte_size INTEGER,
15402
- tag_number INTEGER,
15403
- UNIQUE(session_id, tag_number)
15404
- );
15405
-
15406
- CREATE TABLE IF NOT EXISTS pending_ops (
15407
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15408
- session_id TEXT,
15409
- tag_id INTEGER,
15410
- operation TEXT,
15411
- queued_at INTEGER
15412
- );
15413
15634
 
15414
- CREATE TABLE IF NOT EXISTS source_contents (
15415
- tag_id INTEGER,
15416
- session_id TEXT,
15417
- content TEXT,
15418
- created_at INTEGER,
15419
- PRIMARY KEY(session_id, tag_id)
15420
- );
15635
+ // src/hooks/magic-context/read-session-formatting.ts
15636
+ var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
15637
+ var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
15638
+ var MAX_COMMITS_PER_BLOCK = 5;
15639
+ function hasMeaningfulUserText(parts) {
15640
+ for (const part of parts) {
15641
+ if (part === null || typeof part !== "object")
15642
+ continue;
15643
+ const candidate = part;
15644
+ if (candidate.type !== "text" || typeof candidate.text !== "string")
15645
+ continue;
15646
+ if (candidate.ignored === true)
15647
+ continue;
15648
+ const cleaned = removeSystemReminders(candidate.text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
15649
+ if (!cleaned)
15650
+ continue;
15651
+ if (isSystemDirective(cleaned))
15652
+ continue;
15653
+ return true;
15654
+ }
15655
+ return false;
15656
+ }
15657
+ function extractTexts(parts) {
15658
+ const texts = [];
15659
+ for (const part of parts) {
15660
+ if (part === null || typeof part !== "object")
15661
+ continue;
15662
+ const p = part;
15663
+ if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
15664
+ texts.push(p.text.trim());
15665
+ }
15666
+ }
15667
+ return texts;
15668
+ }
15669
+ function estimateTokens(text) {
15670
+ return Math.ceil(text.length / 3.5);
15671
+ }
15672
+ function normalizeText(text) {
15673
+ return text.replace(/\s+/g, " ").trim();
15674
+ }
15675
+ function compactRole(role) {
15676
+ if (role === "assistant")
15677
+ return "A";
15678
+ if (role === "user")
15679
+ return "U";
15680
+ return role.slice(0, 1).toUpperCase() || "M";
15681
+ }
15682
+ function formatBlock(block) {
15683
+ const range = block.startOrdinal === block.endOrdinal ? `[${block.startOrdinal}]` : `[${block.startOrdinal}-${block.endOrdinal}]`;
15684
+ const commitSuffix = block.commitHashes.length > 0 ? ` commits: ${block.commitHashes.join(", ")}` : "";
15685
+ return `${range} ${block.role}:${commitSuffix} ${block.parts.join(" / ")}`;
15686
+ }
15687
+ function extractCommitHashes(text) {
15688
+ const hashes = [];
15689
+ const seen = new Set;
15690
+ for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
15691
+ const hash2 = match[1]?.toLowerCase();
15692
+ if (!hash2 || seen.has(hash2))
15693
+ continue;
15694
+ seen.add(hash2);
15695
+ hashes.push(hash2);
15696
+ if (hashes.length >= MAX_COMMITS_PER_BLOCK)
15697
+ break;
15698
+ }
15699
+ return hashes;
15700
+ }
15701
+ function compactTextForSummary(text, role) {
15702
+ const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
15703
+ if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
15704
+ return { text, commitHashes };
15705
+ }
15706
+ const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
15707
+ return {
15708
+ text: withoutHashes.length > 0 ? withoutHashes : text,
15709
+ commitHashes
15710
+ };
15711
+ }
15712
+ function mergeCommitHashes(existing, next) {
15713
+ if (next.length === 0)
15714
+ return existing;
15715
+ const merged = [...existing];
15716
+ for (const hash2 of next) {
15717
+ if (merged.includes(hash2))
15718
+ continue;
15719
+ merged.push(hash2);
15720
+ if (merged.length >= MAX_COMMITS_PER_BLOCK)
15721
+ break;
15722
+ }
15723
+ return merged;
15724
+ }
15421
15725
 
15422
- CREATE TABLE IF NOT EXISTS compartments (
15423
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15424
- session_id TEXT NOT NULL,
15425
- sequence INTEGER NOT NULL,
15426
- start_message INTEGER NOT NULL,
15427
- end_message INTEGER NOT NULL,
15428
- start_message_id TEXT DEFAULT '',
15429
- end_message_id TEXT DEFAULT '',
15430
- title TEXT NOT NULL,
15431
- content TEXT NOT NULL,
15432
- created_at INTEGER NOT NULL,
15433
- UNIQUE(session_id, sequence)
15434
- );
15435
-
15436
- CREATE TABLE IF NOT EXISTS session_facts (
15437
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15438
- session_id TEXT NOT NULL,
15439
- category TEXT NOT NULL,
15440
- content TEXT NOT NULL,
15441
- created_at INTEGER NOT NULL,
15442
- updated_at INTEGER NOT NULL
15443
- );
15444
-
15445
- CREATE TABLE IF NOT EXISTS session_notes (
15446
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15447
- session_id TEXT NOT NULL,
15448
- content TEXT NOT NULL,
15449
- created_at INTEGER NOT NULL
15450
- );
15451
-
15452
- CREATE TABLE IF NOT EXISTS memories (
15453
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15454
- project_path TEXT NOT NULL,
15455
- category TEXT NOT NULL,
15456
- content TEXT NOT NULL,
15457
- normalized_hash TEXT NOT NULL,
15458
- source_session_id TEXT,
15459
- source_type TEXT DEFAULT 'historian',
15460
- seen_count INTEGER DEFAULT 1,
15461
- retrieval_count INTEGER DEFAULT 0,
15462
- first_seen_at INTEGER NOT NULL,
15463
- created_at INTEGER NOT NULL,
15464
- updated_at INTEGER NOT NULL,
15465
- last_seen_at INTEGER NOT NULL,
15466
- last_retrieved_at INTEGER,
15467
- status TEXT DEFAULT 'active',
15468
- expires_at INTEGER,
15469
- verification_status TEXT DEFAULT 'unverified',
15470
- verified_at INTEGER,
15471
- superseded_by_memory_id INTEGER,
15472
- merged_from TEXT,
15473
- metadata_json TEXT,
15474
- UNIQUE(project_path, category, normalized_hash)
15475
- );
15476
-
15477
- CREATE TABLE IF NOT EXISTS memory_embeddings (
15478
- memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
15479
- embedding BLOB NOT NULL,
15480
- model_id TEXT
15481
- );
15482
-
15483
- CREATE TABLE IF NOT EXISTS dream_state (
15484
- key TEXT PRIMARY KEY,
15485
- value TEXT NOT NULL
15486
- );
15487
-
15488
- CREATE TABLE IF NOT EXISTS dream_queue (
15489
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15490
- project_path TEXT NOT NULL,
15491
- reason TEXT NOT NULL,
15492
- enqueued_at INTEGER NOT NULL,
15493
- started_at INTEGER,
15494
- retry_count INTEGER DEFAULT 0
15495
- );
15496
- CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
15497
- CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
15498
-
15499
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
15500
- content,
15501
- category,
15502
- content='memories',
15503
- content_rowid='id',
15504
- tokenize='porter unicode61'
15505
- );
15506
-
15507
- CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
15508
- session_id UNINDEXED,
15509
- message_ordinal UNINDEXED,
15510
- message_id UNINDEXED,
15511
- role,
15512
- content,
15513
- tokenize='porter unicode61'
15514
- );
15515
-
15516
- CREATE TABLE IF NOT EXISTS message_history_index (
15517
- session_id TEXT PRIMARY KEY,
15518
- last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
15519
- updated_at INTEGER NOT NULL
15520
- );
15521
-
15522
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
15523
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
15524
- END;
15525
-
15526
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
15527
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
15528
- END;
15529
-
15530
- CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
15531
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
15532
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
15533
- END;
15534
-
15535
- CREATE TABLE IF NOT EXISTS session_meta (
15536
- session_id TEXT PRIMARY KEY,
15537
- last_response_time INTEGER,
15538
- cache_ttl TEXT,
15539
- counter INTEGER DEFAULT 0,
15540
- last_nudge_tokens INTEGER DEFAULT 0,
15541
- last_nudge_band TEXT DEFAULT '',
15542
- last_transform_error TEXT DEFAULT '',
15543
- nudge_anchor_message_id TEXT DEFAULT '',
15544
- nudge_anchor_text TEXT DEFAULT '',
15545
- sticky_turn_reminder_text TEXT DEFAULT '',
15546
- sticky_turn_reminder_message_id TEXT DEFAULT '',
15547
- note_nudge_trigger_pending INTEGER DEFAULT 0,
15548
- note_nudge_trigger_message_id TEXT DEFAULT '',
15549
- note_nudge_sticky_text TEXT DEFAULT '',
15550
- note_nudge_sticky_message_id TEXT DEFAULT '',
15551
- is_subagent INTEGER DEFAULT 0,
15552
- last_context_percentage REAL DEFAULT 0,
15553
- last_input_tokens INTEGER DEFAULT 0,
15554
- times_execute_threshold_reached INTEGER DEFAULT 0,
15555
- compartment_in_progress INTEGER DEFAULT 0,
15556
- system_prompt_hash TEXT DEFAULT '',
15557
- memory_block_cache TEXT DEFAULT '',
15558
- memory_block_count INTEGER DEFAULT 0
15559
- );
15560
-
15561
- CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
15562
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
15563
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
15564
- CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
15565
-
15566
- CREATE TABLE IF NOT EXISTS recomp_compartments (
15567
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15568
- session_id TEXT NOT NULL,
15569
- sequence INTEGER NOT NULL,
15570
- start_message INTEGER NOT NULL,
15571
- end_message INTEGER NOT NULL,
15572
- start_message_id TEXT DEFAULT '',
15573
- end_message_id TEXT DEFAULT '',
15574
- title TEXT NOT NULL,
15575
- content TEXT NOT NULL,
15576
- pass_number INTEGER NOT NULL,
15577
- created_at INTEGER NOT NULL,
15578
- UNIQUE(session_id, sequence)
15579
- );
15580
-
15581
- CREATE TABLE IF NOT EXISTS recomp_facts (
15582
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15583
- session_id TEXT NOT NULL,
15584
- category TEXT NOT NULL,
15585
- content TEXT NOT NULL,
15586
- pass_number INTEGER NOT NULL,
15587
- created_at INTEGER NOT NULL
15588
- );
15589
-
15590
- CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
15591
- CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
15592
- CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
15593
- CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
15594
- CREATE INDEX IF NOT EXISTS idx_session_notes_session ON session_notes(session_id);
15595
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
15596
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
15597
- CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
15598
- CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
15599
- `);
15600
- ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
15601
- ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
15602
- ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
15603
- ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
15604
- ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
15605
- ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
15606
- ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
15607
- ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
15608
- ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
15609
- ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
15610
- ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
15611
- ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
15612
- ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
15613
- ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
15614
- ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
15615
- ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
15616
- ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
15617
- ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
15618
- ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
15619
- ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
15726
+ // src/hooks/magic-context/read-session-raw.ts
15727
+ function isRawMessageRow(row) {
15728
+ if (row === null || typeof row !== "object")
15729
+ return false;
15730
+ const candidate = row;
15731
+ return typeof candidate.id === "string" && typeof candidate.data === "string";
15620
15732
  }
15621
- function ensureColumn(db, table, column, definition) {
15622
- if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
15623
- throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
15624
- }
15625
- const rows = db.prepare(`PRAGMA table_info(${table})`).all();
15626
- if (rows.some((row) => row.name === column)) {
15627
- return;
15628
- }
15629
- db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
15733
+ function isRawPartRow(row) {
15734
+ if (row === null || typeof row !== "object")
15735
+ return false;
15736
+ const candidate = row;
15737
+ return typeof candidate.message_id === "string" && typeof candidate.data === "string";
15630
15738
  }
15631
- function createFallbackDatabase() {
15632
- try {
15633
- const fallback = new Database(":memory:");
15634
- initializeDatabase(fallback);
15635
- return fallback;
15636
- } catch (error48) {
15637
- throw new Error(`[magic-context] storage fatal: failed to initialize fallback database: ${getErrorMessage(error48)}`);
15739
+ function parseJsonRecord(value) {
15740
+ const parsed = JSON.parse(value);
15741
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15742
+ throw new Error("Expected JSON object");
15638
15743
  }
15744
+ return parsed;
15639
15745
  }
15640
- function openDatabase() {
15641
- try {
15642
- const { dbDir, dbPath } = resolveDatabasePath();
15643
- const existing = databases.get(dbPath);
15644
- if (existing) {
15645
- if (!persistenceByDatabase.has(existing)) {
15646
- persistenceByDatabase.set(existing, true);
15647
- }
15648
- return existing;
15649
- }
15650
- mkdirSync(dbDir, { recursive: true });
15651
- const db = new Database(dbPath);
15652
- initializeDatabase(db);
15653
- databases.set(dbPath, db);
15654
- persistenceByDatabase.set(db, true);
15655
- persistenceErrorByDatabase.delete(db);
15656
- return db;
15657
- } catch (error48) {
15658
- log("[magic-context] storage error:", error48);
15659
- const errorMessage = getErrorMessage(error48);
15660
- const existingFallback = databases.get(FALLBACK_DATABASE_KEY);
15661
- if (existingFallback) {
15662
- if (!persistenceByDatabase.has(existingFallback)) {
15663
- persistenceByDatabase.set(existingFallback, false);
15664
- persistenceErrorByDatabase.set(existingFallback, errorMessage);
15665
- }
15666
- return existingFallback;
15667
- }
15668
- const fallback = createFallbackDatabase();
15669
- databases.set(FALLBACK_DATABASE_KEY, fallback);
15670
- persistenceByDatabase.set(fallback, false);
15671
- persistenceErrorByDatabase.set(fallback, errorMessage);
15672
- return fallback;
15673
- }
15746
+ function parseJsonUnknown(value) {
15747
+ return JSON.parse(value);
15674
15748
  }
15675
- function isDatabasePersisted(db) {
15676
- return persistenceByDatabase.get(db) ?? false;
15677
- }
15678
- function getDatabasePersistenceError(db) {
15679
- return persistenceErrorByDatabase.get(db) ?? null;
15749
+ function readRawSessionMessagesFromDb(db, sessionId) {
15750
+ const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
15751
+ const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
15752
+ const partsByMessageId = new Map;
15753
+ for (const part of partRows) {
15754
+ const list = partsByMessageId.get(part.message_id) ?? [];
15755
+ list.push(parseJsonUnknown(part.data));
15756
+ partsByMessageId.set(part.message_id, list);
15757
+ }
15758
+ return messageRows.map((row, index) => {
15759
+ const info = parseJsonRecord(row.data);
15760
+ const role = typeof info.role === "string" ? info.role : "unknown";
15761
+ return {
15762
+ ordinal: index + 1,
15763
+ id: row.id,
15764
+ role,
15765
+ parts: partsByMessageId.get(row.id) ?? []
15766
+ };
15767
+ });
15680
15768
  }
15681
- // src/features/magic-context/storage-meta-shared.ts
15682
- var META_COLUMNS = {
15683
- lastResponseTime: "last_response_time",
15684
- cacheTtl: "cache_ttl",
15685
- counter: "counter",
15686
- lastNudgeTokens: "last_nudge_tokens",
15687
- lastNudgeBand: "last_nudge_band",
15688
- lastTransformError: "last_transform_error",
15689
- isSubagent: "is_subagent",
15690
- lastContextPercentage: "last_context_percentage",
15691
- lastInputTokens: "last_input_tokens",
15692
- timesExecuteThresholdReached: "times_execute_threshold_reached",
15693
- compartmentInProgress: "compartment_in_progress",
15694
- systemPromptHash: "system_prompt_hash",
15695
- clearedReasoningThroughTag: "cleared_reasoning_through_tag"
15696
- };
15697
- var BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
15698
- function isSessionMetaRow(row) {
15699
- if (row === null || typeof row !== "object")
15700
- return false;
15701
- const r = row;
15702
- return typeof r.session_id === "string" && typeof r.last_response_time === "number" && typeof r.cache_ttl === "string" && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && typeof r.last_nudge_band === "string" && typeof r.last_transform_error === "string" && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && typeof r.times_execute_threshold_reached === "number" && typeof r.compartment_in_progress === "number" && (typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && typeof r.cleared_reasoning_through_tag === "number";
15769
+
15770
+ // src/hooks/magic-context/tag-content-primitives.ts
15771
+ var encoder = new TextEncoder;
15772
+ var TAG_PREFIX_REGEX = /^\u00A7\d+\u00A7\s*/;
15773
+ function byteSize(value) {
15774
+ return encoder.encode(value).length;
15703
15775
  }
15704
- function getDefaultSessionMeta(sessionId) {
15705
- return {
15706
- sessionId,
15707
- lastResponseTime: 0,
15708
- cacheTtl: "5m",
15709
- counter: 0,
15710
- lastNudgeTokens: 0,
15711
- lastNudgeBand: null,
15712
- lastTransformError: null,
15713
- isSubagent: false,
15714
- lastContextPercentage: 0,
15715
- lastInputTokens: 0,
15716
- timesExecuteThresholdReached: 0,
15717
- compartmentInProgress: false,
15718
- systemPromptHash: "",
15719
- clearedReasoningThroughTag: 0
15720
- };
15776
+ function stripTagPrefix(value) {
15777
+ return value.replace(TAG_PREFIX_REGEX, "");
15721
15778
  }
15722
- function ensureSessionMetaRow(db, sessionId) {
15723
- const defaults = getDefaultSessionMeta(sessionId);
15724
- db.prepare("INSERT OR IGNORE INTO session_meta (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, cleared_reasoning_through_tag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(sessionId, defaults.lastResponseTime, defaults.cacheTtl, defaults.counter, defaults.lastNudgeTokens, defaults.lastNudgeBand ?? "", defaults.lastTransformError ?? "", defaults.isSubagent ? 1 : 0, defaults.lastContextPercentage, defaults.lastInputTokens, defaults.timesExecuteThresholdReached, defaults.compartmentInProgress ? 1 : 0, defaults.systemPromptHash ?? "", defaults.clearedReasoningThroughTag);
15779
+ function prependTag(tagId, value) {
15780
+ const stripped = stripTagPrefix(value);
15781
+ return `\xA7${tagId}\xA7 ${stripped}`;
15725
15782
  }
15726
- function toSessionMeta(row) {
15727
- return {
15728
- sessionId: row.session_id,
15729
- lastResponseTime: row.last_response_time,
15730
- cacheTtl: row.cache_ttl,
15731
- counter: row.counter,
15732
- lastNudgeTokens: row.last_nudge_tokens,
15733
- lastNudgeBand: row.last_nudge_band.length > 0 ? row.last_nudge_band : null,
15734
- lastTransformError: row.last_transform_error.length > 0 ? row.last_transform_error : null,
15735
- isSubagent: row.is_subagent === 1,
15736
- lastContextPercentage: row.last_context_percentage,
15737
- lastInputTokens: row.last_input_tokens,
15738
- timesExecuteThresholdReached: row.times_execute_threshold_reached,
15739
- compartmentInProgress: row.compartment_in_progress === 1,
15740
- systemPromptHash: String(row.system_prompt_hash),
15741
- clearedReasoningThroughTag: row.cleared_reasoning_through_tag
15742
- };
15783
+ function isThinkingPart(part) {
15784
+ if (part === null || typeof part !== "object")
15785
+ return false;
15786
+ const candidate = part;
15787
+ return candidate.type === "thinking" || candidate.type === "reasoning";
15743
15788
  }
15744
15789
 
15745
- // src/features/magic-context/storage-meta-persisted.ts
15746
- function isPersistedUsageRow(row) {
15747
- if (row === null || typeof row !== "object")
15790
+ // src/hooks/magic-context/tag-part-guards.ts
15791
+ function isTextPart(part) {
15792
+ if (part === null || typeof part !== "object")
15748
15793
  return false;
15749
- const r = row;
15750
- return typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && typeof r.last_response_time === "number";
15794
+ const p = part;
15795
+ return p.type === "text" && typeof p.text === "string";
15751
15796
  }
15752
- function isPersistedNudgePlacementRow(row) {
15753
- if (row === null || typeof row !== "object")
15797
+ function isToolPartWithOutput(part) {
15798
+ if (part === null || typeof part !== "object")
15754
15799
  return false;
15755
- const r = row;
15756
- return typeof r.nudge_anchor_message_id === "string" && typeof r.nudge_anchor_text === "string";
15757
- }
15758
- function isPersistedStickyTurnReminderRow(row) {
15759
- if (row === null || typeof row !== "object")
15800
+ const p = part;
15801
+ if (p.type !== "tool" || typeof p.callID !== "string")
15760
15802
  return false;
15761
- const r = row;
15762
- return typeof r.sticky_turn_reminder_text === "string" && typeof r.sticky_turn_reminder_message_id === "string";
15803
+ if (p.state === null || typeof p.state !== "object")
15804
+ return false;
15805
+ return typeof p.state.output === "string";
15763
15806
  }
15764
- function isPersistedNoteNudgeRow(row) {
15765
- if (row === null || typeof row !== "object")
15807
+ function isFilePart(part) {
15808
+ if (part === null || typeof part !== "object")
15766
15809
  return false;
15767
- const r = row;
15768
- 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";
15810
+ const p = part;
15811
+ return p.type === "file" && typeof p.url === "string";
15769
15812
  }
15770
- function getDefaultPersistedNoteNudge() {
15771
- return {
15772
- triggerPending: false,
15773
- triggerMessageId: null,
15774
- stickyText: null,
15775
- stickyMessageId: null
15776
- };
15813
+ function buildFileSourceContent(parts) {
15814
+ const content = parts.filter(isTextPart).map((part) => stripTagPrefix(part.text)).join(`
15815
+ `).trim();
15816
+ return content.length > 0 ? content : null;
15777
15817
  }
15778
- function loadPersistedUsage(db, sessionId) {
15779
- const result = db.prepare("SELECT last_context_percentage, last_input_tokens, last_response_time FROM session_meta WHERE session_id = ?").get(sessionId);
15780
- if (!isPersistedUsageRow(result) || result.last_context_percentage === 0 && result.last_input_tokens === 0) {
15781
- return null;
15782
- }
15783
- return {
15784
- usage: {
15785
- percentage: result.last_context_percentage,
15786
- inputTokens: result.last_input_tokens
15787
- },
15788
- updatedAt: result.last_response_time || Date.now()
15789
- };
15818
+
15819
+ // src/hooks/magic-context/read-session-chunk.ts
15820
+ var activeRawMessageCache = null;
15821
+ function cleanUserText(text) {
15822
+ return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
15790
15823
  }
15791
- function getPersistedNudgePlacement(db, sessionId) {
15792
- const result = db.prepare("SELECT nudge_anchor_message_id, nudge_anchor_text FROM session_meta WHERE session_id = ?").get(sessionId);
15793
- if (!isPersistedNudgePlacementRow(result)) {
15794
- return null;
15824
+ function withRawSessionMessageCache(fn) {
15825
+ const outerCache = activeRawMessageCache;
15826
+ if (!outerCache) {
15827
+ activeRawMessageCache = new Map;
15795
15828
  }
15796
- if (result.nudge_anchor_message_id.length === 0 || result.nudge_anchor_text.length === 0) {
15797
- return null;
15829
+ try {
15830
+ return fn();
15831
+ } finally {
15832
+ if (!outerCache) {
15833
+ activeRawMessageCache = null;
15834
+ }
15798
15835
  }
15799
- return {
15800
- messageId: result.nudge_anchor_message_id,
15801
- nudgeText: result.nudge_anchor_text
15802
- };
15803
- }
15804
- function setPersistedNudgePlacement(db, sessionId, messageId, nudgeText) {
15805
- db.transaction(() => {
15806
- ensureSessionMetaRow(db, sessionId);
15807
- db.prepare("UPDATE session_meta SET nudge_anchor_message_id = ?, nudge_anchor_text = ? WHERE session_id = ?").run(messageId, nudgeText, sessionId);
15808
- })();
15809
- }
15810
- function clearPersistedNudgePlacement(db, sessionId) {
15811
- db.prepare("UPDATE session_meta SET nudge_anchor_message_id = '', nudge_anchor_text = '' WHERE session_id = ?").run(sessionId);
15812
15836
  }
15813
- function getPersistedStickyTurnReminder(db, sessionId) {
15814
- const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
15815
- if (!isPersistedStickyTurnReminderRow(result)) {
15816
- return null;
15817
- }
15818
- if (result.sticky_turn_reminder_text.length === 0) {
15819
- return null;
15837
+ function readRawSessionMessages(sessionId) {
15838
+ if (activeRawMessageCache) {
15839
+ const cached2 = activeRawMessageCache.get(sessionId);
15840
+ if (cached2) {
15841
+ return cached2;
15842
+ }
15843
+ const messages = withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
15844
+ activeRawMessageCache.set(sessionId, messages);
15845
+ return messages;
15820
15846
  }
15821
- return {
15822
- text: result.sticky_turn_reminder_text,
15823
- messageId: result.sticky_turn_reminder_message_id.length > 0 ? result.sticky_turn_reminder_message_id : null
15824
- };
15847
+ return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
15825
15848
  }
15826
- function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
15827
- db.transaction(() => {
15828
- ensureSessionMetaRow(db, sessionId);
15829
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = ?, sticky_turn_reminder_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
15830
- })();
15849
+ function getRawSessionMessageCount(sessionId) {
15850
+ return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
15831
15851
  }
15832
- function clearPersistedStickyTurnReminder(db, sessionId) {
15833
- db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
15852
+ function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
15853
+ const messages = readRawSessionMessages(sessionId);
15854
+ const keys = [];
15855
+ for (const message of messages) {
15856
+ if (message.ordinal > upToMessageIndex)
15857
+ break;
15858
+ for (const [partIndex, part] of message.parts.entries()) {
15859
+ if (isTextPart(part)) {
15860
+ keys.push(`${message.id}:p${partIndex}`);
15861
+ }
15862
+ if (isFilePart(part)) {
15863
+ keys.push(`${message.id}:file${partIndex}`);
15864
+ }
15865
+ if (isToolPartWithOutput(part)) {
15866
+ keys.push(part.callID);
15867
+ }
15868
+ }
15869
+ }
15870
+ return keys;
15834
15871
  }
15835
- function getPersistedNoteNudge(db, sessionId) {
15836
- const result = db.prepare("SELECT note_nudge_trigger_pending, note_nudge_trigger_message_id, note_nudge_sticky_text, note_nudge_sticky_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
15837
- if (!isPersistedNoteNudgeRow(result)) {
15838
- return getDefaultPersistedNoteNudge();
15872
+ var PROTECTED_TAIL_USER_TURNS = 5;
15873
+ function getProtectedTailStartOrdinal(sessionId) {
15874
+ const messages = readRawSessionMessages(sessionId);
15875
+ const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
15876
+ if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
15877
+ return 1;
15839
15878
  }
15840
- return {
15841
- triggerPending: result.note_nudge_trigger_pending === 1,
15842
- triggerMessageId: result.note_nudge_trigger_message_id.length > 0 ? result.note_nudge_trigger_message_id : null,
15843
- stickyText: result.note_nudge_sticky_text.length > 0 ? result.note_nudge_sticky_text : null,
15844
- stickyMessageId: result.note_nudge_sticky_message_id.length > 0 ? result.note_nudge_sticky_message_id : null
15845
- };
15879
+ return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
15846
15880
  }
15847
- function setPersistedNoteNudgeTrigger(db, sessionId, triggerMessageId = "") {
15848
- db.transaction(() => {
15849
- ensureSessionMetaRow(db, sessionId);
15850
- db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 1, note_nudge_trigger_message_id = ?, note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(triggerMessageId, sessionId);
15851
- })();
15881
+ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
15882
+ const messages = readRawSessionMessages(sessionId);
15883
+ const startOrdinal = Math.max(1, offset);
15884
+ const lines = [];
15885
+ const lineMeta = [];
15886
+ let totalTokens = 0;
15887
+ let messagesProcessed = 0;
15888
+ let lastOrdinal = startOrdinal - 1;
15889
+ let lastMessageId = "";
15890
+ let firstMessageId = "";
15891
+ let currentBlock = null;
15892
+ let pendingNoiseMeta = [];
15893
+ let commitClusters = 0;
15894
+ let lastFlushedRole = "";
15895
+ function flushCurrentBlock() {
15896
+ if (!currentBlock)
15897
+ return true;
15898
+ const blockText = formatBlock(currentBlock);
15899
+ const blockTokens = estimateTokens(blockText);
15900
+ if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
15901
+ return false;
15902
+ }
15903
+ if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
15904
+ commitClusters++;
15905
+ }
15906
+ lastFlushedRole = currentBlock.role;
15907
+ if (!firstMessageId)
15908
+ firstMessageId = currentBlock.meta[0]?.messageId ?? "";
15909
+ lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
15910
+ lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
15911
+ messagesProcessed += currentBlock.meta.length;
15912
+ lines.push(blockText);
15913
+ lineMeta.push(...currentBlock.meta);
15914
+ totalTokens += blockTokens;
15915
+ currentBlock = null;
15916
+ return true;
15917
+ }
15918
+ for (const msg of messages) {
15919
+ if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
15920
+ break;
15921
+ if (msg.ordinal < startOrdinal)
15922
+ continue;
15923
+ const meta3 = { ordinal: msg.ordinal, messageId: msg.id };
15924
+ if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
15925
+ pendingNoiseMeta.push(meta3);
15926
+ continue;
15927
+ }
15928
+ const role = compactRole(msg.role);
15929
+ const compacted = compactTextForSummary(extractTexts(msg.parts).map((t) => msg.role === "user" ? cleanUserText(t) : t).map(normalizeText).filter((value) => value.length > 0).join(" / "), msg.role);
15930
+ const text = compacted.text;
15931
+ if (!text) {
15932
+ pendingNoiseMeta.push(meta3);
15933
+ continue;
15934
+ }
15935
+ if (currentBlock && currentBlock.role === role) {
15936
+ currentBlock.endOrdinal = msg.ordinal;
15937
+ currentBlock.parts.push(text);
15938
+ currentBlock.meta.push(...pendingNoiseMeta, meta3);
15939
+ currentBlock.commitHashes = mergeCommitHashes(currentBlock.commitHashes, compacted.commitHashes);
15940
+ pendingNoiseMeta = [];
15941
+ continue;
15942
+ }
15943
+ if (!flushCurrentBlock())
15944
+ break;
15945
+ currentBlock = {
15946
+ role,
15947
+ startOrdinal: pendingNoiseMeta[0]?.ordinal ?? msg.ordinal,
15948
+ endOrdinal: msg.ordinal,
15949
+ parts: [text],
15950
+ meta: [...pendingNoiseMeta, meta3],
15951
+ commitHashes: [...compacted.commitHashes]
15952
+ };
15953
+ pendingNoiseMeta = [];
15954
+ }
15955
+ flushCurrentBlock();
15956
+ return {
15957
+ startIndex: startOrdinal,
15958
+ endIndex: lastOrdinal,
15959
+ startMessageId: firstMessageId,
15960
+ endMessageId: lastMessageId,
15961
+ messageCount: messagesProcessed,
15962
+ tokenEstimate: totalTokens,
15963
+ hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
15964
+ text: lines.join(`
15965
+ `),
15966
+ lines: lineMeta,
15967
+ commitClusterCount: commitClusters
15968
+ };
15852
15969
  }
15853
- function setPersistedNoteNudgeTriggerMessageId(db, sessionId, triggerMessageId) {
15854
- db.transaction(() => {
15855
- ensureSessionMetaRow(db, sessionId);
15856
- db.prepare("UPDATE session_meta SET note_nudge_trigger_message_id = ? WHERE session_id = ?").run(triggerMessageId, sessionId);
15857
- })();
15970
+
15971
+ // src/features/magic-context/message-index.ts
15972
+ var lastIndexedStatements = new WeakMap;
15973
+ var insertMessageStatements = new WeakMap;
15974
+ var upsertIndexStatements = new WeakMap;
15975
+ var deleteFtsStatements = new WeakMap;
15976
+ var deleteIndexStatements = new WeakMap;
15977
+ var countIndexedMessageStatements = new WeakMap;
15978
+ var deleteIndexedMessageStatements = new WeakMap;
15979
+ function normalizeIndexText(text) {
15980
+ return text.replace(/\s+/g, " ").trim();
15858
15981
  }
15859
- function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
15860
- db.transaction(() => {
15861
- ensureSessionMetaRow(db, sessionId);
15862
- 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(text, messageId, sessionId);
15863
- })();
15982
+ function getLastIndexedStatement(db) {
15983
+ let stmt = lastIndexedStatements.get(db);
15984
+ if (!stmt) {
15985
+ stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
15986
+ lastIndexedStatements.set(db, stmt);
15987
+ }
15988
+ return stmt;
15864
15989
  }
15865
- function clearPersistedNoteNudge(db, sessionId) {
15866
- 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);
15990
+ function getInsertMessageStatement(db) {
15991
+ let stmt = insertMessageStatements.get(db);
15992
+ if (!stmt) {
15993
+ stmt = db.prepare("INSERT INTO message_history_fts (session_id, message_ordinal, message_id, role, content) VALUES (?, ?, ?, ?, ?)");
15994
+ insertMessageStatements.set(db, stmt);
15995
+ }
15996
+ return stmt;
15867
15997
  }
15868
- // src/shared/internal-initiator-marker.ts
15869
- var OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
15870
-
15871
- // src/shared/system-directive.ts
15872
- var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: MAGIC-CONTEXT";
15873
- function isSystemDirective(text) {
15874
- return text.trimStart().startsWith(SYSTEM_DIRECTIVE_PREFIX);
15998
+ function getUpsertIndexStatement(db) {
15999
+ let stmt = upsertIndexStatements.get(db);
16000
+ if (!stmt) {
16001
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, updated_at = excluded.updated_at");
16002
+ upsertIndexStatements.set(db, stmt);
16003
+ }
16004
+ return stmt;
15875
16005
  }
15876
- function removeSystemReminders(text) {
15877
- return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
16006
+ function getDeleteFtsStatement(db) {
16007
+ let stmt = deleteFtsStatements.get(db);
16008
+ if (!stmt) {
16009
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ?");
16010
+ deleteFtsStatements.set(db, stmt);
16011
+ }
16012
+ return stmt;
15878
16013
  }
15879
-
15880
- // src/hooks/magic-context/read-session-db.ts
15881
- import { Database as Database2 } from "bun:sqlite";
15882
- import { join as join6 } from "path";
15883
- function getOpenCodeDbPath() {
15884
- return join6(getDataDir(), "opencode", "opencode.db");
16014
+ function getDeleteIndexStatement(db) {
16015
+ let stmt = deleteIndexStatements.get(db);
16016
+ if (!stmt) {
16017
+ stmt = db.prepare("DELETE FROM message_history_index WHERE session_id = ?");
16018
+ deleteIndexStatements.set(db, stmt);
16019
+ }
16020
+ return stmt;
15885
16021
  }
15886
- var cachedReadOnlyDb = null;
15887
- function closeCachedReadOnlyDb() {
15888
- if (!cachedReadOnlyDb) {
15889
- return;
16022
+ function getCountIndexedMessageStatement(db) {
16023
+ let stmt = countIndexedMessageStatements.get(db);
16024
+ if (!stmt) {
16025
+ stmt = db.prepare("SELECT COUNT(*) AS count FROM message_history_fts WHERE session_id = ? AND message_id = ?");
16026
+ countIndexedMessageStatements.set(db, stmt);
15890
16027
  }
15891
- try {
15892
- cachedReadOnlyDb.db.close(false);
15893
- } catch (error48) {
15894
- log("[magic-context] failed to close cached OpenCode read-only DB:", error48);
15895
- } finally {
15896
- cachedReadOnlyDb = null;
16028
+ return stmt;
16029
+ }
16030
+ function getDeleteIndexedMessageStatement(db) {
16031
+ let stmt = deleteIndexedMessageStatements.get(db);
16032
+ if (!stmt) {
16033
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ? AND message_id = ?");
16034
+ deleteIndexedMessageStatements.set(db, stmt);
15897
16035
  }
16036
+ return stmt;
15898
16037
  }
15899
- function getReadOnlySessionDb() {
15900
- const dbPath = getOpenCodeDbPath();
15901
- if (cachedReadOnlyDb?.path === dbPath) {
15902
- return cachedReadOnlyDb.db;
16038
+ function getLastIndexedOrdinal(db, sessionId) {
16039
+ const row = getLastIndexedStatement(db).get(sessionId);
16040
+ return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
16041
+ }
16042
+ function deleteIndexedMessage(db, sessionId, messageId) {
16043
+ const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
16044
+ const count = typeof row?.count === "number" ? row.count : 0;
16045
+ if (count > 0) {
16046
+ getDeleteIndexedMessageStatement(db).run(sessionId, messageId);
15903
16047
  }
15904
- closeCachedReadOnlyDb();
15905
- const db = new Database2(dbPath, { readonly: true });
15906
- cachedReadOnlyDb = { path: dbPath, db };
15907
- return db;
16048
+ getDeleteIndexStatement(db).run(sessionId);
16049
+ return count;
15908
16050
  }
15909
- function withReadOnlySessionDb(fn) {
15910
- return fn(getReadOnlySessionDb());
16051
+ function clearIndexedMessages(db, sessionId) {
16052
+ db.transaction(() => {
16053
+ getDeleteFtsStatement(db).run(sessionId);
16054
+ getDeleteIndexStatement(db).run(sessionId);
16055
+ })();
15911
16056
  }
15912
- function getRawSessionMessageCountFromDb(db, sessionId) {
15913
- const row = db.prepare("SELECT COUNT(*) as count FROM message WHERE session_id = ?").get(sessionId);
15914
- return typeof row?.count === "number" ? row.count : 0;
16057
+ function getIndexableContent(role, parts) {
16058
+ if (role === "user") {
16059
+ if (!hasMeaningfulUserText(parts)) {
16060
+ return "";
16061
+ }
16062
+ return extractTexts(parts).map(cleanUserText).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16063
+ }
16064
+ if (role === "assistant") {
16065
+ return extractTexts(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16066
+ }
16067
+ return "";
15915
16068
  }
15916
-
15917
- // src/hooks/magic-context/read-session-formatting.ts
15918
- var COMMIT_HASH_PATTERN = /`?\b([0-9a-f]{6,12})\b`?/gi;
15919
- var COMMIT_HINT_PATTERN = /\b(commit(?:ted)?|cherry-?pick(?:ed)?|hash(?:es)?|sha)\b/i;
15920
- var MAX_COMMITS_PER_BLOCK = 5;
15921
- function hasMeaningfulUserText(parts) {
15922
- for (const part of parts) {
15923
- if (part === null || typeof part !== "object")
15924
- continue;
15925
- const candidate = part;
15926
- if (candidate.type !== "text" || typeof candidate.text !== "string")
15927
- continue;
15928
- if (candidate.ignored === true)
15929
- continue;
15930
- const cleaned = removeSystemReminders(candidate.text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
15931
- if (!cleaned)
15932
- continue;
15933
- if (isSystemDirective(cleaned))
15934
- continue;
15935
- return true;
16069
+ function ensureMessagesIndexed(db, sessionId, readMessages) {
16070
+ const messages = readMessages(sessionId);
16071
+ if (messages.length === 0) {
16072
+ db.transaction(() => clearIndexedMessages(db, sessionId))();
16073
+ return;
15936
16074
  }
15937
- return false;
16075
+ let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
16076
+ if (lastIndexedOrdinal > messages.length) {
16077
+ db.transaction(() => clearIndexedMessages(db, sessionId))();
16078
+ lastIndexedOrdinal = 0;
16079
+ }
16080
+ if (lastIndexedOrdinal >= messages.length) {
16081
+ return;
16082
+ }
16083
+ const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
16084
+ ordinal: message.ordinal,
16085
+ id: message.id,
16086
+ role: message.role,
16087
+ content: getIndexableContent(message.role, message.parts)
16088
+ })).filter((message) => message.content.length > 0);
16089
+ const now = Date.now();
16090
+ db.transaction(() => {
16091
+ const insertMessage = getInsertMessageStatement(db);
16092
+ for (const message of messagesToInsert) {
16093
+ insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
16094
+ }
16095
+ getUpsertIndexStatement(db).run(sessionId, messages.length, now);
16096
+ })();
16097
+ }
16098
+ // src/features/magic-context/storage-db.ts
16099
+ import { Database as Database2 } from "bun:sqlite";
16100
+ import { mkdirSync } from "fs";
16101
+ import { join as join6 } from "path";
16102
+ var databases = new Map;
16103
+ var FALLBACK_DATABASE_KEY = "__fallback__:memory:";
16104
+ var persistenceByDatabase = new WeakMap;
16105
+ var persistenceErrorByDatabase = new WeakMap;
16106
+ function resolveDatabasePath() {
16107
+ const dbDir = join6(getOpenCodeStorageDir(), "plugin", "magic-context");
16108
+ return { dbDir, dbPath: join6(dbDir, "context.db") };
16109
+ }
16110
+ function initializeDatabase(db) {
16111
+ db.run("PRAGMA journal_mode=WAL");
16112
+ db.run("PRAGMA busy_timeout=5000");
16113
+ db.run("PRAGMA foreign_keys=ON");
16114
+ db.run(`
16115
+ CREATE TABLE IF NOT EXISTS tags (
16116
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16117
+ session_id TEXT,
16118
+ message_id TEXT,
16119
+ type TEXT,
16120
+ status TEXT DEFAULT 'active',
16121
+ byte_size INTEGER,
16122
+ tag_number INTEGER,
16123
+ UNIQUE(session_id, tag_number)
16124
+ );
16125
+
16126
+ CREATE TABLE IF NOT EXISTS pending_ops (
16127
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16128
+ session_id TEXT,
16129
+ tag_id INTEGER,
16130
+ operation TEXT,
16131
+ queued_at INTEGER
16132
+ );
16133
+
16134
+ CREATE TABLE IF NOT EXISTS source_contents (
16135
+ tag_id INTEGER,
16136
+ session_id TEXT,
16137
+ content TEXT,
16138
+ created_at INTEGER,
16139
+ PRIMARY KEY(session_id, tag_id)
16140
+ );
16141
+
16142
+ CREATE TABLE IF NOT EXISTS compartments (
16143
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16144
+ session_id TEXT NOT NULL,
16145
+ sequence INTEGER NOT NULL,
16146
+ start_message INTEGER NOT NULL,
16147
+ end_message INTEGER NOT NULL,
16148
+ start_message_id TEXT DEFAULT '',
16149
+ end_message_id TEXT DEFAULT '',
16150
+ title TEXT NOT NULL,
16151
+ content TEXT NOT NULL,
16152
+ created_at INTEGER NOT NULL,
16153
+ UNIQUE(session_id, sequence)
16154
+ );
16155
+
16156
+ CREATE TABLE IF NOT EXISTS session_facts (
16157
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16158
+ session_id TEXT NOT NULL,
16159
+ category TEXT NOT NULL,
16160
+ content TEXT NOT NULL,
16161
+ created_at INTEGER NOT NULL,
16162
+ updated_at INTEGER NOT NULL
16163
+ );
16164
+
16165
+ CREATE TABLE IF NOT EXISTS session_notes (
16166
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16167
+ session_id TEXT NOT NULL,
16168
+ content TEXT NOT NULL,
16169
+ created_at INTEGER NOT NULL
16170
+ );
16171
+
16172
+ CREATE TABLE IF NOT EXISTS memories (
16173
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16174
+ project_path TEXT NOT NULL,
16175
+ category TEXT NOT NULL,
16176
+ content TEXT NOT NULL,
16177
+ normalized_hash TEXT NOT NULL,
16178
+ source_session_id TEXT,
16179
+ source_type TEXT DEFAULT 'historian',
16180
+ seen_count INTEGER DEFAULT 1,
16181
+ retrieval_count INTEGER DEFAULT 0,
16182
+ first_seen_at INTEGER NOT NULL,
16183
+ created_at INTEGER NOT NULL,
16184
+ updated_at INTEGER NOT NULL,
16185
+ last_seen_at INTEGER NOT NULL,
16186
+ last_retrieved_at INTEGER,
16187
+ status TEXT DEFAULT 'active',
16188
+ expires_at INTEGER,
16189
+ verification_status TEXT DEFAULT 'unverified',
16190
+ verified_at INTEGER,
16191
+ superseded_by_memory_id INTEGER,
16192
+ merged_from TEXT,
16193
+ metadata_json TEXT,
16194
+ UNIQUE(project_path, category, normalized_hash)
16195
+ );
16196
+
16197
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
16198
+ memory_id INTEGER PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
16199
+ embedding BLOB NOT NULL,
16200
+ model_id TEXT
16201
+ );
16202
+
16203
+ CREATE TABLE IF NOT EXISTS dream_state (
16204
+ key TEXT PRIMARY KEY,
16205
+ value TEXT NOT NULL
16206
+ );
16207
+
16208
+ CREATE TABLE IF NOT EXISTS dream_queue (
16209
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16210
+ project_path TEXT NOT NULL,
16211
+ reason TEXT NOT NULL,
16212
+ enqueued_at INTEGER NOT NULL,
16213
+ started_at INTEGER,
16214
+ retry_count INTEGER DEFAULT 0
16215
+ );
16216
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
16217
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
16218
+
16219
+ CREATE TABLE IF NOT EXISTS smart_notes (
16220
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16221
+ project_path TEXT NOT NULL,
16222
+ content TEXT NOT NULL,
16223
+ surface_condition TEXT NOT NULL,
16224
+ status TEXT NOT NULL DEFAULT 'pending',
16225
+ created_session_id TEXT,
16226
+ created_at INTEGER NOT NULL,
16227
+ updated_at INTEGER NOT NULL,
16228
+ last_checked_at INTEGER,
16229
+ ready_at INTEGER,
16230
+ ready_reason TEXT
16231
+ );
16232
+ CREATE INDEX IF NOT EXISTS idx_smart_notes_project_status ON smart_notes(project_path, status);
16233
+
16234
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
16235
+ content,
16236
+ category,
16237
+ content='memories',
16238
+ content_rowid='id',
16239
+ tokenize='porter unicode61'
16240
+ );
16241
+
16242
+ CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
16243
+ session_id UNINDEXED,
16244
+ message_ordinal UNINDEXED,
16245
+ message_id UNINDEXED,
16246
+ role,
16247
+ content,
16248
+ tokenize='porter unicode61'
16249
+ );
16250
+
16251
+ CREATE TABLE IF NOT EXISTS message_history_index (
16252
+ session_id TEXT PRIMARY KEY,
16253
+ last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
16254
+ updated_at INTEGER NOT NULL
16255
+ );
16256
+
16257
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
16258
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
16259
+ END;
16260
+
16261
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
16262
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
16263
+ END;
16264
+
16265
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
16266
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
16267
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
16268
+ END;
16269
+
16270
+ CREATE TABLE IF NOT EXISTS session_meta (
16271
+ session_id TEXT PRIMARY KEY,
16272
+ last_response_time INTEGER,
16273
+ cache_ttl TEXT,
16274
+ counter INTEGER DEFAULT 0,
16275
+ last_nudge_tokens INTEGER DEFAULT 0,
16276
+ last_nudge_band TEXT DEFAULT '',
16277
+ last_transform_error TEXT DEFAULT '',
16278
+ nudge_anchor_message_id TEXT DEFAULT '',
16279
+ nudge_anchor_text TEXT DEFAULT '',
16280
+ sticky_turn_reminder_text TEXT DEFAULT '',
16281
+ sticky_turn_reminder_message_id TEXT DEFAULT '',
16282
+ note_nudge_trigger_pending INTEGER DEFAULT 0,
16283
+ note_nudge_trigger_message_id TEXT DEFAULT '',
16284
+ note_nudge_sticky_text TEXT DEFAULT '',
16285
+ note_nudge_sticky_message_id TEXT DEFAULT '',
16286
+ is_subagent INTEGER DEFAULT 0,
16287
+ last_context_percentage REAL DEFAULT 0,
16288
+ last_input_tokens INTEGER DEFAULT 0,
16289
+ times_execute_threshold_reached INTEGER DEFAULT 0,
16290
+ compartment_in_progress INTEGER DEFAULT 0,
16291
+ system_prompt_hash TEXT DEFAULT '',
16292
+ memory_block_cache TEXT DEFAULT '',
16293
+ memory_block_count INTEGER DEFAULT 0
16294
+ );
16295
+
16296
+ CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
16297
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
16298
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
16299
+ CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
16300
+
16301
+ CREATE TABLE IF NOT EXISTS recomp_compartments (
16302
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16303
+ session_id TEXT NOT NULL,
16304
+ sequence INTEGER NOT NULL,
16305
+ start_message INTEGER NOT NULL,
16306
+ end_message INTEGER NOT NULL,
16307
+ start_message_id TEXT DEFAULT '',
16308
+ end_message_id TEXT DEFAULT '',
16309
+ title TEXT NOT NULL,
16310
+ content TEXT NOT NULL,
16311
+ pass_number INTEGER NOT NULL,
16312
+ created_at INTEGER NOT NULL,
16313
+ UNIQUE(session_id, sequence)
16314
+ );
16315
+
16316
+ CREATE TABLE IF NOT EXISTS recomp_facts (
16317
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16318
+ session_id TEXT NOT NULL,
16319
+ category TEXT NOT NULL,
16320
+ content TEXT NOT NULL,
16321
+ pass_number INTEGER NOT NULL,
16322
+ created_at INTEGER NOT NULL
16323
+ );
16324
+
16325
+ CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
16326
+ CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
16327
+ CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
16328
+ CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
16329
+ CREATE INDEX IF NOT EXISTS idx_session_notes_session ON session_notes(session_id);
16330
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
16331
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
16332
+ CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
16333
+ CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
16334
+ `);
16335
+ ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
16336
+ ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
16337
+ ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
16338
+ ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
16339
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
16340
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
16341
+ ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
16342
+ ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
16343
+ ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
16344
+ ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
16345
+ ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
16346
+ ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
16347
+ ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
16348
+ ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
16349
+ ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
16350
+ ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
16351
+ ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
16352
+ ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
16353
+ ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
16354
+ ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
16355
+ ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
15938
16356
  }
15939
- function extractTexts(parts) {
15940
- const texts = [];
15941
- for (const part of parts) {
15942
- if (part === null || typeof part !== "object")
15943
- continue;
15944
- const p = part;
15945
- if (p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) {
15946
- texts.push(p.text.trim());
15947
- }
16357
+ function ensureColumn(db, table, column, definition) {
16358
+ if (!/^[a-z_]+$/.test(table) || !/^[a-z_]+$/.test(column) || !/^[A-Z0-9_'(),\s]+$/i.test(definition)) {
16359
+ throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
15948
16360
  }
15949
- return texts;
15950
- }
15951
- function estimateTokens(text) {
15952
- return Math.ceil(text.length / 3.5);
15953
- }
15954
- function normalizeText(text) {
15955
- return text.replace(/\s+/g, " ").trim();
15956
- }
15957
- function compactRole(role) {
15958
- if (role === "assistant")
15959
- return "A";
15960
- if (role === "user")
15961
- return "U";
15962
- return role.slice(0, 1).toUpperCase() || "M";
15963
- }
15964
- function formatBlock(block) {
15965
- const range = block.startOrdinal === block.endOrdinal ? `[${block.startOrdinal}]` : `[${block.startOrdinal}-${block.endOrdinal}]`;
15966
- const commitSuffix = block.commitHashes.length > 0 ? ` commits: ${block.commitHashes.join(", ")}` : "";
15967
- return `${range} ${block.role}:${commitSuffix} ${block.parts.join(" / ")}`;
15968
- }
15969
- function extractCommitHashes(text) {
15970
- const hashes = [];
15971
- const seen = new Set;
15972
- for (const match of text.matchAll(COMMIT_HASH_PATTERN)) {
15973
- const hash2 = match[1]?.toLowerCase();
15974
- if (!hash2 || seen.has(hash2))
15975
- continue;
15976
- seen.add(hash2);
15977
- hashes.push(hash2);
15978
- if (hashes.length >= MAX_COMMITS_PER_BLOCK)
15979
- break;
16361
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
16362
+ if (rows.some((row) => row.name === column)) {
16363
+ return;
15980
16364
  }
15981
- return hashes;
16365
+ db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
15982
16366
  }
15983
- function compactTextForSummary(text, role) {
15984
- const commitHashes = role === "assistant" ? extractCommitHashes(text) : [];
15985
- if (commitHashes.length === 0 || !COMMIT_HINT_PATTERN.test(text)) {
15986
- return { text, commitHashes };
16367
+ function createFallbackDatabase() {
16368
+ try {
16369
+ const fallback = new Database2(":memory:");
16370
+ initializeDatabase(fallback);
16371
+ return fallback;
16372
+ } catch (error48) {
16373
+ throw new Error(`[magic-context] storage fatal: failed to initialize fallback database: ${getErrorMessage(error48)}`);
15987
16374
  }
15988
- const withoutHashes = text.replace(COMMIT_HASH_PATTERN, "").replace(/\(\s*\)/g, "").replace(/\s+,/g, ",").replace(/,\s*,+/g, ", ").replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1").trim();
15989
- return {
15990
- text: withoutHashes.length > 0 ? withoutHashes : text,
15991
- commitHashes
15992
- };
15993
16375
  }
15994
- function mergeCommitHashes(existing, next) {
15995
- if (next.length === 0)
15996
- return existing;
15997
- const merged = [...existing];
15998
- for (const hash2 of next) {
15999
- if (merged.includes(hash2))
16000
- continue;
16001
- merged.push(hash2);
16002
- if (merged.length >= MAX_COMMITS_PER_BLOCK)
16003
- break;
16376
+ function openDatabase() {
16377
+ try {
16378
+ const { dbDir, dbPath } = resolveDatabasePath();
16379
+ const existing = databases.get(dbPath);
16380
+ if (existing) {
16381
+ if (!persistenceByDatabase.has(existing)) {
16382
+ persistenceByDatabase.set(existing, true);
16383
+ }
16384
+ return existing;
16385
+ }
16386
+ mkdirSync(dbDir, { recursive: true });
16387
+ const db = new Database2(dbPath);
16388
+ initializeDatabase(db);
16389
+ databases.set(dbPath, db);
16390
+ persistenceByDatabase.set(db, true);
16391
+ persistenceErrorByDatabase.delete(db);
16392
+ return db;
16393
+ } catch (error48) {
16394
+ log("[magic-context] storage error:", error48);
16395
+ const errorMessage = getErrorMessage(error48);
16396
+ const existingFallback = databases.get(FALLBACK_DATABASE_KEY);
16397
+ if (existingFallback) {
16398
+ if (!persistenceByDatabase.has(existingFallback)) {
16399
+ persistenceByDatabase.set(existingFallback, false);
16400
+ persistenceErrorByDatabase.set(existingFallback, errorMessage);
16401
+ }
16402
+ return existingFallback;
16403
+ }
16404
+ const fallback = createFallbackDatabase();
16405
+ databases.set(FALLBACK_DATABASE_KEY, fallback);
16406
+ persistenceByDatabase.set(fallback, false);
16407
+ persistenceErrorByDatabase.set(fallback, errorMessage);
16408
+ return fallback;
16004
16409
  }
16005
- return merged;
16006
16410
  }
16007
-
16008
- // src/hooks/magic-context/read-session-raw.ts
16009
- function isRawMessageRow(row) {
16010
- if (row === null || typeof row !== "object")
16011
- return false;
16012
- const candidate = row;
16013
- return typeof candidate.id === "string" && typeof candidate.data === "string";
16411
+ function isDatabasePersisted(db) {
16412
+ return persistenceByDatabase.get(db) ?? false;
16014
16413
  }
16015
- function isRawPartRow(row) {
16414
+ function getDatabasePersistenceError(db) {
16415
+ return persistenceErrorByDatabase.get(db) ?? null;
16416
+ }
16417
+ // src/features/magic-context/storage-meta-shared.ts
16418
+ var META_COLUMNS = {
16419
+ lastResponseTime: "last_response_time",
16420
+ cacheTtl: "cache_ttl",
16421
+ counter: "counter",
16422
+ lastNudgeTokens: "last_nudge_tokens",
16423
+ lastNudgeBand: "last_nudge_band",
16424
+ lastTransformError: "last_transform_error",
16425
+ isSubagent: "is_subagent",
16426
+ lastContextPercentage: "last_context_percentage",
16427
+ lastInputTokens: "last_input_tokens",
16428
+ timesExecuteThresholdReached: "times_execute_threshold_reached",
16429
+ compartmentInProgress: "compartment_in_progress",
16430
+ systemPromptHash: "system_prompt_hash",
16431
+ clearedReasoningThroughTag: "cleared_reasoning_through_tag"
16432
+ };
16433
+ var BOOLEAN_META_KEYS = new Set(["isSubagent", "compartmentInProgress"]);
16434
+ function isSessionMetaRow(row) {
16016
16435
  if (row === null || typeof row !== "object")
16017
16436
  return false;
16018
- const candidate = row;
16019
- return typeof candidate.message_id === "string" && typeof candidate.data === "string";
16020
- }
16021
- function parseJsonRecord(value) {
16022
- const parsed = JSON.parse(value);
16023
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16024
- throw new Error("Expected JSON object");
16025
- }
16026
- return parsed;
16027
- }
16028
- function parseJsonUnknown(value) {
16029
- return JSON.parse(value);
16030
- }
16031
- function readRawSessionMessagesFromDb(db, sessionId) {
16032
- const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawMessageRow);
16033
- const partRows = db.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId).filter(isRawPartRow);
16034
- const partsByMessageId = new Map;
16035
- for (const part of partRows) {
16036
- const list = partsByMessageId.get(part.message_id) ?? [];
16037
- list.push(parseJsonUnknown(part.data));
16038
- partsByMessageId.set(part.message_id, list);
16039
- }
16040
- return messageRows.map((row, index) => {
16041
- const info = parseJsonRecord(row.data);
16042
- const role = typeof info.role === "string" ? info.role : "unknown";
16043
- return {
16044
- ordinal: index + 1,
16045
- id: row.id,
16046
- role,
16047
- parts: partsByMessageId.get(row.id) ?? []
16048
- };
16049
- });
16050
- }
16051
-
16052
- // src/hooks/magic-context/tag-content-primitives.ts
16053
- var encoder = new TextEncoder;
16054
- var TAG_PREFIX_REGEX = /^\u00A7\d+\u00A7\s*/;
16055
- function byteSize(value) {
16056
- return encoder.encode(value).length;
16437
+ const r = row;
16438
+ return typeof r.session_id === "string" && typeof r.last_response_time === "number" && typeof r.cache_ttl === "string" && typeof r.counter === "number" && typeof r.last_nudge_tokens === "number" && typeof r.last_nudge_band === "string" && typeof r.last_transform_error === "string" && typeof r.is_subagent === "number" && typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && typeof r.times_execute_threshold_reached === "number" && typeof r.compartment_in_progress === "number" && (typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") && typeof r.cleared_reasoning_through_tag === "number";
16057
16439
  }
16058
- function stripTagPrefix(value) {
16059
- return value.replace(TAG_PREFIX_REGEX, "");
16440
+ function getDefaultSessionMeta(sessionId) {
16441
+ return {
16442
+ sessionId,
16443
+ lastResponseTime: 0,
16444
+ cacheTtl: "5m",
16445
+ counter: 0,
16446
+ lastNudgeTokens: 0,
16447
+ lastNudgeBand: null,
16448
+ lastTransformError: null,
16449
+ isSubagent: false,
16450
+ lastContextPercentage: 0,
16451
+ lastInputTokens: 0,
16452
+ timesExecuteThresholdReached: 0,
16453
+ compartmentInProgress: false,
16454
+ systemPromptHash: "",
16455
+ clearedReasoningThroughTag: 0
16456
+ };
16060
16457
  }
16061
- function prependTag(tagId, value) {
16062
- const stripped = stripTagPrefix(value);
16063
- return `\xA7${tagId}\xA7 ${stripped}`;
16458
+ function ensureSessionMetaRow(db, sessionId) {
16459
+ const defaults = getDefaultSessionMeta(sessionId);
16460
+ db.prepare("INSERT OR IGNORE INTO session_meta (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, cleared_reasoning_through_tag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(sessionId, defaults.lastResponseTime, defaults.cacheTtl, defaults.counter, defaults.lastNudgeTokens, defaults.lastNudgeBand ?? "", defaults.lastTransformError ?? "", defaults.isSubagent ? 1 : 0, defaults.lastContextPercentage, defaults.lastInputTokens, defaults.timesExecuteThresholdReached, defaults.compartmentInProgress ? 1 : 0, defaults.systemPromptHash ?? "", defaults.clearedReasoningThroughTag);
16064
16461
  }
16065
- function isThinkingPart(part) {
16066
- if (part === null || typeof part !== "object")
16067
- return false;
16068
- const candidate = part;
16069
- return candidate.type === "thinking" || candidate.type === "reasoning";
16462
+ function toSessionMeta(row) {
16463
+ return {
16464
+ sessionId: row.session_id,
16465
+ lastResponseTime: row.last_response_time,
16466
+ cacheTtl: row.cache_ttl,
16467
+ counter: row.counter,
16468
+ lastNudgeTokens: row.last_nudge_tokens,
16469
+ lastNudgeBand: row.last_nudge_band.length > 0 ? row.last_nudge_band : null,
16470
+ lastTransformError: row.last_transform_error.length > 0 ? row.last_transform_error : null,
16471
+ isSubagent: row.is_subagent === 1,
16472
+ lastContextPercentage: row.last_context_percentage,
16473
+ lastInputTokens: row.last_input_tokens,
16474
+ timesExecuteThresholdReached: row.times_execute_threshold_reached,
16475
+ compartmentInProgress: row.compartment_in_progress === 1,
16476
+ systemPromptHash: String(row.system_prompt_hash),
16477
+ clearedReasoningThroughTag: row.cleared_reasoning_through_tag
16478
+ };
16070
16479
  }
16071
16480
 
16072
- // src/hooks/magic-context/tag-part-guards.ts
16073
- function isTextPart(part) {
16074
- if (part === null || typeof part !== "object")
16481
+ // src/features/magic-context/storage-meta-persisted.ts
16482
+ function isPersistedUsageRow(row) {
16483
+ if (row === null || typeof row !== "object")
16075
16484
  return false;
16076
- const p = part;
16077
- return p.type === "text" && typeof p.text === "string";
16485
+ const r = row;
16486
+ return typeof r.last_context_percentage === "number" && typeof r.last_input_tokens === "number" && typeof r.last_response_time === "number";
16078
16487
  }
16079
- function isToolPartWithOutput(part) {
16080
- if (part === null || typeof part !== "object")
16081
- return false;
16082
- const p = part;
16083
- if (p.type !== "tool" || typeof p.callID !== "string")
16084
- return false;
16085
- if (p.state === null || typeof p.state !== "object")
16488
+ function isPersistedReasoningWatermarkRow(row) {
16489
+ if (row === null || typeof row !== "object")
16086
16490
  return false;
16087
- return typeof p.state.output === "string";
16491
+ const r = row;
16492
+ return typeof r.cleared_reasoning_through_tag === "number";
16088
16493
  }
16089
- function isFilePart(part) {
16090
- if (part === null || typeof part !== "object")
16494
+ function isPersistedNudgePlacementRow(row) {
16495
+ if (row === null || typeof row !== "object")
16091
16496
  return false;
16092
- const p = part;
16093
- return p.type === "file" && typeof p.url === "string";
16497
+ const r = row;
16498
+ return typeof r.nudge_anchor_message_id === "string" && typeof r.nudge_anchor_text === "string";
16094
16499
  }
16095
- function buildFileSourceContent(parts) {
16096
- const content = parts.filter(isTextPart).map((part) => stripTagPrefix(part.text)).join(`
16097
- `).trim();
16098
- return content.length > 0 ? content : null;
16500
+ function isPersistedStickyTurnReminderRow(row) {
16501
+ if (row === null || typeof row !== "object")
16502
+ return false;
16503
+ const r = row;
16504
+ return typeof r.sticky_turn_reminder_text === "string" && typeof r.sticky_turn_reminder_message_id === "string";
16099
16505
  }
16100
-
16101
- // src/hooks/magic-context/read-session-chunk.ts
16102
- var activeRawMessageCache = null;
16103
- function cleanUserText(text) {
16104
- return removeSystemReminders(text).replace(OMO_INTERNAL_INITIATOR_MARKER, "").trim();
16506
+ function isPersistedNoteNudgeRow(row) {
16507
+ if (row === null || typeof row !== "object")
16508
+ return false;
16509
+ const r = row;
16510
+ 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";
16105
16511
  }
16106
- function withRawSessionMessageCache(fn) {
16107
- const outerCache = activeRawMessageCache;
16108
- if (!outerCache) {
16109
- activeRawMessageCache = new Map;
16110
- }
16111
- try {
16112
- return fn();
16113
- } finally {
16114
- if (!outerCache) {
16115
- activeRawMessageCache = null;
16116
- }
16117
- }
16512
+ function getDefaultPersistedNoteNudge() {
16513
+ return {
16514
+ triggerPending: false,
16515
+ triggerMessageId: null,
16516
+ stickyText: null,
16517
+ stickyMessageId: null
16518
+ };
16118
16519
  }
16119
- function readRawSessionMessages(sessionId) {
16120
- if (activeRawMessageCache) {
16121
- const cached2 = activeRawMessageCache.get(sessionId);
16122
- if (cached2) {
16123
- return cached2;
16124
- }
16125
- const messages = withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
16126
- activeRawMessageCache.set(sessionId, messages);
16127
- return messages;
16520
+ function loadPersistedUsage(db, sessionId) {
16521
+ const result = db.prepare("SELECT last_context_percentage, last_input_tokens, last_response_time FROM session_meta WHERE session_id = ?").get(sessionId);
16522
+ if (!isPersistedUsageRow(result) || result.last_context_percentage === 0 && result.last_input_tokens === 0) {
16523
+ return null;
16128
16524
  }
16129
- return withReadOnlySessionDb((db) => readRawSessionMessagesFromDb(db, sessionId));
16130
- }
16131
- function getRawSessionMessageCount(sessionId) {
16132
- return withReadOnlySessionDb((db) => getRawSessionMessageCountFromDb(db, sessionId));
16525
+ return {
16526
+ usage: {
16527
+ percentage: result.last_context_percentage,
16528
+ inputTokens: result.last_input_tokens
16529
+ },
16530
+ updatedAt: result.last_response_time || Date.now()
16531
+ };
16133
16532
  }
16134
- function getRawSessionTagKeysThrough(sessionId, upToMessageIndex) {
16135
- const messages = readRawSessionMessages(sessionId);
16136
- const keys = [];
16137
- for (const message of messages) {
16138
- if (message.ordinal > upToMessageIndex)
16139
- break;
16140
- for (const [partIndex, part] of message.parts.entries()) {
16141
- if (isTextPart(part)) {
16142
- keys.push(`${message.id}:p${partIndex}`);
16143
- }
16144
- if (isFilePart(part)) {
16145
- keys.push(`${message.id}:file${partIndex}`);
16146
- }
16147
- if (isToolPartWithOutput(part)) {
16148
- keys.push(part.callID);
16149
- }
16150
- }
16151
- }
16152
- return keys;
16533
+ function getPersistedReasoningWatermark(db, sessionId) {
16534
+ const result = db.prepare("SELECT cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?").get(sessionId);
16535
+ return isPersistedReasoningWatermarkRow(result) ? result.cleared_reasoning_through_tag : 0;
16153
16536
  }
16154
- var PROTECTED_TAIL_USER_TURNS = 5;
16155
- function getProtectedTailStartOrdinal(sessionId) {
16156
- const messages = readRawSessionMessages(sessionId);
16157
- const userOrdinals = messages.filter((m) => m.role === "user" && hasMeaningfulUserText(m.parts)).map((m) => m.ordinal);
16158
- if (userOrdinals.length < PROTECTED_TAIL_USER_TURNS) {
16159
- return 1;
16160
- }
16161
- return userOrdinals[userOrdinals.length - PROTECTED_TAIL_USER_TURNS];
16537
+ function setPersistedReasoningWatermark(db, sessionId, tagNumber) {
16538
+ ensureSessionMetaRow(db, sessionId);
16539
+ db.prepare("UPDATE session_meta SET cleared_reasoning_through_tag = ? WHERE session_id = ?").run(tagNumber, sessionId);
16162
16540
  }
16163
- function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal) {
16164
- const messages = readRawSessionMessages(sessionId);
16165
- const startOrdinal = Math.max(1, offset);
16166
- const lines = [];
16167
- const lineMeta = [];
16168
- let totalTokens = 0;
16169
- let messagesProcessed = 0;
16170
- let lastOrdinal = startOrdinal - 1;
16171
- let lastMessageId = "";
16172
- let firstMessageId = "";
16173
- let currentBlock = null;
16174
- let pendingNoiseMeta = [];
16175
- let commitClusters = 0;
16176
- let lastFlushedRole = "";
16177
- function flushCurrentBlock() {
16178
- if (!currentBlock)
16179
- return true;
16180
- const blockText = formatBlock(currentBlock);
16181
- const blockTokens = estimateTokens(blockText);
16182
- if (totalTokens + blockTokens > tokenBudget && totalTokens > 0) {
16183
- return false;
16184
- }
16185
- if (currentBlock.role === "A" && currentBlock.commitHashes.length > 0 && lastFlushedRole !== "A") {
16186
- commitClusters++;
16187
- }
16188
- lastFlushedRole = currentBlock.role;
16189
- if (!firstMessageId)
16190
- firstMessageId = currentBlock.meta[0]?.messageId ?? "";
16191
- lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
16192
- lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
16193
- messagesProcessed += currentBlock.meta.length;
16194
- lines.push(blockText);
16195
- lineMeta.push(...currentBlock.meta);
16196
- totalTokens += blockTokens;
16197
- currentBlock = null;
16198
- return true;
16541
+ function getPersistedNudgePlacement(db, sessionId) {
16542
+ const result = db.prepare("SELECT nudge_anchor_message_id, nudge_anchor_text FROM session_meta WHERE session_id = ?").get(sessionId);
16543
+ if (!isPersistedNudgePlacementRow(result)) {
16544
+ return null;
16199
16545
  }
16200
- for (const msg of messages) {
16201
- if (eligibleEndOrdinal !== undefined && msg.ordinal >= eligibleEndOrdinal)
16202
- break;
16203
- if (msg.ordinal < startOrdinal)
16204
- continue;
16205
- const meta3 = { ordinal: msg.ordinal, messageId: msg.id };
16206
- if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
16207
- pendingNoiseMeta.push(meta3);
16208
- continue;
16209
- }
16210
- const role = compactRole(msg.role);
16211
- const compacted = compactTextForSummary(extractTexts(msg.parts).map((t) => msg.role === "user" ? cleanUserText(t) : t).map(normalizeText).filter((value) => value.length > 0).join(" / "), msg.role);
16212
- const text = compacted.text;
16213
- if (!text) {
16214
- pendingNoiseMeta.push(meta3);
16215
- continue;
16216
- }
16217
- if (currentBlock && currentBlock.role === role) {
16218
- currentBlock.endOrdinal = msg.ordinal;
16219
- currentBlock.parts.push(text);
16220
- currentBlock.meta.push(...pendingNoiseMeta, meta3);
16221
- currentBlock.commitHashes = mergeCommitHashes(currentBlock.commitHashes, compacted.commitHashes);
16222
- pendingNoiseMeta = [];
16223
- continue;
16224
- }
16225
- if (!flushCurrentBlock())
16226
- break;
16227
- currentBlock = {
16228
- role,
16229
- startOrdinal: pendingNoiseMeta[0]?.ordinal ?? msg.ordinal,
16230
- endOrdinal: msg.ordinal,
16231
- parts: [text],
16232
- meta: [...pendingNoiseMeta, meta3],
16233
- commitHashes: [...compacted.commitHashes]
16234
- };
16235
- pendingNoiseMeta = [];
16546
+ if (result.nudge_anchor_message_id.length === 0 || result.nudge_anchor_text.length === 0) {
16547
+ return null;
16236
16548
  }
16237
- flushCurrentBlock();
16238
16549
  return {
16239
- startIndex: startOrdinal,
16240
- endIndex: lastOrdinal,
16241
- startMessageId: firstMessageId,
16242
- endMessageId: lastMessageId,
16243
- messageCount: messagesProcessed,
16244
- tokenEstimate: totalTokens,
16245
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, messages.length) : messages.length),
16246
- text: lines.join(`
16247
- `),
16248
- lines: lineMeta,
16249
- commitClusterCount: commitClusters
16550
+ messageId: result.nudge_anchor_message_id,
16551
+ nudgeText: result.nudge_anchor_text
16250
16552
  };
16251
16553
  }
16252
-
16253
- // src/features/magic-context/message-index.ts
16254
- var lastIndexedStatements = new WeakMap;
16255
- var insertMessageStatements = new WeakMap;
16256
- var upsertIndexStatements = new WeakMap;
16257
- var deleteFtsStatements = new WeakMap;
16258
- var deleteIndexStatements = new WeakMap;
16259
- function normalizeIndexText(text) {
16260
- return text.replace(/\s+/g, " ").trim();
16554
+ function setPersistedNudgePlacement(db, sessionId, messageId, nudgeText) {
16555
+ db.transaction(() => {
16556
+ ensureSessionMetaRow(db, sessionId);
16557
+ db.prepare("UPDATE session_meta SET nudge_anchor_message_id = ?, nudge_anchor_text = ? WHERE session_id = ?").run(messageId, nudgeText, sessionId);
16558
+ })();
16261
16559
  }
16262
- function getLastIndexedStatement(db) {
16263
- let stmt = lastIndexedStatements.get(db);
16264
- if (!stmt) {
16265
- stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
16266
- lastIndexedStatements.set(db, stmt);
16267
- }
16268
- return stmt;
16560
+ function clearPersistedNudgePlacement(db, sessionId) {
16561
+ db.prepare("UPDATE session_meta SET nudge_anchor_message_id = '', nudge_anchor_text = '' WHERE session_id = ?").run(sessionId);
16269
16562
  }
16270
- function getInsertMessageStatement(db) {
16271
- let stmt = insertMessageStatements.get(db);
16272
- if (!stmt) {
16273
- stmt = db.prepare("INSERT INTO message_history_fts (session_id, message_ordinal, message_id, role, content) VALUES (?, ?, ?, ?, ?)");
16274
- insertMessageStatements.set(db, stmt);
16563
+ function getPersistedStickyTurnReminder(db, sessionId) {
16564
+ const result = db.prepare("SELECT sticky_turn_reminder_text, sticky_turn_reminder_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
16565
+ if (!isPersistedStickyTurnReminderRow(result)) {
16566
+ return null;
16275
16567
  }
16276
- return stmt;
16277
- }
16278
- function getUpsertIndexStatement(db) {
16279
- let stmt = upsertIndexStatements.get(db);
16280
- if (!stmt) {
16281
- stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, updated_at = excluded.updated_at");
16282
- upsertIndexStatements.set(db, stmt);
16568
+ if (result.sticky_turn_reminder_text.length === 0) {
16569
+ return null;
16283
16570
  }
16284
- return stmt;
16571
+ return {
16572
+ text: result.sticky_turn_reminder_text,
16573
+ messageId: result.sticky_turn_reminder_message_id.length > 0 ? result.sticky_turn_reminder_message_id : null
16574
+ };
16285
16575
  }
16286
- function getDeleteFtsStatement(db) {
16287
- let stmt = deleteFtsStatements.get(db);
16288
- if (!stmt) {
16289
- stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ?");
16290
- deleteFtsStatements.set(db, stmt);
16291
- }
16292
- return stmt;
16576
+ function setPersistedStickyTurnReminder(db, sessionId, text, messageId = "") {
16577
+ db.transaction(() => {
16578
+ ensureSessionMetaRow(db, sessionId);
16579
+ db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = ?, sticky_turn_reminder_message_id = ? WHERE session_id = ?").run(text, messageId, sessionId);
16580
+ })();
16293
16581
  }
16294
- function getDeleteIndexStatement(db) {
16295
- let stmt = deleteIndexStatements.get(db);
16296
- if (!stmt) {
16297
- stmt = db.prepare("DELETE FROM message_history_index WHERE session_id = ?");
16298
- deleteIndexStatements.set(db, stmt);
16582
+ function clearPersistedStickyTurnReminder(db, sessionId) {
16583
+ db.prepare("UPDATE session_meta SET sticky_turn_reminder_text = '', sticky_turn_reminder_message_id = '' WHERE session_id = ?").run(sessionId);
16584
+ }
16585
+ function getPersistedNoteNudge(db, sessionId) {
16586
+ const result = db.prepare("SELECT note_nudge_trigger_pending, note_nudge_trigger_message_id, note_nudge_sticky_text, note_nudge_sticky_message_id FROM session_meta WHERE session_id = ?").get(sessionId);
16587
+ if (!isPersistedNoteNudgeRow(result)) {
16588
+ return getDefaultPersistedNoteNudge();
16299
16589
  }
16300
- return stmt;
16301
- }
16302
- function getLastIndexedOrdinal(db, sessionId) {
16303
- const row = getLastIndexedStatement(db).get(sessionId);
16304
- return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
16590
+ return {
16591
+ triggerPending: result.note_nudge_trigger_pending === 1,
16592
+ triggerMessageId: result.note_nudge_trigger_message_id.length > 0 ? result.note_nudge_trigger_message_id : null,
16593
+ stickyText: result.note_nudge_sticky_text.length > 0 ? result.note_nudge_sticky_text : null,
16594
+ stickyMessageId: result.note_nudge_sticky_message_id.length > 0 ? result.note_nudge_sticky_message_id : null
16595
+ };
16305
16596
  }
16306
- function clearIndexedMessages(db, sessionId) {
16597
+ function setPersistedNoteNudgeTrigger(db, sessionId, triggerMessageId = "") {
16307
16598
  db.transaction(() => {
16308
- getDeleteFtsStatement(db).run(sessionId);
16309
- getDeleteIndexStatement(db).run(sessionId);
16599
+ ensureSessionMetaRow(db, sessionId);
16600
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_pending = 1, note_nudge_trigger_message_id = ?, note_nudge_sticky_text = '', note_nudge_sticky_message_id = '' WHERE session_id = ?").run(triggerMessageId, sessionId);
16310
16601
  })();
16311
16602
  }
16312
- function getIndexableContent(role, parts) {
16313
- if (role === "user") {
16314
- if (!hasMeaningfulUserText(parts)) {
16315
- return "";
16316
- }
16317
- return extractTexts(parts).map(cleanUserText).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16318
- }
16319
- if (role === "assistant") {
16320
- return extractTexts(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
16321
- }
16322
- return "";
16603
+ function setPersistedNoteNudgeTriggerMessageId(db, sessionId, triggerMessageId) {
16604
+ db.transaction(() => {
16605
+ ensureSessionMetaRow(db, sessionId);
16606
+ db.prepare("UPDATE session_meta SET note_nudge_trigger_message_id = ? WHERE session_id = ?").run(triggerMessageId, sessionId);
16607
+ })();
16323
16608
  }
16324
- function ensureMessagesIndexed(db, sessionId, readMessages) {
16325
- const messages = readMessages(sessionId);
16326
- if (messages.length === 0) {
16327
- db.transaction(() => clearIndexedMessages(db, sessionId))();
16328
- return;
16329
- }
16330
- let lastIndexedOrdinal = getLastIndexedOrdinal(db, sessionId);
16331
- if (lastIndexedOrdinal > messages.length) {
16332
- db.transaction(() => clearIndexedMessages(db, sessionId))();
16333
- lastIndexedOrdinal = 0;
16334
- }
16335
- if (lastIndexedOrdinal >= messages.length) {
16336
- return;
16337
- }
16338
- const messagesToInsert = messages.filter((message) => message.ordinal > lastIndexedOrdinal).filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
16339
- ordinal: message.ordinal,
16340
- id: message.id,
16341
- role: message.role,
16342
- content: getIndexableContent(message.role, message.parts)
16343
- })).filter((message) => message.content.length > 0);
16344
- const now = Date.now();
16609
+ function setPersistedDeliveredNoteNudge(db, sessionId, text, messageId = "") {
16345
16610
  db.transaction(() => {
16346
- const insertMessage = getInsertMessageStatement(db);
16347
- for (const message of messagesToInsert) {
16348
- insertMessage.run(sessionId, message.ordinal, message.id, message.role, message.content);
16349
- }
16350
- getUpsertIndexStatement(db).run(sessionId, messages.length, now);
16611
+ ensureSessionMetaRow(db, sessionId);
16612
+ 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(text, messageId, sessionId);
16351
16613
  })();
16352
16614
  }
16353
-
16615
+ function clearPersistedNoteNudge(db, sessionId) {
16616
+ 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);
16617
+ }
16618
+ function getStrippedPlaceholderIds(db, sessionId) {
16619
+ const row = db.prepare("SELECT stripped_placeholder_ids FROM session_meta WHERE session_id = ?").get(sessionId);
16620
+ const raw = row?.stripped_placeholder_ids;
16621
+ if (!raw || raw.length === 0)
16622
+ return new Set;
16623
+ try {
16624
+ const parsed = JSON.parse(raw);
16625
+ if (Array.isArray(parsed))
16626
+ return new Set(parsed.filter((v) => typeof v === "string"));
16627
+ } catch {}
16628
+ return new Set;
16629
+ }
16630
+ function setStrippedPlaceholderIds(db, sessionId, ids) {
16631
+ ensureSessionMetaRow(db, sessionId);
16632
+ const json2 = ids.size > 0 ? JSON.stringify([...ids]) : "";
16633
+ db.prepare("UPDATE session_meta SET stripped_placeholder_ids = ? WHERE session_id = ?").run(json2, sessionId);
16634
+ }
16635
+ function removeStrippedPlaceholderId(db, sessionId, messageId) {
16636
+ const ids = getStrippedPlaceholderIds(db, sessionId);
16637
+ if (!ids.delete(messageId)) {
16638
+ return false;
16639
+ }
16640
+ setStrippedPlaceholderIds(db, sessionId, ids);
16641
+ return true;
16642
+ }
16354
16643
  // src/features/magic-context/storage-meta-session.ts
16355
16644
  function getOrCreateSessionMeta(db, sessionId) {
16356
16645
  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, cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?").get(sessionId);
@@ -16516,6 +16805,9 @@ function getSourceContents(db, sessionId, tagIds) {
16516
16805
  var insertTagStatements = new WeakMap;
16517
16806
  var updateTagStatusStatements = new WeakMap;
16518
16807
  var updateTagMessageIdStatements = new WeakMap;
16808
+ var getTagNumbersByMessageIdStatements = new WeakMap;
16809
+ var deleteTagsByMessageIdStatements = new WeakMap;
16810
+ var getMaxTagNumberBySessionStatements = new WeakMap;
16519
16811
  function getInsertTagStatement(db) {
16520
16812
  let stmt = insertTagStatements.get(db);
16521
16813
  if (!stmt) {
@@ -16540,6 +16832,30 @@ function getUpdateTagMessageIdStatement(db) {
16540
16832
  }
16541
16833
  return stmt;
16542
16834
  }
16835
+ function getTagNumbersByMessageIdStatement(db) {
16836
+ let stmt = getTagNumbersByMessageIdStatements.get(db);
16837
+ if (!stmt) {
16838
+ stmt = db.prepare("SELECT tag_number FROM tags WHERE session_id = ? AND (message_id = ? OR message_id LIKE ? ESCAPE '\\' OR message_id LIKE ? ESCAPE '\\') ORDER BY tag_number ASC");
16839
+ getTagNumbersByMessageIdStatements.set(db, stmt);
16840
+ }
16841
+ return stmt;
16842
+ }
16843
+ function getDeleteTagsByMessageIdStatement(db) {
16844
+ let stmt = deleteTagsByMessageIdStatements.get(db);
16845
+ if (!stmt) {
16846
+ stmt = db.prepare("DELETE FROM tags WHERE session_id = ? AND (message_id = ? OR message_id LIKE ? ESCAPE '\\' OR message_id LIKE ? ESCAPE '\\')");
16847
+ deleteTagsByMessageIdStatements.set(db, stmt);
16848
+ }
16849
+ return stmt;
16850
+ }
16851
+ function getMaxTagNumberBySessionStatement(db) {
16852
+ let stmt = getMaxTagNumberBySessionStatements.get(db);
16853
+ if (!stmt) {
16854
+ stmt = db.prepare("SELECT COALESCE(MAX(tag_number), 0) AS max_tag_number FROM tags WHERE session_id = ?");
16855
+ getMaxTagNumberBySessionStatements.set(db, stmt);
16856
+ }
16857
+ return stmt;
16858
+ }
16543
16859
  function isTagRow(row) {
16544
16860
  if (row === null || typeof row !== "object")
16545
16861
  return false;
@@ -16558,6 +16874,21 @@ function toTagEntry(row) {
16558
16874
  sessionId: row.session_id
16559
16875
  };
16560
16876
  }
16877
+ function isTagNumberRow(row) {
16878
+ if (row === null || typeof row !== "object")
16879
+ return false;
16880
+ const r = row;
16881
+ return typeof r.tag_number === "number";
16882
+ }
16883
+ function isMaxTagNumberRow(row) {
16884
+ if (row === null || typeof row !== "object")
16885
+ return false;
16886
+ const r = row;
16887
+ return typeof r.max_tag_number === "number";
16888
+ }
16889
+ function escapeLikePattern(value) {
16890
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
16891
+ }
16561
16892
  function insertTag(db, sessionId, messageId, type, byteSize2, tagNumber) {
16562
16893
  getInsertTagStatement(db).run(sessionId, messageId, type, byteSize2, tagNumber);
16563
16894
  return tagNumber;
@@ -16568,6 +16899,21 @@ function updateTagStatus(db, sessionId, tagId, status) {
16568
16899
  function updateTagMessageId(db, sessionId, tagId, messageId) {
16569
16900
  getUpdateTagMessageIdStatement(db).run(messageId, sessionId, tagId);
16570
16901
  }
16902
+ function deleteTagsByMessageId(db, sessionId, messageId) {
16903
+ const escapedMessageId = escapeLikePattern(messageId);
16904
+ const textPartPattern = `${escapedMessageId}:p%`;
16905
+ const filePartPattern = `${escapedMessageId}:file%`;
16906
+ const tagNumbers = getTagNumbersByMessageIdStatement(db).all(sessionId, messageId, textPartPattern, filePartPattern).filter(isTagNumberRow).map((row) => row.tag_number);
16907
+ if (tagNumbers.length === 0) {
16908
+ return [];
16909
+ }
16910
+ getDeleteTagsByMessageIdStatement(db).run(sessionId, messageId, textPartPattern, filePartPattern);
16911
+ return tagNumbers;
16912
+ }
16913
+ function getMaxTagNumberBySession(db, sessionId) {
16914
+ const row = getMaxTagNumberBySessionStatement(db).get(sessionId);
16915
+ return isMaxTagNumberRow(row) ? row.max_tag_number : 0;
16916
+ }
16571
16917
  function getTagsBySession(db, sessionId) {
16572
16918
  const rows = db.prepare("SELECT id, message_id, type, status, byte_size, session_id, tag_number FROM tags WHERE session_id = ? ORDER BY tag_number ASC, id ASC").all(sessionId).filter(isTagRow);
16573
16919
  return rows.map(toTagEntry);
@@ -17440,6 +17786,85 @@ function getMessageUpdatedAssistantInfo(properties) {
17440
17786
  }
17441
17787
  };
17442
17788
  }
17789
+ function getMessageRemovedInfo(properties) {
17790
+ if (!isRecord(properties)) {
17791
+ return null;
17792
+ }
17793
+ if (typeof properties.sessionID !== "string" || typeof properties.messageID !== "string") {
17794
+ return null;
17795
+ }
17796
+ return {
17797
+ sessionID: properties.sessionID,
17798
+ messageID: properties.messageID
17799
+ };
17800
+ }
17801
+
17802
+ // src/hooks/magic-context/note-nudger.ts
17803
+ var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
17804
+ var lastDeliveredAt = new Map;
17805
+ function getPersistedNoteNudgeDeliveredAt(_db, sessionId) {
17806
+ return lastDeliveredAt.get(sessionId) ?? 0;
17807
+ }
17808
+ function recordNoteNudgeDeliveryTime(sessionId) {
17809
+ lastDeliveredAt.set(sessionId, Date.now());
17810
+ }
17811
+ function onNoteTrigger(db, sessionId, trigger) {
17812
+ setPersistedNoteNudgeTrigger(db, sessionId);
17813
+ sessionLog(sessionId, `note-nudge: trigger fired (${trigger}), triggerPending=true`);
17814
+ }
17815
+ function peekNoteNudgeText(db, sessionId, currentUserMessageId, projectIdentity) {
17816
+ const state = getPersistedNoteNudge(db, sessionId);
17817
+ if (!state.triggerPending)
17818
+ return null;
17819
+ if (!state.triggerMessageId && currentUserMessageId) {
17820
+ setPersistedNoteNudgeTriggerMessageId(db, sessionId, currentUserMessageId);
17821
+ state.triggerMessageId = currentUserMessageId;
17822
+ }
17823
+ if (state.triggerMessageId && currentUserMessageId && state.triggerMessageId === currentUserMessageId) {
17824
+ sessionLog(sessionId, `note-nudge: deferring \u2014 current user message ${currentUserMessageId} is same as trigger-time message`);
17825
+ return null;
17826
+ }
17827
+ const deliveredAt = getPersistedNoteNudgeDeliveredAt(db, sessionId);
17828
+ if (deliveredAt > 0 && Date.now() - deliveredAt < NOTE_NUDGE_COOLDOWN_MS) {
17829
+ sessionLog(sessionId, `note-nudge: suppressing \u2014 last delivered ${Math.round((Date.now() - deliveredAt) / 1000)}s ago (cooldown ${NOTE_NUDGE_COOLDOWN_MS / 60000}m)`);
17830
+ clearPersistedNoteNudge(db, sessionId);
17831
+ return null;
17832
+ }
17833
+ const notes = getSessionNotes(db, sessionId);
17834
+ const readySmartCount = projectIdentity ? getReadySmartNotes(db, projectIdentity).length : 0;
17835
+ const totalCount = notes.length + readySmartCount;
17836
+ if (totalCount === 0) {
17837
+ sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
17838
+ clearPersistedNoteNudge(db, sessionId);
17839
+ return null;
17840
+ }
17841
+ const parts = [];
17842
+ if (notes.length > 0) {
17843
+ parts.push(`${notes.length} deferred note${notes.length === 1 ? "" : "s"}`);
17844
+ }
17845
+ if (readySmartCount > 0) {
17846
+ parts.push(`${readySmartCount} ready smart note${readySmartCount === 1 ? "" : "s"}`);
17847
+ }
17848
+ sessionLog(sessionId, `note-nudge: delivering nudge for ${parts.join(" and ")}`);
17849
+ return `You have ${parts.join(" and ")}. Review with ctx_note read \u2014 some may be actionable now.`;
17850
+ }
17851
+ function markNoteNudgeDelivered(db, sessionId, text, messageId) {
17852
+ setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
17853
+ recordNoteNudgeDeliveryTime(sessionId);
17854
+ sessionLog(sessionId, messageId ? `note-nudge: marked delivered, sticky anchor=${messageId}` : "note-nudge: marked delivered without anchor");
17855
+ }
17856
+ function getStickyNoteNudge(db, sessionId) {
17857
+ const state = getPersistedNoteNudge(db, sessionId);
17858
+ if (!state.stickyText || !state.stickyMessageId)
17859
+ return null;
17860
+ return { text: state.stickyText, messageId: state.stickyMessageId };
17861
+ }
17862
+ function clearNoteNudgeState(db, sessionId, options) {
17863
+ if (options?.persist !== false) {
17864
+ clearPersistedNoteNudge(db, sessionId);
17865
+ }
17866
+ lastDeliveredAt.delete(sessionId);
17867
+ }
17443
17868
 
17444
17869
  // src/hooks/magic-context/event-handler.ts
17445
17870
  var CONTEXT_USAGE_TTL_MS = 60 * 60 * 1000;
@@ -17451,6 +17876,46 @@ function evictExpiredUsageEntries(contextUsageMap) {
17451
17876
  }
17452
17877
  }
17453
17878
  }
17879
+ function cleanupRemovedMessageState(deps, sessionId, messageId) {
17880
+ return deps.db.transaction(() => {
17881
+ const removedTagNumbers = deleteTagsByMessageId(deps.db, sessionId, messageId);
17882
+ sessionLog(sessionId, `event message.removed: deleted ${removedTagNumbers.length} tag(s) for message ${messageId}`);
17883
+ const strippedPlaceholderRemoved = removeStrippedPlaceholderId(deps.db, sessionId, messageId);
17884
+ sessionLog(sessionId, strippedPlaceholderRemoved ? `event message.removed: removed ${messageId} from stripped placeholder ids` : `event message.removed: stripped placeholder ids unchanged for ${messageId}`);
17885
+ const persistedNudgePlacement = getPersistedNudgePlacement(deps.db, sessionId);
17886
+ const clearedNudgePlacement = persistedNudgePlacement?.messageId === messageId;
17887
+ if (clearedNudgePlacement) {
17888
+ clearPersistedNudgePlacement(deps.db, sessionId);
17889
+ }
17890
+ sessionLog(sessionId, clearedNudgePlacement ? `event message.removed: cleared nudge anchor for ${messageId}` : `event message.removed: nudge anchor unchanged for ${messageId}`);
17891
+ const persistedNoteNudge = getPersistedNoteNudge(deps.db, sessionId);
17892
+ const clearedNoteNudge = persistedNoteNudge.triggerMessageId === messageId || persistedNoteNudge.stickyMessageId === messageId;
17893
+ if (clearedNoteNudge) {
17894
+ clearPersistedNoteNudge(deps.db, sessionId);
17895
+ }
17896
+ sessionLog(sessionId, clearedNoteNudge ? `event message.removed: cleared note nudge state for ${messageId}` : `event message.removed: note nudge state unchanged for ${messageId}`);
17897
+ const persistedStickyTurnReminder = getPersistedStickyTurnReminder(deps.db, sessionId);
17898
+ const clearedStickyTurnReminder = persistedStickyTurnReminder?.messageId === messageId;
17899
+ if (clearedStickyTurnReminder) {
17900
+ clearPersistedStickyTurnReminder(deps.db, sessionId);
17901
+ }
17902
+ sessionLog(sessionId, clearedStickyTurnReminder ? `event message.removed: cleared sticky turn reminder for ${messageId}` : `event message.removed: sticky turn reminder unchanged for ${messageId}`);
17903
+ const currentWatermark = getPersistedReasoningWatermark(deps.db, sessionId);
17904
+ const maxRemainingTag = getMaxTagNumberBySession(deps.db, sessionId);
17905
+ if (currentWatermark > maxRemainingTag) {
17906
+ setPersistedReasoningWatermark(deps.db, sessionId, maxRemainingTag);
17907
+ sessionLog(sessionId, `event message.removed: reset reasoning watermark ${currentWatermark}\u2192${maxRemainingTag}`);
17908
+ } else {
17909
+ sessionLog(sessionId, `event message.removed: reasoning watermark unchanged at ${currentWatermark} (max tag ${maxRemainingTag})`);
17910
+ }
17911
+ const removedIndexedMessages = deleteIndexedMessage(deps.db, sessionId, messageId);
17912
+ sessionLog(sessionId, `event message.removed: deleted ${removedIndexedMessages} indexed message row(s) for ${messageId}`);
17913
+ return {
17914
+ clearedNudgePlacement,
17915
+ clearedNoteNudge
17916
+ };
17917
+ })();
17918
+ }
17454
17919
  function createEventHandler2(deps) {
17455
17920
  return async (input) => {
17456
17921
  evictExpiredUsageEntries(deps.contextUsageMap);
@@ -17539,6 +18004,37 @@ function createEventHandler2(deps) {
17539
18004
  }
17540
18005
  return;
17541
18006
  }
18007
+ if (input.event.type === "message.removed") {
18008
+ const info = getMessageRemovedInfo(input.event.properties);
18009
+ if (!info) {
18010
+ const sessionId = properties ? resolveSessionId(properties) : null;
18011
+ if (sessionId) {
18012
+ sessionLog(sessionId, "event message.removed: no message removal info extracted from event");
18013
+ } else {
18014
+ log("[magic-context] event message.removed: no message removal info extracted from event");
18015
+ }
18016
+ return;
18017
+ }
18018
+ sessionLog(info.sessionID, `event message.removed: invalidating state for message ${info.messageID}`);
18019
+ try {
18020
+ const cleanup = cleanupRemovedMessageState(deps, info.sessionID, info.messageID);
18021
+ deps.tagger.cleanup(info.sessionID);
18022
+ sessionLog(info.sessionID, "event message.removed: invalidated tagger session cache");
18023
+ if (cleanup.clearedNudgePlacement) {
18024
+ deps.nudgePlacements.clear(info.sessionID, { persist: false });
18025
+ sessionLog(info.sessionID, "event message.removed: cleared in-memory nudge placement cache");
18026
+ }
18027
+ if (cleanup.clearedNoteNudge) {
18028
+ clearNoteNudgeState(deps.db, info.sessionID, { persist: false });
18029
+ sessionLog(info.sessionID, "event message.removed: cleared in-memory note nudge state");
18030
+ }
18031
+ deps.onSessionCacheInvalidated?.(info.sessionID);
18032
+ sessionLog(info.sessionID, "event message.removed: cleared session injection cache");
18033
+ } catch (error48) {
18034
+ sessionLog(info.sessionID, "event message.removed cleanup failed:", error48);
18035
+ }
18036
+ return;
18037
+ }
17542
18038
  if (input.event.type === "session.compacted") {
17543
18039
  const sessionId = resolveSessionId(properties);
17544
18040
  if (!sessionId) {
@@ -18383,46 +18879,6 @@ function createTextCompleteHandler() {
18383
18879
  };
18384
18880
  }
18385
18881
 
18386
- // src/hooks/magic-context/note-nudger.ts
18387
- function onNoteTrigger(db, sessionId, trigger) {
18388
- setPersistedNoteNudgeTrigger(db, sessionId);
18389
- sessionLog(sessionId, `note-nudge: trigger fired (${trigger}), triggerPending=true`);
18390
- }
18391
- function peekNoteNudgeText(db, sessionId, currentUserMessageId) {
18392
- const state = getPersistedNoteNudge(db, sessionId);
18393
- if (!state.triggerPending)
18394
- return null;
18395
- if (!state.triggerMessageId && currentUserMessageId) {
18396
- setPersistedNoteNudgeTriggerMessageId(db, sessionId, currentUserMessageId);
18397
- state.triggerMessageId = currentUserMessageId;
18398
- }
18399
- if (state.triggerMessageId && currentUserMessageId && state.triggerMessageId === currentUserMessageId) {
18400
- sessionLog(sessionId, `note-nudge: deferring \u2014 current user message ${currentUserMessageId} is same as trigger-time message`);
18401
- return null;
18402
- }
18403
- const notes = getSessionNotes(db, sessionId);
18404
- if (notes.length === 0) {
18405
- sessionLog(sessionId, "note-nudge: triggerPending but no notes found, skipping");
18406
- return null;
18407
- }
18408
- sessionLog(sessionId, `note-nudge: delivering nudge for ${notes.length} notes`);
18409
- const plural = notes.length === 1 ? "note" : "notes";
18410
- return `You have ${notes.length} deferred ${plural}. Review with ctx_note read \u2014 some may be actionable now.`;
18411
- }
18412
- function markNoteNudgeDelivered(db, sessionId, text, messageId) {
18413
- setPersistedDeliveredNoteNudge(db, sessionId, messageId ? text : "", messageId ?? "");
18414
- sessionLog(sessionId, messageId ? `note-nudge: marked delivered, sticky anchor=${messageId}` : "note-nudge: marked delivered without anchor");
18415
- }
18416
- function getStickyNoteNudge(db, sessionId) {
18417
- const state = getPersistedNoteNudge(db, sessionId);
18418
- if (!state.stickyText || !state.stickyMessageId)
18419
- return null;
18420
- return { text: state.stickyText, messageId: state.stickyMessageId };
18421
- }
18422
- function clearNoteNudgeState(db, sessionId) {
18423
- clearPersistedNoteNudge(db, sessionId);
18424
- }
18425
-
18426
18882
  // src/hooks/magic-context/strip-content.ts
18427
18883
  var DROPPED_PLACEHOLDER_PATTERN = /^\[dropped \u00A7\d+\u00A7\]$/;
18428
18884
  var TAG_PREFIX_PATTERN = /^\u00A7\d+\u00A7\s*/;
@@ -21273,9 +21729,50 @@ function runPostTransformPhase(args) {
21273
21729
  sessionLog(args.sessionId, `transform: injected ${compartmentResult.compartmentCount} compartments ` + `(covering raw messages 1-${compartmentResult.compartmentEndMessage}, ` + `skipped ${compartmentResult.skippedVisibleMessages} visible messages)`);
21274
21730
  }
21275
21731
  }
21276
- const strippedDropped = stripDroppedPlaceholderMessages(args.messages);
21277
- if (strippedDropped > 0) {
21278
- sessionLog(args.sessionId, `stripped ${strippedDropped} empty dropped-placeholder messages`);
21732
+ {
21733
+ const persistedIds = getStrippedPlaceholderIds(args.db, args.sessionId);
21734
+ if (persistedIds.size > 0) {
21735
+ let replayed = 0;
21736
+ for (let i = args.messages.length - 1;i >= 0; i--) {
21737
+ const msgId = args.messages[i].info.id;
21738
+ if (msgId && persistedIds.has(msgId)) {
21739
+ args.messages.splice(i, 1);
21740
+ replayed++;
21741
+ }
21742
+ }
21743
+ if (replayed > 0) {
21744
+ sessionLog(args.sessionId, `placeholder replay: removed ${replayed} previously-stripped messages`);
21745
+ }
21746
+ }
21747
+ if (isCacheBustingPass) {
21748
+ const preStripIds = new Set;
21749
+ for (const msg of args.messages) {
21750
+ if (msg.info.id)
21751
+ preStripIds.add(msg.info.id);
21752
+ }
21753
+ const strippedDropped = stripDroppedPlaceholderMessages(args.messages);
21754
+ if (strippedDropped > 0) {
21755
+ const postStripIds = new Set;
21756
+ for (const msg of args.messages) {
21757
+ if (msg.info.id)
21758
+ postStripIds.add(msg.info.id);
21759
+ }
21760
+ let newlyStrippedCount = 0;
21761
+ for (const id of preStripIds) {
21762
+ if (!postStripIds.has(id)) {
21763
+ persistedIds.add(id);
21764
+ newlyStrippedCount++;
21765
+ }
21766
+ }
21767
+ for (const id of persistedIds) {
21768
+ if (!preStripIds.has(id) && !postStripIds.has(id)) {
21769
+ persistedIds.delete(id);
21770
+ }
21771
+ }
21772
+ setStrippedPlaceholderIds(args.db, args.sessionId, persistedIds);
21773
+ sessionLog(args.sessionId, `stripped ${strippedDropped} placeholder messages (${newlyStrippedCount} new, ${persistedIds.size} total persisted)`);
21774
+ }
21775
+ }
21279
21776
  }
21280
21777
  if (isCacheBustingPass) {
21281
21778
  const protectedTailStart = Math.max(0, args.messages.length - args.protectedTags * 2);
@@ -21370,10 +21867,10 @@ function createNudgePlacementStore(db) {
21370
21867
  store.set(sessionId, persisted);
21371
21868
  return persisted;
21372
21869
  },
21373
- clear(sessionId) {
21870
+ clear(sessionId, options) {
21374
21871
  store.delete(sessionId);
21375
21872
  missingSessions.add(sessionId);
21376
- if (db) {
21873
+ if (db && options?.persist !== false) {
21377
21874
  clearPersistedNudgePlacement(db, sessionId);
21378
21875
  }
21379
21876
  }
@@ -21600,6 +22097,9 @@ function createEventHook(args) {
21600
22097
  args.commitSeenLastPass?.delete(sessionId);
21601
22098
  clearNoteNudgeState(args.db, sessionId);
21602
22099
  }
22100
+ if (input.event.type === "message.removed") {
22101
+ return;
22102
+ }
21603
22103
  const entry = args.contextUsageMap.get(sessionId);
21604
22104
  if (!entry)
21605
22105
  return;
@@ -21666,7 +22166,11 @@ function createToolExecuteAfterHook(args) {
21666
22166
  args.recentReduceBySession.set(typedInput.sessionID, Date.now());
21667
22167
  }
21668
22168
  if (typedInput.tool === "todowrite") {
21669
- onNoteTrigger(args.db, typedInput.sessionID, "todos_complete");
22169
+ const todoArgs = typedInput.args;
22170
+ const todos = todoArgs?.todos;
22171
+ if (Array.isArray(todos) && todos.length > 0 && todos.every((t) => t.status === "completed" || t.status === "cancelled")) {
22172
+ onNoteTrigger(args.db, typedInput.sessionID, "todos_complete");
22173
+ }
21670
22174
  }
21671
22175
  if (typedInput.tool === "ctx_note") {
21672
22176
  clearNoteNudgeState(args.db, typedInput.sessionID);
@@ -21675,6 +22179,10 @@ function createToolExecuteAfterHook(args) {
21675
22179
  };
21676
22180
  }
21677
22181
 
22182
+ // src/hooks/magic-context/system-prompt-hash.ts
22183
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
22184
+ import { join as join8 } from "path";
22185
+
21678
22186
  // src/agents/magic-context-prompt.ts
21679
22187
  var BASE_INTRO = (protectedTags) => `Messages and tool outputs are tagged with \xA7N\xA7 identifiers (e.g., \xA71\xA7, \xA742\xA7).
21680
22188
  Use \`ctx_reduce\` to manage context size. It supports one operation:
@@ -21684,6 +22192,13 @@ Use \`ctx_note\` for deferred intentions \u2014 things to tackle later, not righ
21684
22192
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
21685
22193
  Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
21686
22194
  Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start=N end=M>\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
22195
+ **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
22196
+ - Can't remember where a related codebase or dependency lives \u2192 \`ctx_search(query="opencode source code path")\`
22197
+ - Forgot a prior architectural decision or constraint \u2192 \`ctx_search(query="why did we choose SQLite over postgres")\`
22198
+ - Need a config value, API key location, or environment detail \u2192 \`ctx_search(query="embedding provider configuration")\`
22199
+ - Looking for how something was implemented previously \u2192 \`ctx_search(query="how does the dreamer lease work")\`
22200
+ - Want to recall what was decided in an earlier conversation \u2192 \`ctx_search(query="dashboard release signing setup")\`
22201
+ \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.
21687
22202
  NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
21688
22203
  NEVER drop user messages \u2014 they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
21689
22204
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
@@ -21692,7 +22207,14 @@ var BASE_INTRO_NO_REDUCE = `Messages and tool outputs are tagged with \xA7N\xA7
21692
22207
  Use \`ctx_note\` for deferred intentions \u2014 things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
21693
22208
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
21694
22209
  Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
21695
- Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start=N end=M>\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.`;
22210
+ Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start=N end=M>\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
22211
+ **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
22212
+ - Can't remember where a related codebase or dependency lives \u2192 \`ctx_search(query="opencode source code path")\`
22213
+ - Forgot a prior architectural decision or constraint \u2192 \`ctx_search(query="why did we choose SQLite over postgres")\`
22214
+ - Need a config value, API key location, or environment detail \u2192 \`ctx_search(query="embedding provider configuration")\`
22215
+ - Looking for how something was implemented previously \u2192 \`ctx_search(query="how does the dreamer lease work")\`
22216
+ - Want to recall what was decided in an earlier conversation \u2192 \`ctx_search(query="dashboard release signing setup")\`
22217
+ \`ctx_search\` returns ranked results from memories, session facts, and raw message history. Use message ordinals from results with \`ctx_expand\` to retrieve surrounding conversation context.`;
21696
22218
  var SISYPHUS_SECTION = `
21697
22219
  ### Reduction Triggers
21698
22220
  - After collecting background agent results (explore/librarian) \u2014 drop raw outputs once you extracted what you need.
@@ -21829,16 +22351,20 @@ function detectAgentFromSystemPrompt(systemPrompt) {
21829
22351
  }
21830
22352
  return null;
21831
22353
  }
21832
- function buildMagicContextSection(agent, protectedTags, ctxReduceEnabled = true) {
22354
+ function buildMagicContextSection(agent, protectedTags, ctxReduceEnabled = true, dreamerEnabled = false) {
22355
+ const smartNoteGuidance = dreamerEnabled ? `
22356
+ When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
22357
+ The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
22358
+ Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`` : "";
21833
22359
  if (!ctxReduceEnabled) {
21834
22360
  return `## Magic Context
21835
22361
 
21836
- ${BASE_INTRO_NO_REDUCE}`;
22362
+ ${BASE_INTRO_NO_REDUCE}${smartNoteGuidance}`;
21837
22363
  }
21838
22364
  const section = agent ? AGENT_SECTIONS[agent] : GENERIC_SECTION;
21839
22365
  return `## Magic Context
21840
22366
 
21841
- ${BASE_INTRO(protectedTags)}
22367
+ ${BASE_INTRO(protectedTags)}${smartNoteGuidance}
21842
22368
  ${section}
21843
22369
 
21844
22370
  Prefer many small targeted operations over one large blanket operation. Compress early and often \u2014 don't wait for warnings.`;
@@ -21846,8 +22372,37 @@ Prefer many small targeted operations over one large blanket operation. Compress
21846
22372
 
21847
22373
  // src/hooks/magic-context/system-prompt-hash.ts
21848
22374
  var MAGIC_CONTEXT_MARKER = "## Magic Context";
22375
+ var PROJECT_DOCS_MARKER = "<project-docs>";
22376
+ var DOC_FILES = ["ARCHITECTURE.md", "STRUCTURE.md"];
22377
+ function readProjectDocs(directory) {
22378
+ const sections = [];
22379
+ for (const filename of DOC_FILES) {
22380
+ const filePath = join8(directory, filename);
22381
+ try {
22382
+ if (existsSync4(filePath)) {
22383
+ const content = readFileSync3(filePath, "utf-8").trim();
22384
+ if (content.length > 0) {
22385
+ sections.push(`<${filename}>
22386
+ ${content}
22387
+ </${filename}>`);
22388
+ }
22389
+ }
22390
+ } catch (error48) {
22391
+ log(`[magic-context] failed to read ${filename}:`, error48);
22392
+ }
22393
+ }
22394
+ if (sections.length === 0)
22395
+ return null;
22396
+ return `${PROJECT_DOCS_MARKER}
22397
+ ${sections.join(`
22398
+
22399
+ `)}
22400
+ </project-docs>`;
22401
+ }
21849
22402
  function createSystemPromptHashHandler(deps) {
21850
22403
  const stickyDateBySession = new Map;
22404
+ const cachedDocsBySession = new Map;
22405
+ const shouldInjectDocs = deps.dreamerEnabled && deps.injectDocs;
21851
22406
  return async (input, output) => {
21852
22407
  const sessionId = input.sessionID;
21853
22408
  if (!sessionId)
@@ -21856,11 +22411,27 @@ function createSystemPromptHashHandler(deps) {
21856
22411
  `);
21857
22412
  if (fullPrompt.length > 0 && !fullPrompt.includes(MAGIC_CONTEXT_MARKER)) {
21858
22413
  const detectedAgent = detectAgentFromSystemPrompt(fullPrompt);
21859
- const guidance = buildMagicContextSection(detectedAgent, deps.protectedTags, deps.ctxReduceEnabled);
22414
+ const guidance = buildMagicContextSection(detectedAgent, deps.protectedTags, deps.ctxReduceEnabled, deps.dreamerEnabled);
21860
22415
  output.system.push(guidance);
21861
22416
  sessionLog(sessionId, `injected ${detectedAgent ?? "generic"} guidance into system prompt`);
21862
22417
  }
21863
22418
  const isCacheBusting = deps.flushedSessions.has(sessionId);
22419
+ if (shouldInjectDocs) {
22420
+ const hasCached = cachedDocsBySession.has(sessionId);
22421
+ if (!hasCached || isCacheBusting) {
22422
+ const docsContent = readProjectDocs(deps.directory);
22423
+ cachedDocsBySession.set(sessionId, docsContent);
22424
+ if (docsContent && !hasCached) {
22425
+ sessionLog(sessionId, `loaded project docs (${docsContent.length} chars)`);
22426
+ } else if (docsContent && isCacheBusting) {
22427
+ sessionLog(sessionId, "refreshed project docs (cache-busting pass)");
22428
+ }
22429
+ }
22430
+ const docsBlock = cachedDocsBySession.get(sessionId);
22431
+ if (docsBlock && !fullPrompt.includes(PROJECT_DOCS_MARKER)) {
22432
+ output.system.push(docsBlock);
22433
+ }
22434
+ }
21864
22435
  const DATE_PATTERN = /Today's date: .+/;
21865
22436
  for (let i = 0;i < output.system.length; i++) {
21866
22437
  const match = output.system[i].match(DATE_PATTERN);
@@ -22070,6 +22641,9 @@ function createMagicContextHook(deps) {
22070
22641
  db,
22071
22642
  protectedTags: deps.config.protected_tags,
22072
22643
  ctxReduceEnabled,
22644
+ dreamerEnabled: deps.config.dreamer?.enabled === true,
22645
+ injectDocs: deps.config.dreamer?.inject_docs !== false,
22646
+ directory: deps.directory,
22073
22647
  flushedSessions,
22074
22648
  lastHeuristicsTurnId
22075
22649
  });
@@ -22504,9 +23078,14 @@ var CTX_NOTE_DESCRIPTION = `Save or inspect durable session notes that persist f
22504
23078
  Use this for short goals, constraints, decisions, or reminders worth carrying forward.
22505
23079
 
22506
23080
  Actions:
22507
- - \`write\`: Append one note.
22508
- - \`read\`: Show current notes.
22509
- - \`clear\`: Remove all notes.
23081
+ - \`write\`: Append one note. Optionally provide \`surface_condition\` to create a smart note.
23082
+ - \`read\`: Show current notes (session notes + ready smart notes).
23083
+ - \`clear\`: Remove all session notes.
23084
+ - \`dismiss\`: Dismiss a ready smart note by \`note_id\`.
23085
+
23086
+ **Smart Notes**: When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
23087
+ The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
23088
+ Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`
22510
23089
 
22511
23090
  Historian reads these notes, deduplicates them, and rewrites the remaining useful notes over time.`;
22512
23091
  // src/tools/ctx-note/tools.ts
@@ -22515,8 +23094,10 @@ function createCtxNoteTool(deps) {
22515
23094
  return tool3({
22516
23095
  description: CTX_NOTE_DESCRIPTION,
22517
23096
  args: {
22518
- action: tool3.schema.enum(["write", "read", "clear"]).optional().describe("Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'."),
22519
- content: tool3.schema.string().optional().describe("Note text to store when action is 'write'.")
23097
+ action: tool3.schema.enum(["write", "read", "clear", "dismiss"]).optional().describe("Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'."),
23098
+ content: tool3.schema.string().optional().describe("Note text to store when action is 'write'."),
23099
+ surface_condition: tool3.schema.string().optional().describe("Open-ended condition for smart notes. When provided, creates a project-scoped smart note that the dreamer evaluates nightly. The note surfaces when the condition is met."),
23100
+ note_id: tool3.schema.number().optional().describe("Smart note ID to dismiss (required for 'dismiss' action).")
22520
23101
  },
22521
23102
  async execute(args, toolContext) {
22522
23103
  const sessionId = toolContext.sessionID;
@@ -22526,26 +23107,63 @@ function createCtxNoteTool(deps) {
22526
23107
  if (!content) {
22527
23108
  return "Error: 'content' is required when action is 'write'.";
22528
23109
  }
23110
+ if (args.surface_condition?.trim()) {
23111
+ if (!deps.dreamerEnabled) {
23112
+ return "Error: Smart notes require dreamer to be enabled. Enable dreamer in magic-context.jsonc to use surface_condition.";
23113
+ }
23114
+ if (!deps.projectIdentity) {
23115
+ return "Error: Could not resolve project identity for smart note.";
23116
+ }
23117
+ const note = addSmartNote(deps.db, deps.projectIdentity, content, args.surface_condition.trim(), sessionId);
23118
+ return `Created smart note #${note.id}. Dreamer will evaluate the condition during nightly runs:
23119
+ - Content: ${content}
23120
+ - Condition: ${args.surface_condition.trim()}`;
23121
+ }
22529
23122
  addSessionNote(deps.db, sessionId, content);
22530
23123
  const total = getSessionNotes(deps.db, sessionId).length;
22531
23124
  return `Saved session note ${total}. Historian will rewrite or deduplicate notes as needed.`;
22532
23125
  }
23126
+ if (action === "dismiss") {
23127
+ const noteId = args.note_id;
23128
+ if (typeof noteId !== "number") {
23129
+ return "Error: 'note_id' is required when action is 'dismiss'.";
23130
+ }
23131
+ const dismissed = dismissSmartNote(deps.db, noteId);
23132
+ return dismissed ? `Smart note #${noteId} dismissed.` : `Smart note #${noteId} not found or already dismissed.`;
23133
+ }
22533
23134
  if (action === "clear") {
22534
23135
  const existing = getSessionNotes(deps.db, sessionId);
22535
23136
  clearSessionNotes(deps.db, sessionId);
22536
23137
  return existing.length === 0 ? "Session notes were already empty." : `Cleared ${existing.length} session note${existing.length === 1 ? "" : "s"}.`;
22537
23138
  }
22538
23139
  const notes = getSessionNotes(deps.db, sessionId);
22539
- if (notes.length === 0) {
22540
- return `## Session Notes
23140
+ const readySmartNotes = deps.projectIdentity ? getReadySmartNotes(deps.db, deps.projectIdentity) : [];
23141
+ const sections = [];
23142
+ if (notes.length > 0) {
23143
+ const lines = notes.map((note, index) => `${index + 1}. ${note.content}`);
23144
+ sections.push(`## Session Notes
22541
23145
 
22542
- No session notes saved yet.`;
23146
+ ${lines.join(`
23147
+ `)}`);
22543
23148
  }
22544
- const lines = notes.map((note, index) => `${index + 1}. ${note.content}`);
22545
- return `## Session Notes
23149
+ if (readySmartNotes.length > 0) {
23150
+ const lines = readySmartNotes.map((n) => `- **#${n.id}**: ${n.content}
23151
+ Condition met: ${n.readyReason ?? n.surfaceCondition}
23152
+ _(dismiss with \`ctx_note(action="dismiss", note_id=${n.id})\`)_`);
23153
+ sections.push(`## \uD83D\uDD14 Ready Smart Notes
22546
23154
 
22547
23155
  ${lines.join(`
22548
- `)}`;
23156
+
23157
+ `)}`);
23158
+ }
23159
+ if (sections.length === 0) {
23160
+ return `## Notes
23161
+
23162
+ No session notes or smart notes.`;
23163
+ }
23164
+ return sections.join(`
23165
+
23166
+ `);
22549
23167
  }
22550
23168
  });
22551
23169
  }
@@ -22800,7 +23418,8 @@ async function getSemanticScores(args) {
22800
23418
  function getFtsMatches(args) {
22801
23419
  try {
22802
23420
  return searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit);
22803
- } catch {
23421
+ } catch (error48) {
23422
+ log(`[search] FTS query failed for "${args.query}": ${error48 instanceof Error ? error48.message : String(error48)}`);
22804
23423
  return [];
22805
23424
  }
22806
23425
  }
@@ -23134,7 +23753,11 @@ function createToolRegistry(args) {
23134
23753
  protectedTags: pluginConfig.protected_tags ?? DEFAULT_PROTECTED_TAGS
23135
23754
  }) : {},
23136
23755
  ...createCtxExpandTools(),
23137
- ...createCtxNoteTools({ db }),
23756
+ ...createCtxNoteTools({
23757
+ db,
23758
+ dreamerEnabled: pluginConfig.dreamer?.enabled === true,
23759
+ projectIdentity: projectPath
23760
+ }),
23138
23761
  ...createCtxSearchTools({
23139
23762
  db,
23140
23763
  projectPath,
@@ -23158,20 +23781,20 @@ function createToolRegistry(args) {
23158
23781
  }
23159
23782
 
23160
23783
  // src/shared/opencode-compaction-detector.ts
23161
- import { join as join9 } from "path";
23784
+ import { join as join10 } from "path";
23162
23785
 
23163
23786
  // src/shared/opencode-config-dir.ts
23164
23787
  import { homedir as homedir3 } from "os";
23165
- import { join as join8, resolve } from "path";
23788
+ import { join as join9, resolve } from "path";
23166
23789
  function getCliConfigDir() {
23167
23790
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
23168
23791
  if (envConfigDir) {
23169
23792
  return resolve(envConfigDir);
23170
23793
  }
23171
23794
  if (process.platform === "win32") {
23172
- return join8(homedir3(), ".config", "opencode");
23795
+ return join9(homedir3(), ".config", "opencode");
23173
23796
  }
23174
- return join8(process.env.XDG_CONFIG_HOME || join8(homedir3(), ".config"), "opencode");
23797
+ return join9(process.env.XDG_CONFIG_HOME || join9(homedir3(), ".config"), "opencode");
23175
23798
  }
23176
23799
  function getOpenCodeConfigDir(_options) {
23177
23800
  return getCliConfigDir();
@@ -23180,10 +23803,10 @@ function getOpenCodeConfigPaths(options) {
23180
23803
  const configDir = getOpenCodeConfigDir(options);
23181
23804
  return {
23182
23805
  configDir,
23183
- configJson: join8(configDir, "opencode.json"),
23184
- configJsonc: join8(configDir, "opencode.jsonc"),
23185
- packageJson: join8(configDir, "package.json"),
23186
- omoConfig: join8(configDir, "magic-context.jsonc")
23806
+ configJson: join9(configDir, "opencode.json"),
23807
+ configJsonc: join9(configDir, "opencode.jsonc"),
23808
+ packageJson: join9(configDir, "package.json"),
23809
+ omoConfig: join9(configDir, "magic-context.jsonc")
23187
23810
  };
23188
23811
  }
23189
23812
 
@@ -23215,15 +23838,15 @@ function isOpenCodeAutoCompactionEnabled(directory) {
23215
23838
  return true;
23216
23839
  }
23217
23840
  function readProjectCompactionConfig(directory) {
23218
- const dotOpenCodeJsonc = join9(directory, ".opencode", "opencode.jsonc");
23219
- const dotOpenCodeJson = join9(directory, ".opencode", "opencode.json");
23841
+ const dotOpenCodeJsonc = join10(directory, ".opencode", "opencode.jsonc");
23842
+ const dotOpenCodeJson = join10(directory, ".opencode", "opencode.json");
23220
23843
  const dotOpenCodeConfig = readJsoncFile(dotOpenCodeJsonc) ?? readJsoncFile(dotOpenCodeJson);
23221
23844
  const dotOpenCodeCompactionConflict = hasCompactionConflict(dotOpenCodeConfig?.compaction);
23222
23845
  if (dotOpenCodeCompactionConflict !== undefined) {
23223
23846
  return dotOpenCodeCompactionConflict;
23224
23847
  }
23225
- const rootJsonc = join9(directory, "opencode.jsonc");
23226
- const rootJson = join9(directory, "opencode.json");
23848
+ const rootJsonc = join10(directory, "opencode.jsonc");
23849
+ const rootJson = join10(directory, "opencode.json");
23227
23850
  const rootConfig = readJsoncFile(rootJsonc) ?? readJsoncFile(rootJson);
23228
23851
  return hasCompactionConflict(rootConfig?.compaction);
23229
23852
  }