@cortexkit/opencode-magic-context 0.49.11 → 0.49.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agents/smart-notes-dreamer-gate.fixture.d.ts +28 -0
- package/dist/agents/smart-notes-dreamer-gate.fixture.d.ts.map +1 -0
- package/dist/{announcement-hdbkt8v5.js → announcement-g789dmt6.js} +2 -2
- package/dist/auto-search-worker.js +4 -4
- package/dist/chunk-embedding-write-worker.js +89 -0
- package/dist/{compaction-marker-candidates-xxz35vwp.js → compaction-marker-candidates-v5c6997n.js} +3 -3
- package/dist/compaction-marker-candidates-worker.js +1 -1
- package/dist/config/agent-disable.d.ts +14 -0
- package/dist/config/agent-disable.d.ts.map +1 -1
- package/dist/config/dependency-notes.d.ts +34 -0
- package/dist/config/dependency-notes.d.ts.map +1 -0
- package/dist/config/schema/agent-overrides.d.ts +12 -12
- package/dist/config/schema/magic-context.d.ts +88 -88
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-xwkfncav.js → conflict-warning-hook-mgq17yr3.js} +8 -8
- package/dist/{context-twz3s0cg.js → context-azqk0vfd.js} +121 -145
- package/dist/{derive-budgets-3nbxsyss.js → derive-budgets-zn7yzd2d.js} +3 -3
- package/dist/embedding-backfill-5e1t5wha.js +22 -0
- package/dist/embedding-worker-process.js +2 -2
- package/dist/embedding-worker.js +6 -6
- package/dist/features/magic-context/chunk-embedding-write-worker.d.ts +2 -0
- package/dist/features/magic-context/chunk-embedding-write-worker.d.ts.map +1 -0
- package/dist/features/magic-context/chunk-embedding-writer.d.ts +48 -0
- package/dist/features/magic-context/chunk-embedding-writer.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +3 -2
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +2 -0
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts +5 -0
- package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-config.d.ts +22 -4
- package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +22 -0
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts +4 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +6 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts +25 -11
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +32 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity.d.ts +1 -0
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/unborn-head.d.ts +34 -0
- package/dist/features/magic-context/memory/unborn-head.d.ts.map +1 -0
- package/dist/features/magic-context/mural/compress-cues.d.ts +3 -0
- package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +5 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +2 -0
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/{history-owner-ty29fz0f.js → history-owner-xp3pngv7.js} +3 -3
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-embed-session.d.ts +10 -0
- package/dist/hooks/magic-context/auto-embed-session.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-prompt.d.ts +6 -0
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -0
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger-error.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-trigger-error.d.ts.map +1 -0
- package/dist/hooks/magic-context/embed-session-state.d.ts +8 -1
- package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts +2 -0
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +3 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/pending-materialization.d.ts +2 -0
- package/dist/hooks/magic-context/pending-materialization.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts +12 -0
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/raw-exact-ordinal-read.d.ts +58 -0
- package/dist/hooks/magic-context/raw-exact-ordinal-read.d.ts.map +1 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +7 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +19 -0
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +2 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +2 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +4 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-bzp9s8z2.js → index-1d289pq2.js} +2 -2
- package/dist/{index-w28y7d0z.js → index-1sarmj9z.js} +15 -15
- package/dist/{index-z7avx6rw.js → index-2jr1smmh.js} +280 -160
- package/dist/{index-4ccdr7yw.js → index-3kdm78zp.js} +5 -5
- package/dist/{index-9159kk15.js → index-3kx0kg0f.js} +64 -7
- package/dist/{index-0jz47061.js → index-a8bvhwqs.js} +190 -30
- package/dist/{index-84j7jq8a.js → index-aw1bh90w.js} +10 -10
- package/dist/{index-qr1mkc2y.js → index-b16gpywn.js} +9 -9
- package/dist/{index-g7jkrz0x.js → index-bp5083x2.js} +1 -1
- package/dist/{index-yhrrx7qa.js → index-bxdg2nbt.js} +3 -3
- package/dist/{index-0abg88h2.js → index-dgs16axy.js} +4 -4
- package/dist/{index-mp2kghkv.js → index-dyvbsvga.js} +1 -1
- package/dist/{index-bmwfzz28.js → index-e20m7284.js} +4 -4
- package/dist/{index-4jkhhpn1.js → index-e4dsjj1p.js} +3 -3
- package/dist/{index-s178h9z8.js → index-f71thw8j.js} +1 -1
- package/dist/{index-kvw6xew3.js → index-fkx2qnvw.js} +2 -2
- package/dist/{index-f8cn4xqa.js → index-fwefky9w.js} +18 -5
- package/dist/{index-0sahzkwr.js → index-g2q4htwp.js} +46 -2
- package/dist/{index-mw7c950g.js → index-gp3kcpc4.js} +1 -1
- package/dist/{index-g40wgxxt.js → index-hnm8raee.js} +4 -4
- package/dist/{index-gjvjh960.js → index-hqkczed7.js} +1 -1
- package/dist/{index-mvvephtj.js → index-j75v2476.js} +1 -1
- package/dist/{index-150zz6fh.js → index-k29z4aqh.js} +1 -1
- package/dist/{index-b64n4d0h.js → index-kee4kn5j.js} +1 -1
- package/dist/index-ksdyrrav.js +52 -0
- package/dist/{index-angx1s0k.js → index-mmfa86mt.js} +16 -6
- package/dist/{index-9jvab89d.js → index-n6m03s3n.js} +2 -2
- package/dist/{index-q28hsbpg.js → index-nj7yykkx.js} +1 -1
- package/dist/{index-ghc9f0n7.js → index-p5y3k4g7.js} +29 -1
- package/dist/{index-xztdf9t7.js → index-qkd9zz0p.js} +17 -17
- package/dist/index-saews2s3.js +229 -0
- package/dist/{index-kkvxnbt8.js → index-sj89f3b0.js} +62 -33
- package/dist/{index-jw4gbfkv.js → index-v10f1rjv.js} +5 -5
- package/dist/{index-0j6crjgh.js → index-v13skev1.js} +13 -13
- package/dist/{index-hqnggmxn.js → index-wj6ht7nm.js} +547 -179
- package/dist/{index-a865mj02.js → index-wk0n1cgt.js} +4 -4
- package/dist/{index-q3qck7j2.js → index-wz6mzzb1.js} +6 -5
- package/dist/{index-se1w3v2m.js → index-y1qysm3q.js} +332 -256
- package/dist/{index-a4b0gbyg.js → index-y9f76s3f.js} +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +119 -129
- package/dist/{logger-wn328tfg.js → logger-jm3sqqzr.js} +2 -2
- package/dist/{message-index-bounded-0rbbg9rd.js → message-index-bounded-vm64mqpq.js} +16 -16
- package/dist/{message-index-ct5x2kwa.js → message-index-seaqpb7c.js} +16 -16
- package/dist/{message-time-backfill-nb5hhnrt.js → message-time-backfill-qjge9044.js} +3 -3
- package/dist/migration-worker.js +11 -11
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{project-embedding-registry-ft9gh2e2.js → project-embedding-registry-x45bv3qr.js} +12 -11
- package/dist/raw-ordinal-warmup-worker.js +12 -7
- package/dist/{read-session-chunk-y3hhsbzw.js → read-session-chunk-ax05f7gr.js} +11 -11
- package/dist/{read-session-raw-gm6xj30v.js → read-session-raw-m56hc7v2.js} +7 -3
- package/dist/recomp-orchestrator-dmbeadwt.js +61 -0
- package/dist/{safe-notification-target-jdynm677.js → safe-notification-target-1szv1k5k.js} +3 -3
- package/dist/{search-jd8xh721.js → search-5yd5ytzs.js} +25 -24
- package/dist/{send-session-notification-vzhj8bsg.js → send-session-notification-24r4esbh.js} +6 -6
- package/dist/shared/data-path.d.ts +4 -0
- package/dist/shared/data-path.d.ts.map +1 -1
- package/dist/shared/model-suggestion-retry.d.ts +2 -0
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/prompt-async-transport.d.ts.map +1 -1
- package/dist/{storage-tags-35m4gms2.js → storage-tags-yygfb9c8.js} +7 -5
- package/dist/{task-config-cgpg0nvh.js → task-config-x3qmayd2.js} +6 -2
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-pmqxfq4q.js → transform-postprocess-phase-72v86syx.js} +32 -31
- package/dist/v2/auto-search-worker.js +4 -4
- package/dist/v2/chunk-embedding-write-worker.js +88 -0
- package/dist/v2/{compaction-marker-candidates-a15w3mqh.js → compaction-marker-candidates-1vqwr3s5.js} +2 -2
- package/dist/v2/compaction-marker-candidates-worker.js +1 -1
- package/dist/v2/{context-p1zfcw41.js → context-w055w17c.js} +628 -255
- package/dist/v2/{derive-budgets-0rmpmxjw.js → derive-budgets-2zh4gm2c.js} +3 -3
- package/dist/v2/embedding-backfill-jmaf8zrr.js +21 -0
- package/dist/v2/embedding-worker-process.js +1 -1
- package/dist/v2/embedding-worker.js +5 -5
- package/dist/v2/{history-owner-2cr9py37.js → history-owner-42e16w0n.js} +2 -2
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/dream-manual.d.ts.map +1 -1
- package/dist/v2/hooks/dream-trigger.d.ts +1 -1
- package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
- package/dist/v2/hooks/transform-features.d.ts +3 -2
- package/dist/v2/hooks/transform-features.d.ts.map +1 -1
- package/dist/v2/{logger-te6e1j72.js → logger-7qt02bnb.js} +1 -1
- package/dist/v2/{message-index-bounded-7wcbp975.js → message-index-bounded-wepg5723.js} +15 -15
- package/dist/v2/{message-index-cjhv92m2.js → message-index-gcmd0ng0.js} +15 -15
- package/dist/v2/{message-time-backfill-rz1pnpaj.js → message-time-backfill-bsteyc4d.js} +2 -2
- package/dist/v2/migration-worker.js +10 -10
- package/dist/v2/{project-embedding-registry-g34gcf74.js → project-embedding-registry-0msp56p8.js} +11 -10
- package/dist/v2/raw-ordinal-warmup-worker.js +12 -7
- package/dist/v2/{read-session-chunk-8d3v87vr.js → read-session-chunk-hr3w5bjk.js} +10 -10
- package/dist/v2/{read-session-raw-h4azh85b.js → read-session-raw-b3tf6ra1.js} +6 -2
- package/dist/v2/recomp-orchestrator-ca7qt75a.js +60 -0
- package/dist/v2/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/v2/{safe-notification-target-936374xx.js → safe-notification-target-qg7n2s77.js} +2 -2
- package/dist/v2/{search-a1acjwf8.js → search-c86dcv14.js} +24 -23
- package/dist/v2/{send-session-notification-z5mcjdea.js → send-session-notification-8ghn9tnv.js} +5 -5
- package/dist/v2/{server-npm52h2q.js → server-05xjtq6c.js} +1 -1
- package/dist/v2/{server-s6jqh8bj.js → server-0ppsweh6.js} +1 -1
- package/dist/v2/server-0s95v3en.js +49 -0
- package/dist/v2/{server-hn9fb3v0.js → server-34kwfsr4.js} +2 -2
- package/dist/v2/server-4gn3z5an.js +229 -0
- package/dist/v2/{server-kn7skz9r.js → server-6cdy4rc0.js} +3 -3
- package/dist/v2/{server-7dv9wt9r.js → server-6pmqg9m4.js} +1 -1
- package/dist/v2/{server-7fvmt15t.js → server-b81w6sw3.js} +273 -107
- package/dist/v2/{server-kmapq3v0.js → server-bxpwy6fy.js} +18 -5
- package/dist/v2/{server-d6t16r89.js → server-d8tj10db.js} +1 -1
- package/dist/v2/{server-cxsr0ng6.js → server-da07v4xp.js} +1 -1
- package/dist/v2/{server-rxcq4t50.js → server-ef89gdsx.js} +5 -5
- package/dist/v2/{server-9e5r8y60.js → server-f2xf5gcb.js} +46 -2
- package/dist/v2/{server-83d71zab.js → server-g10mdym6.js} +3 -3
- package/dist/v2/{server-njnx39q2.js → server-gvkm8xaj.js} +189 -29
- package/dist/v2/{server-ckrb5rm8.js → server-h0410h0v.js} +1 -1
- package/dist/v2/{server-sg68np13.js → server-hek6z3w6.js} +330 -254
- package/dist/v2/{server-f27he3j1.js → server-k2bzedxk.js} +4 -4
- package/dist/v2/{server-wkhfyzpg.js → server-m97brnat.js} +9 -9
- package/dist/v2/{server-erfp0xb6.js → server-n8ch9683.js} +16 -16
- package/dist/v2/{server-bx59ezg6.js → server-prg55b7k.js} +16 -6
- package/dist/v2/{server-89fcbe37.js → server-pwv4ezb0.js} +1 -1
- package/dist/v2/{server-r5p3b1wy.js → server-qd11g795.js} +29 -1
- package/dist/v2/{server-ewqshbs8.js → server-qhfvvfxj.js} +64 -7
- package/dist/v2/{server-988cqjme.js → server-rrqpvwqd.js} +2 -2
- package/dist/v2/{server-d3f4g62c.js → server-t4prcjc1.js} +13 -13
- package/dist/v2/{server-nveratm5.js → server-tbe1femw.js} +4 -4
- package/dist/v2/{server-jc8v585p.js → server-th4y11y7.js} +15 -15
- package/dist/v2/{server-h3wc20sv.js → server-vbe4se57.js} +4 -4
- package/dist/v2/{server-pbpp8r8h.js → server-xh7jar6k.js} +9 -9
- package/dist/v2/{server-63ha7f0f.js → server-yrvqaswb.js} +1 -1
- package/dist/v2/{server-5x7kmyz8.js → server-zc3e54vy.js} +10 -51
- package/dist/v2/{server-xqf2d2mg.js → server-zs8d0s3b.js} +2 -2
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +18 -10
- package/dist/v2/{storage-tags-c4zfcxv4.js → storage-tags-a2gq6dw7.js} +6 -4
- package/dist/v2/store-reader.d.ts +1 -0
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/{task-config-x0hq3qfd.js → task-config-nvz8mapa.js} +6 -2
- package/dist/v2/{wrapup-orchestrator-87q9d5e2.js → wrapup-orchestrator-e1a2w6jh.js} +31 -29
- package/dist/wrapup-orchestrator-2051r6qm.js +48 -0
- package/package.json +6 -5
- package/src/agents/smart-notes-dreamer-gate.fixture.ts +171 -0
- package/src/agents/smart-notes-dreamer-gate.golden.json +245 -0
- package/src/agents/smart-notes-dreamer-gate.test.ts +161 -0
- package/src/config/agent-disable.ts +13 -0
- package/src/config/dependency-notes.test.ts +114 -0
- package/src/config/dependency-notes.ts +80 -0
- package/src/config/schema/magic-context.ts +23 -14
- package/src/features/magic-context/dreamer/task-registry.ts +40 -0
- package/src/shared/data-path.test.ts +35 -1
- package/src/shared/data-path.ts +57 -1
- package/src/shared/model-suggestion-retry.test.ts +34 -0
- package/src/shared/model-suggestion-retry.ts +23 -3
- package/src/shared/prompt-async-transport.test.ts +1 -1
- package/src/shared/prompt-async-transport.ts +16 -3
- package/dist/embedding-backfill-fjbfh1dt.js +0 -21
- package/dist/recomp-orchestrator-67k45s0b.js +0 -60
- package/dist/v2/embedding-backfill-xcw3t6hp.js +0 -20
- package/dist/v2/recomp-orchestrator-n0pq36f0.js +0 -59
- package/dist/wrapup-orchestrator-jqapepw0.js +0 -47
- /package/dist/{index-e6xnpach.js → index-b7p25tm0.js} +0 -0
- /package/dist/v2/{index-e6xnpach.js → index-b7p25tm0.js} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { afterAll, beforeAll, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { loadPluginConfigDetailed, parsePluginConfig } from "../config";
|
|
6
|
+
import { isDreamerRunnable, isSmartNoteSurfaceAvailable } from "../config/agent-disable";
|
|
7
|
+
import { pluginConfigReader } from "../config/live-run-config";
|
|
8
|
+
import { closeDatabase, openDatabase } from "../features/magic-context/storage";
|
|
9
|
+
import type { Database } from "../shared/sqlite";
|
|
10
|
+
import { createTestTempDirFromPath } from "../shared/test-temp-dir";
|
|
11
|
+
import {
|
|
12
|
+
GATE_CASE_COUNT,
|
|
13
|
+
gateCases,
|
|
14
|
+
renderToolSurfaces,
|
|
15
|
+
type ToolSurfaceDigests,
|
|
16
|
+
} from "./smart-notes-dreamer-gate.fixture";
|
|
17
|
+
import golden from "./smart-notes-dreamer-gate.golden.json";
|
|
18
|
+
|
|
19
|
+
// Smart notes are evaluated by the dreamer, so ctx_note drops its smart-note
|
|
20
|
+
// argument and text when the dreamer is not runnable, as it already did for
|
|
21
|
+
// smart_notes.enabled=false. The golden holds the tool bytes from before that
|
|
22
|
+
// gate; every config with a runnable dreamer must still render them exactly.
|
|
23
|
+
|
|
24
|
+
const SMART_NOTE_TEXT = /surface_condition|smart.note|outside checker/i;
|
|
25
|
+
const oldXdg = process.env.XDG_DATA_HOME;
|
|
26
|
+
let dir: string;
|
|
27
|
+
let db: Database;
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
dir = createTestTempDirFromPath(join(tmpdir(), "smart-notes-dreamer-gate-"));
|
|
31
|
+
process.env.XDG_DATA_HOME = dir;
|
|
32
|
+
db = openDatabase();
|
|
33
|
+
});
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
closeDatabase();
|
|
36
|
+
if (oldXdg === undefined) delete process.env.XDG_DATA_HOME;
|
|
37
|
+
else process.env.XDG_DATA_HOME = oldXdg;
|
|
38
|
+
rmSync(dir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const render = (raw: Record<string, unknown>): Promise<ToolSurfaceDigests> => {
|
|
42
|
+
const config = parsePluginConfig(raw);
|
|
43
|
+
// The value context.ts passes to the OpenCode 2 per-request rewrite.
|
|
44
|
+
return renderToolSurfaces({
|
|
45
|
+
config,
|
|
46
|
+
directory: dir,
|
|
47
|
+
db,
|
|
48
|
+
oc2SmartNotes: isSmartNoteSurfaceAvailable(config),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
test("ctx_* tool bytes match the pre-gate golden for every config with a runnable dreamer, and only ctx_note changes when the dreamer is off", async () => {
|
|
53
|
+
const cases = gateCases();
|
|
54
|
+
const expected = (golden as { cases: Array<Omit<ToolSurfaceDigests, "text">> }).cases;
|
|
55
|
+
expect(cases).toHaveLength(GATE_CASE_COUNT);
|
|
56
|
+
expect(expected).toHaveLength(GATE_CASE_COUNT);
|
|
57
|
+
|
|
58
|
+
let runnable = 0;
|
|
59
|
+
let gatedChanged = 0;
|
|
60
|
+
let gatedAlreadyOff = 0;
|
|
61
|
+
for (const [index, raw] of cases.entries()) {
|
|
62
|
+
const config = parsePluginConfig(raw);
|
|
63
|
+
const got = await render(raw);
|
|
64
|
+
const before = expected[index];
|
|
65
|
+
if (!before) throw new Error(`golden has no case ${index}`);
|
|
66
|
+
const label = `case ${index} ${JSON.stringify(raw)}`;
|
|
67
|
+
if (isDreamerRunnable(config)) {
|
|
68
|
+
runnable++;
|
|
69
|
+
expect({ label, oc1: got.oc1, oc2: got.oc2 }).toEqual({
|
|
70
|
+
label,
|
|
71
|
+
oc1: before.oc1,
|
|
72
|
+
oc2: before.oc2,
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Dreamer off: every other tool is untouched...
|
|
77
|
+
expect({ label, oc1: got.oc1.other, oc2: got.oc2.other }).toEqual({
|
|
78
|
+
label,
|
|
79
|
+
oc1: before.oc1.other,
|
|
80
|
+
oc2: before.oc2.other,
|
|
81
|
+
});
|
|
82
|
+
// ...and ctx_note is exactly the smart-notes-off surface of the same config.
|
|
83
|
+
const off = await render({ ...raw, smart_notes: { enabled: false } });
|
|
84
|
+
expect({ label, oc1: got.oc1.note, oc2: got.oc2.note }).toEqual({
|
|
85
|
+
label,
|
|
86
|
+
oc1: off.oc1.note,
|
|
87
|
+
oc2: off.oc2.note,
|
|
88
|
+
});
|
|
89
|
+
const ctxNote = (text: string) =>
|
|
90
|
+
JSON.stringify((JSON.parse(text) as Record<string, unknown>).ctx_note);
|
|
91
|
+
expect(ctxNote(got.text.oc1)).not.toMatch(SMART_NOTE_TEXT);
|
|
92
|
+
expect(ctxNote(got.text.oc2)).not.toMatch(SMART_NOTE_TEXT);
|
|
93
|
+
if (config.smart_notes.enabled === false) {
|
|
94
|
+
gatedAlreadyOff++;
|
|
95
|
+
expect(got.oc1.note).toBe(before.oc1.note);
|
|
96
|
+
expect(got.oc2.note).toBe(before.oc2.note);
|
|
97
|
+
} else {
|
|
98
|
+
gatedChanged++;
|
|
99
|
+
expect(got.oc1.note).not.toBe(before.oc1.note);
|
|
100
|
+
expect(got.oc2.note).not.toBe(before.oc2.note);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Keep the differential meaningful: both sides must be well populated.
|
|
104
|
+
expect(runnable).toBeGreaterThanOrEqual(100);
|
|
105
|
+
expect(gatedChanged).toBeGreaterThanOrEqual(40);
|
|
106
|
+
expect(gatedAlreadyOff).toBeGreaterThanOrEqual(20);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the OpenCode 2 per-request rewrite receives the dreamer-aware smart-note flag", () => {
|
|
110
|
+
// The differential above passes this flag itself, so pin the production call.
|
|
111
|
+
const source = readFileSync(join(import.meta.dir, "../v2/hooks/context.ts"), "utf8");
|
|
112
|
+
expect(source).toMatch(
|
|
113
|
+
/applyV2PromptSurfaceTools\(\s*draft,\s*promptSurfaceRuntime,\s*config\.prompt_surface,[^)]*isSmartNoteSurfaceAvailable\(config\),\s*\)/,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a live dreamer toggle never changes the tool bytes registered for this process", async () => {
|
|
118
|
+
const previousHome = process.env.HOME;
|
|
119
|
+
const previousConfigHome = process.env.XDG_CONFIG_HOME;
|
|
120
|
+
try {
|
|
121
|
+
for (const [bootDreamer, liveDreamer] of [
|
|
122
|
+
[{}, { disable: true }],
|
|
123
|
+
[{ disable: true }, { disable: false }],
|
|
124
|
+
] as const) {
|
|
125
|
+
const root = createTestTempDirFromPath(join(dir, "live-"));
|
|
126
|
+
process.env.HOME = root;
|
|
127
|
+
process.env.XDG_CONFIG_HOME = join(root, "config");
|
|
128
|
+
const directory = join(root, "project");
|
|
129
|
+
const configPath = join(root, "config", "cortexkit", "magic-context.jsonc");
|
|
130
|
+
mkdirSync(directory, { recursive: true });
|
|
131
|
+
mkdirSync(join(root, "config", "cortexkit"), { recursive: true });
|
|
132
|
+
writeFileSync(configPath, JSON.stringify({ dreamer: bootDreamer }));
|
|
133
|
+
const boot = loadPluginConfigDetailed(directory, false).config;
|
|
134
|
+
const bootAvailable = isSmartNoteSurfaceAvailable(boot);
|
|
135
|
+
// Registration reads the boot config once, as the hosts do.
|
|
136
|
+
const registered = () =>
|
|
137
|
+
renderToolSurfaces({
|
|
138
|
+
config: boot,
|
|
139
|
+
directory,
|
|
140
|
+
db,
|
|
141
|
+
oc2SmartNotes: isSmartNoteSurfaceAvailable(boot),
|
|
142
|
+
});
|
|
143
|
+
const before = await registered();
|
|
144
|
+
|
|
145
|
+
const reader = pluginConfigReader(directory, boot);
|
|
146
|
+
writeFileSync(configPath, JSON.stringify({ dreamer: liveDreamer }));
|
|
147
|
+
const effective = reader.poll().effective;
|
|
148
|
+
// The live view did flip, so the comparison below is not vacuous...
|
|
149
|
+
expect(isSmartNoteSurfaceAvailable(effective)).toBe(!bootAvailable);
|
|
150
|
+
// ...while the boot config the tools were built from is untouched.
|
|
151
|
+
expect(isSmartNoteSurfaceAvailable(boot)).toBe(bootAvailable);
|
|
152
|
+
const after = await registered();
|
|
153
|
+
expect(after.text).toEqual(before.text);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
if (previousHome === undefined) delete process.env.HOME;
|
|
157
|
+
else process.env.HOME = previousHome;
|
|
158
|
+
if (previousConfigHome === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
159
|
+
else process.env.XDG_CONFIG_HOME = previousConfigHome;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
@@ -12,6 +12,19 @@ export function isDreamerRunnable(config: { dreamer?: { disable?: boolean } | nu
|
|
|
12
12
|
return !!config.dreamer && config.dreamer.disable !== true;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Whether smart notes can work at all: `smart_notes.enabled` is not false AND
|
|
17
|
+
* the dreamer, which evaluates every smart-note condition, is runnable. A smart
|
|
18
|
+
* note written without a runnable dreamer would stay parked forever, so the
|
|
19
|
+
* ctx_note tool definition advertises `surface_condition` only when this holds.
|
|
20
|
+
*/
|
|
21
|
+
export function isSmartNoteSurfaceAvailable(config: {
|
|
22
|
+
dreamer?: { disable?: boolean } | null;
|
|
23
|
+
smart_notes?: { enabled?: boolean } | null;
|
|
24
|
+
}): boolean {
|
|
25
|
+
return config.smart_notes?.enabled !== false && isDreamerRunnable(config);
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
export function isHistorianRunnable(config: { historian?: { disable?: boolean } | null }): boolean {
|
|
16
29
|
return config.historian?.disable !== true;
|
|
17
30
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parsePluginConfig } from ".";
|
|
5
|
+
import {
|
|
6
|
+
configDependencyNotes,
|
|
7
|
+
logConfigDependencyNotesOnce,
|
|
8
|
+
resetConfigDependencyNotesForTest,
|
|
9
|
+
} from "./dependency-notes";
|
|
10
|
+
|
|
11
|
+
afterEach(() => resetConfigDependencyNotesForTest());
|
|
12
|
+
|
|
13
|
+
const notesFor = (raw: Record<string, unknown>) => configDependencyNotes(parsePluginConfig(raw));
|
|
14
|
+
|
|
15
|
+
test("a default or fully enabled config produces no dependency notes", () => {
|
|
16
|
+
expect(notesFor({})).toEqual([]);
|
|
17
|
+
expect(notesFor({ dreamer: {} })).toEqual([]);
|
|
18
|
+
expect(notesFor({ dreamer: {}, memory: { enabled: true } })).toEqual([]);
|
|
19
|
+
// Memory off without a runnable dreamer has no dreamer schedule to lose.
|
|
20
|
+
expect(notesFor({ memory: { enabled: false } })).toEqual([]);
|
|
21
|
+
expect(
|
|
22
|
+
notesFor({
|
|
23
|
+
dreamer: { disable: true },
|
|
24
|
+
smart_notes: { enabled: false },
|
|
25
|
+
memory: { enabled: false },
|
|
26
|
+
}),
|
|
27
|
+
).toHaveLength(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("memory off with a runnable dreamer names the dreamer schedule it stops", () => {
|
|
31
|
+
const notes = notesFor({ dreamer: {}, memory: { enabled: false } });
|
|
32
|
+
expect(notes).toHaveLength(1);
|
|
33
|
+
expect(notes[0]).toBe(
|
|
34
|
+
"memory.enabled is false, so these scheduled dreamer tasks do not run for this project, on schedule or through /ctx-dream: map-memories, verify, verify-broad, curate, compress-cues, classify-memories, retrospective, promote-primers, refresh-primers. maintain-docs, evaluate-smart-notes and review-user-memories still run.",
|
|
35
|
+
);
|
|
36
|
+
// Every task unscheduled: nothing is lost, so nothing is reported.
|
|
37
|
+
const tasks = Object.fromEntries(
|
|
38
|
+
[
|
|
39
|
+
"map-memories",
|
|
40
|
+
"verify",
|
|
41
|
+
"verify-broad",
|
|
42
|
+
"curate",
|
|
43
|
+
"compress-cues",
|
|
44
|
+
"classify-memories",
|
|
45
|
+
"retrospective",
|
|
46
|
+
"maintain-docs",
|
|
47
|
+
"evaluate-smart-notes",
|
|
48
|
+
"review-user-memories",
|
|
49
|
+
"promote-primers",
|
|
50
|
+
"refresh-primers",
|
|
51
|
+
].map((task) => [task, { schedule: "" }]),
|
|
52
|
+
);
|
|
53
|
+
expect(notesFor({ dreamer: { tasks }, memory: { enabled: false } })).toEqual([]);
|
|
54
|
+
// Only tasks that do not need project memory scheduled: nothing is lost either.
|
|
55
|
+
const nonMemory = {
|
|
56
|
+
...tasks,
|
|
57
|
+
"maintain-docs": { schedule: "0 1 * * *" },
|
|
58
|
+
"evaluate-smart-notes": { schedule: "0 3 * * *" },
|
|
59
|
+
"review-user-memories": { schedule: "0 3 * * *" },
|
|
60
|
+
};
|
|
61
|
+
expect(notesFor({ dreamer: { tasks: nonMemory }, memory: { enabled: false } })).toEqual([]);
|
|
62
|
+
// One memory task scheduled: the note names just that one.
|
|
63
|
+
expect(
|
|
64
|
+
notesFor({
|
|
65
|
+
dreamer: { tasks: { ...tasks, curate: { schedule: "0 4 * * 0" } } },
|
|
66
|
+
memory: { enabled: false },
|
|
67
|
+
}),
|
|
68
|
+
).toEqual([
|
|
69
|
+
"memory.enabled is false, so these scheduled dreamer tasks do not run for this project, on schedule or through /ctx-dream: curate. maintain-docs, evaluate-smart-notes and review-user-memories still run.",
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a disabled dreamer reports smart notes and the user-memory review schedule", () => {
|
|
74
|
+
const notes = notesFor({ dreamer: { disable: true } });
|
|
75
|
+
expect(notes).toEqual([
|
|
76
|
+
expect.stringContaining("smart_notes.enabled has no effect") as unknown as string,
|
|
77
|
+
expect.stringContaining(
|
|
78
|
+
"dreamer.tasks.review-user-memories.schedule has no effect",
|
|
79
|
+
) as unknown as string,
|
|
80
|
+
]);
|
|
81
|
+
expect(notesFor({ dreamer: { disable: true }, smart_notes: { enabled: false } })).toHaveLength(
|
|
82
|
+
1,
|
|
83
|
+
);
|
|
84
|
+
expect(
|
|
85
|
+
notesFor({
|
|
86
|
+
dreamer: { disable: true, tasks: { "review-user-memories": { schedule: "" } } },
|
|
87
|
+
smart_notes: { enabled: false },
|
|
88
|
+
}),
|
|
89
|
+
).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("each note is logged once per process", () => {
|
|
93
|
+
const lines: string[] = [];
|
|
94
|
+
const config = parsePluginConfig({ dreamer: { disable: true } });
|
|
95
|
+
expect(logConfigDependencyNotesOnce(config, (line) => lines.push(line))).toHaveLength(2);
|
|
96
|
+
expect(logConfigDependencyNotesOnce(config, (line) => lines.push(line))).toEqual([]);
|
|
97
|
+
expect(lines).toHaveLength(2);
|
|
98
|
+
expect(lines.every((line) => line.startsWith("[magic-context] config note: "))).toBe(true);
|
|
99
|
+
// A second plugin instance with a different config still reports its own notes.
|
|
100
|
+
const memoryOff = parsePluginConfig({ dreamer: {}, memory: { enabled: false } });
|
|
101
|
+
expect(logConfigDependencyNotesOnce(memoryOff, (line) => lines.push(line))).toHaveLength(1);
|
|
102
|
+
expect(lines).toHaveLength(3);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("every host logs the dependency notes at startup", () => {
|
|
106
|
+
const src = join(import.meta.dir, "..");
|
|
107
|
+
for (const [file, call] of [
|
|
108
|
+
["index.ts", "logConfigDependencyNotesOnce(pluginConfig, log);"],
|
|
109
|
+
["v2/hooks/context.ts", "logConfigDependencyNotesOnce(config, log);"],
|
|
110
|
+
["../../pi-plugin/src/index.ts", "logConfigDependencyNotesOnce(config, log);"],
|
|
111
|
+
] as const) {
|
|
112
|
+
expect(readFileSync(join(src, file), "utf8")).toContain(call);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PROJECT_MEMORY_DREAM_TASKS } from "../features/magic-context/dreamer/task-registry";
|
|
2
|
+
import { isDreamerRunnable } from "./agent-disable";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Settings whose effect depends on a different section of the config. These
|
|
6
|
+
* lines name a setting that is on but cannot act because another setting turns
|
|
7
|
+
* off what it relies on, so a user reading the log can tell why nothing happens.
|
|
8
|
+
*
|
|
9
|
+
* Only settings the user can see as "on" are reported: an explicit
|
|
10
|
+
* `memory.enabled: false` or `dreamer.disable: true` is the cause in every line,
|
|
11
|
+
* so a configuration that sets neither produces none.
|
|
12
|
+
*/
|
|
13
|
+
export interface DependencyNoteConfig {
|
|
14
|
+
memory?: { enabled?: boolean } | null;
|
|
15
|
+
smart_notes?: { enabled?: boolean } | null;
|
|
16
|
+
dreamer?: {
|
|
17
|
+
disable?: boolean;
|
|
18
|
+
tasks?: Record<string, { schedule?: string } | undefined>;
|
|
19
|
+
} | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scheduledTasks(config: DependencyNoteConfig): string[] {
|
|
23
|
+
const tasks = config.dreamer?.tasks ?? {};
|
|
24
|
+
return Object.entries(tasks)
|
|
25
|
+
.filter(([, task]) => typeof task?.schedule === "string" && task.schedule.trim() !== "")
|
|
26
|
+
.map(([name]) => name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The dependency notes for one resolved config, in a stable order. */
|
|
30
|
+
export function configDependencyNotes(config: DependencyNoteConfig): string[] {
|
|
31
|
+
const notes: string[] = [];
|
|
32
|
+
const dreamerRunnable = isDreamerRunnable(config);
|
|
33
|
+
const dreamerDisabled = config.dreamer?.disable === true;
|
|
34
|
+
const memoryOff = config.memory?.enabled === false;
|
|
35
|
+
|
|
36
|
+
const memoryTasks = scheduledTasks(config).filter((task) =>
|
|
37
|
+
(PROJECT_MEMORY_DREAM_TASKS as readonly string[]).includes(task),
|
|
38
|
+
);
|
|
39
|
+
if (dreamerRunnable && memoryOff && memoryTasks.length > 0) {
|
|
40
|
+
notes.push(
|
|
41
|
+
`memory.enabled is false, so these scheduled dreamer tasks do not run for this project, on schedule or through /ctx-dream: ${memoryTasks.join(", ")}. maintain-docs, evaluate-smart-notes and review-user-memories still run.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (dreamerDisabled && config.smart_notes?.enabled !== false) {
|
|
45
|
+
notes.push(
|
|
46
|
+
"dreamer.disable is true, so smart_notes.enabled has no effect: ctx_note does not offer surface_condition and pending smart notes are not evaluated.",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const reviewSchedule = config.dreamer?.tasks?.["review-user-memories"]?.schedule;
|
|
50
|
+
if (dreamerDisabled && typeof reviewSchedule === "string" && reviewSchedule.trim() !== "") {
|
|
51
|
+
notes.push(
|
|
52
|
+
"dreamer.disable is true, so dreamer.tasks.review-user-memories.schedule has no effect: the historian does not collect user observations and the <user-profile> block gets no new entries.",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return notes;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const loggedNotes = new Set<string>();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Log each dependency note once per process. OpenCode Desktop starts one
|
|
62
|
+
* plugin instance per open project in the same process, so a note already
|
|
63
|
+
* written by another instance is skipped rather than repeated.
|
|
64
|
+
*/
|
|
65
|
+
export function logConfigDependencyNotesOnce(
|
|
66
|
+
config: DependencyNoteConfig,
|
|
67
|
+
log: (message: string) => void,
|
|
68
|
+
): string[] {
|
|
69
|
+
const fresh = configDependencyNotes(config).filter((note) => !loggedNotes.has(note));
|
|
70
|
+
for (const note of fresh) {
|
|
71
|
+
loggedNotes.add(note);
|
|
72
|
+
log(`[magic-context] config note: ${note}`);
|
|
73
|
+
}
|
|
74
|
+
return fresh;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Test-only: forget which notes this process has logged. */
|
|
78
|
+
export function resetConfigDependencyNotesForTest(): void {
|
|
79
|
+
loggedNotes.clear();
|
|
80
|
+
}
|
|
@@ -613,6 +613,8 @@ export const DreamTasksSchema = z
|
|
|
613
613
|
),
|
|
614
614
|
"review-user-memories": ReviewUserMemoriesTaskConfigSchema.default(() =>
|
|
615
615
|
ReviewUserMemoriesTaskConfigSchema.parse(defaultTaskConfig("review-user-memories")),
|
|
616
|
+
).describe(
|
|
617
|
+
"Promote recurring user observations into the <user-profile> block. A non-empty schedule also makes the historian collect user observations, but only while the dreamer is runnable (dreamer.disable not true); collection and review follow that one gate and do not depend on memory.enabled.",
|
|
616
618
|
),
|
|
617
619
|
"promote-primers": PromotePrimersTaskConfigSchema.default(() =>
|
|
618
620
|
PromotePrimersTaskConfigSchema.parse(defaultTaskConfig("promote-primers")),
|
|
@@ -644,7 +646,12 @@ export const DreamerConfigSchema = AgentMetadataSchema.extend({
|
|
|
644
646
|
// Its own node rather than the one shared with the historian through
|
|
645
647
|
// AgentMetadataSchema: `dreamer.disable` is a live-reload key and the
|
|
646
648
|
// historian's is not, and the live marker is set on the schema node.
|
|
647
|
-
disable: z
|
|
649
|
+
disable: z
|
|
650
|
+
.boolean()
|
|
651
|
+
.optional()
|
|
652
|
+
.describe(
|
|
653
|
+
"Disable the dreamer: no scheduled or manual dream task runs. Also turns off smart notes (ctx_note drops surface_condition even with smart_notes.enabled true) and the historian's collection of user observations for review-user-memories. dreamer.inject_docs still applies.",
|
|
654
|
+
),
|
|
648
655
|
runner: z
|
|
649
656
|
.enum(["broca", "host"])
|
|
650
657
|
.optional()
|
|
@@ -659,7 +666,7 @@ export const DreamerConfigSchema = AgentMetadataSchema.extend({
|
|
|
659
666
|
.boolean()
|
|
660
667
|
.default(true)
|
|
661
668
|
.describe(
|
|
662
|
-
"Inject ARCHITECTURE.md and STRUCTURE.md into the m[0] `<project-docs>` block (default true)",
|
|
669
|
+
"Inject ARCHITECTURE.md and STRUCTURE.md into the m[0] `<project-docs>` block (default true). Not a dreamer setting despite its place under dreamer: it applies on every host, also with dreamer.disable true.",
|
|
663
670
|
),
|
|
664
671
|
});
|
|
665
672
|
export type DreamerConfig = z.infer<typeof DreamerConfigSchema>;
|
|
@@ -1181,7 +1188,7 @@ export const MagicContextConfigSchema = z
|
|
|
1181
1188
|
"Historian metadata plus independent strict OpenCode, Pi, and OMP execution blocks. Retained metadata stays at historian; model, fallback_models, variant, and thinking_level belong only in historian.opencode, historian.pi, or historian.omp.",
|
|
1182
1189
|
),
|
|
1183
1190
|
dreamer: DreamerConfigSchema.optional().describe(
|
|
1184
|
-
"Dreamer metadata and scheduling plus independent strict OpenCode, Pi, and OMP execution blocks. schedule and promotion_threshold stay at dreamer.tasks; model, fallback_models, variant, thinking_level, and timeout_minutes belong only in the matching harness block.",
|
|
1191
|
+
"Dreamer metadata and scheduling plus independent strict OpenCode, Pi, and OMP execution blocks. schedule and promotion_threshold stay at dreamer.tasks; model, fallback_models, variant, thinking_level, and timeout_minutes belong only in the matching harness block. Each task follows the setting it serves: memory tasks need memory.enabled, evaluate-smart-notes needs smart_notes.enabled, review-user-memories needs its schedule, maintain-docs only a runnable dreamer.",
|
|
1185
1192
|
),
|
|
1186
1193
|
smart_notes: z
|
|
1187
1194
|
.object({
|
|
@@ -1189,7 +1196,7 @@ export const MagicContextConfigSchema = z
|
|
|
1189
1196
|
.boolean()
|
|
1190
1197
|
.default(true)
|
|
1191
1198
|
.describe(
|
|
1192
|
-
"Enable smart-note authoring and condition evaluation. User-level only: tool definitions are process-wide. Changes require a host restart and rebuild each session's cached prompt once. Pending notes remain stored and unchecked; already-ready notes still surface.",
|
|
1199
|
+
"Enable smart-note authoring and condition evaluation. Needs a runnable dreamer, which evaluates the conditions: with dreamer.disable true (or no dreamer block) ctx_note offers no surface_condition whatever this says. User-level only: tool definitions are process-wide. Changes require a host restart and rebuild each session's cached prompt once. Pending notes remain stored and unchecked; already-ready notes still surface.",
|
|
1193
1200
|
),
|
|
1194
1201
|
retina_handoff: z
|
|
1195
1202
|
.boolean()
|
|
@@ -1303,7 +1310,7 @@ export const MagicContextConfigSchema = z
|
|
|
1303
1310
|
.min(60_000)
|
|
1304
1311
|
.default(DEFAULT_HISTORIAN_TIMEOUT_MS)
|
|
1305
1312
|
.describe(
|
|
1306
|
-
"Timeout for each historian prompt call in both TypeScript and Rust transform modes, in milliseconds (default: 600000)",
|
|
1313
|
+
"Timeout for each historian prompt call in both TypeScript and Rust transform modes, in milliseconds (default: 600000). A historian setting kept at the top level for compatibility; no effect while historian.disable is true or compaction.enabled is false.",
|
|
1307
1314
|
),
|
|
1308
1315
|
commit_cluster_trigger: z
|
|
1309
1316
|
.object({
|
|
@@ -1321,7 +1328,7 @@ export const MagicContextConfigSchema = z
|
|
|
1321
1328
|
})
|
|
1322
1329
|
.default({ enabled: true, min_clusters: 3 })
|
|
1323
1330
|
.describe(
|
|
1324
|
-
"Commit-cluster trigger: fire historian when enough commit clusters accumulate in the unsummarized tail",
|
|
1331
|
+
"Commit-cluster trigger: fire historian when enough commit clusters accumulate in the unsummarized tail. A historian setting kept at the top level for compatibility; no effect while historian.disable is true or compaction.enabled is false.",
|
|
1325
1332
|
),
|
|
1326
1333
|
system_prompt_injection: z
|
|
1327
1334
|
.object({
|
|
@@ -1510,7 +1517,7 @@ export const MagicContextConfigSchema = z
|
|
|
1510
1517
|
.boolean()
|
|
1511
1518
|
.default(true)
|
|
1512
1519
|
.describe(
|
|
1513
|
-
"Enable cross-session memory (default: true). Does not affect history embedding or semantic ctx_search over session history; set embedding.provider to 'off' to stop all embedding.",
|
|
1520
|
+
"Enable cross-session project memory (default: true). Also gates the dreamer's project-memory tasks: while false, map-memories, verify, verify-broad, curate, compress-cues, classify-memories, retrospective, promote-primers and refresh-primers neither run on schedule nor through /ctx-dream; maintain-docs, evaluate-smart-notes and review-user-memories still run. memory.auto_search and memory.git_commit_indexing do not depend on it. Does not affect history embedding or semantic ctx_search over session history; set embedding.provider to 'off' to stop all embedding.",
|
|
1514
1521
|
),
|
|
1515
1522
|
injection_budget_tokens: z
|
|
1516
1523
|
.number()
|
|
@@ -1524,14 +1531,14 @@ export const MagicContextConfigSchema = z
|
|
|
1524
1531
|
.boolean()
|
|
1525
1532
|
.default(true)
|
|
1526
1533
|
.describe(
|
|
1527
|
-
"Automatically promote eligible session facts into memory (default: true)",
|
|
1534
|
+
"Automatically promote eligible session facts into memory (default: true). No effect while memory.enabled is false.",
|
|
1528
1535
|
),
|
|
1529
1536
|
retrieval_count_promotion_threshold: z
|
|
1530
1537
|
.number()
|
|
1531
1538
|
.min(1)
|
|
1532
1539
|
.default(3)
|
|
1533
1540
|
.describe(
|
|
1534
|
-
"
|
|
1541
|
+
"Currently has no effect: no code reads this value, and memories are not promoted to permanent by retrieval count. Kept so existing configs stay valid (min: 1, default: 3).",
|
|
1535
1542
|
),
|
|
1536
1543
|
auto_search: z
|
|
1537
1544
|
.object({
|
|
@@ -1539,7 +1546,7 @@ export const MagicContextConfigSchema = z
|
|
|
1539
1546
|
.boolean()
|
|
1540
1547
|
.default(true)
|
|
1541
1548
|
.describe(
|
|
1542
|
-
"Automatically append a compact <ctx-search-hint> to eligible user messages when relevant memories, conversation, or commits are found. Graduated from experimental.auto_search; on by default (set false to opt out). Independent of memory.enabled.",
|
|
1549
|
+
"Automatically append a compact <ctx-search-hint> to eligible user messages when relevant memories, conversation, or commits are found. Graduated from experimental.auto_search; on by default (set false to opt out). Independent of memory.enabled despite its place under memory: with memory off it still searches past conversation and commits, only the memory source is skipped.",
|
|
1543
1550
|
),
|
|
1544
1551
|
score_threshold: z
|
|
1545
1552
|
.number()
|
|
@@ -1560,7 +1567,7 @@ export const MagicContextConfigSchema = z
|
|
|
1560
1567
|
})
|
|
1561
1568
|
.default({ enabled: true, score_threshold: 0.6, min_prompt_chars: 20 })
|
|
1562
1569
|
.describe(
|
|
1563
|
-
"Auto-search hint: transform-time ctx_search on each new user message; when the top hit clears the threshold, append a compact <ctx-search-hint> block of vague fragments to that user message. Does NOT inject full content. Graduated from experimental.auto_search; enabled by default (set enabled: false to opt out). Independent of memory.enabled.",
|
|
1570
|
+
"Auto-search hint: transform-time ctx_search on each new user message; when the top hit clears the threshold, append a compact <ctx-search-hint> block of vague fragments to that user message. Does NOT inject full content. Graduated from experimental.auto_search; enabled by default (set enabled: false to opt out). Independent of memory.enabled despite its place under memory: with memory off it still searches past conversation and commits.",
|
|
1564
1571
|
),
|
|
1565
1572
|
git_commit_indexing: z
|
|
1566
1573
|
.object({
|
|
@@ -1568,7 +1575,7 @@ export const MagicContextConfigSchema = z
|
|
|
1568
1575
|
.boolean()
|
|
1569
1576
|
.default(false)
|
|
1570
1577
|
.describe(
|
|
1571
|
-
"Index HEAD git commits for ctx_search (git_commit source). Graduated from experimental.git_commit_indexing; opt-in, default off. Independent of memory.enabled.",
|
|
1578
|
+
"Index HEAD git commits for ctx_search (git_commit source). Graduated from experimental.git_commit_indexing; opt-in, default off. Independent of memory.enabled despite its place under memory.",
|
|
1572
1579
|
),
|
|
1573
1580
|
since_days: z
|
|
1574
1581
|
.number()
|
|
@@ -1589,7 +1596,7 @@ export const MagicContextConfigSchema = z
|
|
|
1589
1596
|
})
|
|
1590
1597
|
.default({ enabled: false, since_days: 365, max_commits: 2000 })
|
|
1591
1598
|
.describe(
|
|
1592
|
-
"Index git commit messages from HEAD into ctx_search. Commits become a 4th searchable source alongside memories and session history. Graduated from experimental.git_commit_indexing; opt-in, default off (per-project embedding cost). Independent of memory.enabled.",
|
|
1599
|
+
"Index git commit messages from HEAD into ctx_search. Commits become a 4th searchable source alongside memories and session history. Graduated from experimental.git_commit_indexing; opt-in, default off (per-project embedding cost). Independent of memory.enabled despite its place under memory.",
|
|
1593
1600
|
),
|
|
1594
1601
|
})
|
|
1595
1602
|
.default({
|
|
@@ -1600,7 +1607,9 @@ export const MagicContextConfigSchema = z
|
|
|
1600
1607
|
auto_search: { enabled: true, score_threshold: 0.6, min_prompt_chars: 20 },
|
|
1601
1608
|
git_commit_indexing: { enabled: false, since_days: 365, max_commits: 2000 },
|
|
1602
1609
|
})
|
|
1603
|
-
.describe(
|
|
1610
|
+
.describe(
|
|
1611
|
+
"Cross-session memory configuration. auto_search and git_commit_indexing sit here for history but work with memory.enabled false.",
|
|
1612
|
+
),
|
|
1604
1613
|
})
|
|
1605
1614
|
.transform((data): MagicContextConfig => {
|
|
1606
1615
|
return {
|
|
@@ -278,6 +278,46 @@ export const MEMORY_DOMAIN_TASKS: readonly DreamTaskName[] = [
|
|
|
278
278
|
|
|
279
279
|
const MEMORY_DOMAIN_SET = new Set<DreamTaskName>(MEMORY_DOMAIN_TASKS);
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* The dreamer tasks that maintain project memory or knowledge derived from it
|
|
283
|
+
* (primers grow out of the same sessions and live beside the memories). They
|
|
284
|
+
* run only while `memory.enabled` is true. Every other task serves a different
|
|
285
|
+
* setting and runs with memory off:
|
|
286
|
+
* - evaluate-smart-notes follows `smart_notes.enabled`;
|
|
287
|
+
* - review-user-memories follows user-observation collection, which is on
|
|
288
|
+
* exactly when the review is scheduled and the dreamer is runnable, so
|
|
289
|
+
* observations are collected exactly when they will be reviewed;
|
|
290
|
+
* - maintain-docs needs only a runnable dreamer.
|
|
291
|
+
* A runnable dreamer is the precondition for any scheduler call at all.
|
|
292
|
+
*/
|
|
293
|
+
export const PROJECT_MEMORY_DREAM_TASKS: readonly DreamTaskName[] = [
|
|
294
|
+
"map-memories",
|
|
295
|
+
"verify",
|
|
296
|
+
"verify-broad",
|
|
297
|
+
"curate",
|
|
298
|
+
"compress-cues",
|
|
299
|
+
"classify-memories",
|
|
300
|
+
"retrospective",
|
|
301
|
+
"promote-primers",
|
|
302
|
+
"refresh-primers",
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
const PROJECT_MEMORY_DREAM_TASK_SET = new Set<DreamTaskName>(PROJECT_MEMORY_DREAM_TASKS);
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Whether the per-project settings let this task run. `memory.enabled` gates
|
|
309
|
+
* only the project-memory tasks and `smart_notes.enabled` only
|
|
310
|
+
* evaluate-smart-notes; an omitted setting counts as on.
|
|
311
|
+
*/
|
|
312
|
+
export function dreamTaskAllowedBySettings(
|
|
313
|
+
task: DreamTaskName,
|
|
314
|
+
settings: { projectMemoryEnabled?: boolean; smartNotesEnabled?: boolean },
|
|
315
|
+
): boolean {
|
|
316
|
+
if (PROJECT_MEMORY_DREAM_TASK_SET.has(task)) return settings.projectMemoryEnabled !== false;
|
|
317
|
+
if (task === "evaluate-smart-notes") return settings.smartNotesEnabled !== false;
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
281
321
|
/**
|
|
282
322
|
* Lease KIND per task. `memory` + the three independent kinds are per-project;
|
|
283
323
|
* `user-memories` is GLOBAL (mutates the cross-project user-profile pool, so two
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getOpenCodeStorageDir,
|
|
15
15
|
getProjectMagicContextDir,
|
|
16
16
|
getProjectMagicContextHistorianDir,
|
|
17
|
+
isMagicContextStorageOutsideEngram,
|
|
17
18
|
} from "./data-path";
|
|
18
19
|
import { createTestTempDirFromPath } from "./test-temp-dir";
|
|
19
20
|
|
|
@@ -108,12 +109,45 @@ describe("data-path", () => {
|
|
|
108
109
|
expect(getMagicContextStorageDir()).toBe(
|
|
109
110
|
path.join(os.homedir(), ".local", "share", "cortexkit", "magic-context"),
|
|
110
111
|
);
|
|
112
|
+
expect(isMagicContextStorageOutsideEngram()).toBe(false);
|
|
111
113
|
} finally {
|
|
112
114
|
if (savedTestDir !== undefined) process.env.MAGIC_CONTEXT_TEST_DATA_DIR = savedTestDir;
|
|
113
115
|
if (savedNodeEnv !== undefined) process.env.NODE_ENV = savedNodeEnv;
|
|
114
116
|
}
|
|
115
117
|
});
|
|
116
118
|
|
|
119
|
+
test("an external storage override is outside the Engram backup folder", () => {
|
|
120
|
+
const root = createTestTempDirFromPath(path.join(os.tmpdir(), "mc-storage-backup-"));
|
|
121
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = path.join(root, "external-store");
|
|
122
|
+
|
|
123
|
+
expect(isMagicContextStorageOutsideEngram()).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("storage below the Engram backup folder is considered inside it", () => {
|
|
127
|
+
const root = createTestTempDirFromPath(path.join(os.tmpdir(), "mc-storage-backup-"));
|
|
128
|
+
const backupDir = path.join(root, "default", "magic-context");
|
|
129
|
+
|
|
130
|
+
expect(isMagicContextStorageOutsideEngram(path.join(backupDir, "nested"), backupDir)).toBe(
|
|
131
|
+
false,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("a storage symlink into the default folder is considered inside it", () => {
|
|
136
|
+
const root = createTestTempDirFromPath(path.join(os.tmpdir(), "mc-storage-backup-"));
|
|
137
|
+
const backupDir = path.join(root, "default", "magic-context");
|
|
138
|
+
const storageLink = path.join(root, "storage-link");
|
|
139
|
+
mkdirSync(backupDir, { recursive: true });
|
|
140
|
+
symlinkSync(backupDir, storageLink, "dir");
|
|
141
|
+
|
|
142
|
+
expect(isMagicContextStorageOutsideEngram(storageLink, backupDir)).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("an unset storage override keeps the platform default inside the backup folder", () => {
|
|
146
|
+
expect(process.env.MAGIC_CONTEXT_STORAGE_DIR).toBeUndefined();
|
|
147
|
+
expect(process.env.XDG_DATA_HOME).toBeUndefined();
|
|
148
|
+
expect(isMagicContextStorageOutsideEngram()).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
117
151
|
test("getMagicContextStorageDir backstops to a temp dir under NODE_ENV=test with no guard set", () => {
|
|
118
152
|
// CWD-independent backstop: a `bun test` from a dir whose bunfig has no
|
|
119
153
|
// `[test] preload` runs every suite with neither guard env var set. The
|