diff --git a/AGENTS.md b/AGENTS.md index 0fd347d..105fda3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,13 +26,7 @@ ### Gotcha -* **Calibration used DB message count instead of transformed window count — caused layer 0 false passthrough**: Lore gradient calibration bugs that caused context overflow: (1) Used DB message count instead of transformed window count — after compression, delta saw ~1 new msg → layer 0 passthrough → overflow. Fix: getLastTransformedCount(). (2) actualInput omitted cache.write — cold-cache turns showed ~3 tokens instead of 150K → layer 0. Fix: include cache.write. (3) Trailing pure-text assistant messages cause Anthropic prefill errors, but messages with tool parts must NOT be dropped (SDK converts to tool\_result user-role). Drop predicate: \`hasToolParts\`. (4) Don't mutate message parts you don't own — removed stats PATCH that caused system-reminder persistence bug. - - -* **hostapd -t is not a config dry-run — it adds timestamps to debug output**: hostapd v2.10's \`-t\` flag means 'include timestamps in debug messages', NOT syntax check or dry-run. Running \`hostapd -t \\` fully initialises the interface and hangs as a running AP. There is no built-in config validation flag in hostapd. For validation, use grep-based checks for known-bad directives (e.g. checking for ieee80211r when it's not compiled in) rather than invoking hostapd itself. - - -* **Lore plugin only protects projects where it's registered in opencode.json**: The lore gradient transform only runs for projects with lore registered in opencode.json (or globally in ~/.config/opencode/). Projects without it get zero context management — messages accumulate until overflow triggers a stuck compaction loop. This caused a 404K-token overflow in a getsentry/cli session with no opencode.json. +* **Calibration used DB message count instead of transformed window count — caused layer 0 false passthrough**: Lore gradient/context management bugs and fixes: (1) Used DB message count instead of transformed window count — delta ≈ 1 after compression → layer 0 passthrough → overflow. Fix: getLastTransformedCount(). (2) actualInput omitted cache.write — cold-cache showed ~3 tokens → layer 0. Fix: include cache.write. (3) Trailing pure-text assistant messages cause Anthropic prefill errors. Drop loop must run at ALL layers including 0 — at layer 0 result.messages === output.messages (same ref), so pop() trims in place. Messages with tool parts must NOT be dropped (hasToolParts) — dropping causes infinite tool-call loops. (4) Lore only protects projects registered in opencode.json — unregistered projects get zero context management → stuck compaction loops creating orphaned message pairs. Recovery: delete all messages after last good assistant message (has tokens, no error). * **mt7921e 3dBm tx power on desktop — disable CLC firmware table**: mt7921e/mt7922 PCIe WiFi cards in desktop PCs (no ACPI SAR tables like WRDS/EWRD) get stuck at ~3 dBm tx power because the CLC (Country Location Code) firmware power lookup falls back to a conservative default when no SAR table exists. Fix: set \`options mt7921\_common disable\_clc=1\` in /etc/modprobe.d/mt7921.conf. This lets the regulatory domain ceiling apply (e.g. 23 dBm on 5GHz ch44 in GB). Also set explicit tx power via \`iw dev \ set txpower fixed 2000\` in ExecStartPost since the module param only takes effect on next module load/reboot. @@ -40,17 +34,17 @@ * **Pixel phones fail WPA group key rekey during doze — use 86400s interval**: Android Pixel devices in deep doze/sleep fail to respond to WPA group key handshake frames within hostapd's retry window. With wpa\_group\_rekey=3600, the phone gets deauthenticated every hour ('group key handshake failed (RSN) after 4 tries'). Other devices on the same AP complete the rekey fine. Fix: set wpa\_group\_rekey=86400 (24h) instead of 0 (disabled) for security balance. Also apply to Asus router: nvram set wpa\_gtk\_rekey=86400, wl0\_wpa\_gtk\_rekey=86400, wl1\_wpa\_gtk\_rekey=86400. - -* **Stuck compaction loops leave orphaned user+assistant message pairs in DB**: When OpenCode compaction overflows, it creates paired user+assistant messages per retry (assistant has error.name:'ContextOverflowError', mode:'compaction'). These accumulate and worsen the session. Recovery: find last good assistant message (has tokens, no error), delete all messages after it from both \`message\` and \`part\` tables. Use json\_extract(data, '$.error.name') to identify compaction debris. - * **sudo changes $HOME to /root — hardcode user home in scripts run with sudo**: When running a script with \`sudo\`, \`$HOME\` resolves to \`/root\`, not the invoking user's home. SSH key paths like \`$HOME/.ssh/id\_ed25519\` break. Fix: use \`SUDO\_USER\` env var: \`USER\_HOME=$(eval echo ~${SUDO\_USER:-$USER})\` and reference \`$USER\_HOME/.ssh/id\_ed25519\`. This is a common trap in scripts that need both root privileges (systemctl, writing to /etc) and user-specific resources (SSH keys). -* **Test DB isolation via LORE\_DB\_PATH and Bun test preload**: Lore test suite uses an isolated temp DB via test/setup.ts preload (bunfig.toml). The preload sets LORE\_DB\_PATH to a mkdtempSync path before any test file imports src/db.ts, and the afterAll cleans up. src/db.ts checks LORE\_DB\_PATH first — if set, uses that exact path instead of ~/.local/share/opencode-lore/lore.db. agents-file.test.ts still needs beforeEach cleanup for intra-file isolation and TEST\_UUIDS cleanup in afterAll (shared explicit UUIDs with ltm.test.ts). Individual test files no longer need close() calls or cross-run cleanup beforeAll blocks — the preload handles DB lifecycle. +* **Test DB isolation via LORE\_DB\_PATH and Bun test preload**: Lore test suite uses isolated temp DB via test/setup.ts preload (bunfig.toml). Preload sets LORE\_DB\_PATH to mkdtempSync path before any imports of src/db.ts; afterAll cleans up. src/db.ts checks LORE\_DB\_PATH first. agents-file.test.ts needs beforeEach cleanup for intra-file isolation and TEST\_UUIDS cleanup in afterAll (shared with ltm.test.ts). Individual test files don't need close() calls — preload handles DB lifecycle. -* **Ubuntu packaged hostapd lacks 802.11r (CONFIG\_IEEE80211R not compiled)**: Ubuntu 24.04's hostapd package (2:2.10-21ubuntu0.x) is compiled without CONFIG\_IEEE80211R. Using \`ieee80211r=1\`, \`mobility\_domain\`, \`ft\_over\_ds\`, \`r0kh\`, \`r1kh\`, or \`FT-PSK\` in wpa\_key\_mgmt causes 'unknown configuration item' errors and hostapd fails to start. 802.11k (rrm\_neighbor\_report, rrm\_beacon\_report) and 802.11v (bss\_transition) ARE compiled in and work. Verify with \`strings /usr/sbin/hostapd | grep ieee80211r\` — absence confirms no FT support. Building from source with CONFIG\_IEEE80211R=y is the only workaround. +* **Ubuntu packaged hostapd lacks 802.11r (CONFIG\_IEEE80211R not compiled)**: Ubuntu 24.04 hostapd (2:2.10-21ubuntu0.x) lacks CONFIG\_IEEE80211R. Using \`ieee80211r=1\`, \`mobility\_domain\`, \`FT-PSK\` etc. causes 'unknown configuration item' and fails to start. 802.11k/v directives ARE compiled in. Verify: \`strings /usr/sbin/hostapd | grep ieee80211r\` — absence confirms no FT support. Build from source with CONFIG\_IEEE80211R=y. Note: hostapd has NO config dry-run flag — \`-t\` just adds timestamps to debug output and fully starts the AP. Use grep-based validation for known-bad directives instead. + + +* **Zod v4 .default({}) no longer applies inner field defaults**: Zod v4 changed \`.default()\` to short-circuit: when input is \`undefined\`, it returns the default value directly without parsing it through inner schema defaults. So \`.object({ enabled: z.boolean().default(true) }).default({})\` returns \`{}\` (no \`enabled\` key), not \`{ enabled: true }\`. Fix: provide fully-populated default objects — \`.default({ enabled: true })\`. This affected all nested config sections in src/config.ts during the v3→v4 upgrade. The import \`import { z } from "zod"\` is unchanged — Zod 4's main entry point is the v4 API. ### Pattern diff --git a/package.json b/package.json index fefe1ab..cf4ea56 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dependencies": { "remark": "^15.0.1", "uuidv7": "^1.1.0", - "zod": "^3.25.0" + "zod": "^4.3.6" }, "devDependencies": { "@opencode-ai/plugin": "^1.1.39", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 930324e..c6e1c41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^1.1.0 version: 1.1.0 zod: - specifier: ^3.25.0 - version: 3.25.76 + specifier: ^4.3.6 + version: 4.3.6 devDependencies: '@opencode-ai/plugin': specifier: ^1.1.39 @@ -228,12 +228,12 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.1.8: resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -544,8 +544,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - zod@3.25.76: {} - zod@4.1.8: {} + zod@4.3.6: {} + zwitch@2.0.4: {} diff --git a/src/config.ts b/src/config.ts index 110af1e..55808c2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,14 +15,14 @@ export const LoreConfig = z.object({ /** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.10 (10%). */ ltm: z.number().min(0.02).max(0.3).default(0.10), }) - .default({}), + .default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.10 }), distillation: z .object({ minMessages: z.number().min(3).default(8), maxSegment: z.number().min(5).default(50), metaThreshold: z.number().min(3).default(10), }) - .default({}), + .default({ minMessages: 8, maxSegment: 50, metaThreshold: 10 }), knowledge: z .object({ /** Set to false to disable long-term knowledge storage and system-prompt injection. @@ -32,7 +32,7 @@ export const LoreConfig = z.object({ * system prompt. Default: true. */ enabled: z.boolean().default(true), }) - .default({}), + .default({ enabled: true }), curator: z .object({ enabled: z.boolean().default(true), @@ -41,7 +41,7 @@ export const LoreConfig = z.object({ /** Max knowledge entries per project before consolidation triggers. Default: 25. */ maxEntries: z.number().min(10).default(25), }) - .default({}), + .default({ enabled: true, onIdle: true, afterTurns: 10, maxEntries: 25 }), pruning: z .object({ /** Days to keep distilled temporal messages before pruning. Default: 120. */ @@ -49,7 +49,7 @@ export const LoreConfig = z.object({ /** Max total temporal_messages storage in MB before emergency pruning. Default: 1024 (1 GB). */ maxStorage: z.number().min(50).default(1024), }) - .default({}), + .default({ retention: 120, maxStorage: 1024 }), crossProject: z.boolean().default(true), agentsFile: z .object({ @@ -58,7 +58,7 @@ export const LoreConfig = z.object({ /** Path to the agents file, relative to the project root. */ path: z.string().default("AGENTS.md"), }) - .default({}), + .default({ enabled: true, path: "AGENTS.md" }), }); export type LoreConfig = z.infer;