Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ Magic Context also writes to a few other locations:
|---|---|---|
| `~/.local/share/cortexkit/magic-context/context.db` | SQLite database — tags, compartments, memories, all durable state (XDG-equivalent on Windows) | **Must persist.** Losing it loses your memory/history. |
| `~/.local/share/cortexkit/magic-context/models/` | Local embedding model cache (~90 MB `Xenova/all-MiniLM-L6-v2` ONNX), downloaded on first use when local embeddings are enabled | Should persist, else re-downloaded each run. Not used when `memory.enabled: false` or an `openai_compatible`/`ollama` embedding backend is configured. |
| `${TMPDIR}/opencode/magic-context/magic-context.log` (`pi/` for Pi) | Diagnostic log | Disposable. |
| `MAGIC_CONTEXT_LOG_PATH` (fallback: `${TMPDIR}/opencode/magic-context/magic-context.log`, `pi/` for Pi) | Diagnostic log | Disposable. |

**Sandboxed / ephemeral environments (Docker, CI, disposable containers):** mount the `~/.local/share/cortexkit/magic-context/` directory on a persistent volume so the database and model cache survive between runs. If only the model cache is ephemeral, the model is simply re-downloaded; if the database is ephemeral, memory and history don't accumulate. To avoid the ~90 MB model download entirely, set `memory.enabled: false` or point `embedding` at a remote `openai_compatible`/`ollama` backend.

Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ packages/dashboard/
The dashboard reads from the same SQLite database the plugin writes to:
- **Database**: `~/.local/share/cortexkit/magic-context/context.db`
- **Config**: `~/.config/cortexkit/magic-context.jsonc` (user) · `<project>/.cortexkit/magic-context.jsonc` (project)
- **Logs**: `${TMPDIR}/opencode/magic-context/magic-context.log` (`pi/` for Pi)
- **Logs**: `MAGIC_CONTEXT_LOG_PATH` (fallback: `${TMPDIR}/opencode/magic-context/magic-context.log`, `pi/` for Pi)

Database access uses WAL mode for safe concurrent reads while the plugin writes. Write operations (memory edits, dream queue entries) use `busy_timeout` to handle contention.

Expand Down
81 changes: 81 additions & 0 deletions packages/dashboard/src-tauri/src/log_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ impl Harness {
/// in sync manually because the dashboard doesn't import any TypeScript
/// source.
pub fn resolve_log_path_for(harness: Harness) -> PathBuf {
if let Some(env_path) = std::env::var("MAGIC_CONTEXT_LOG_PATH")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
{
return PathBuf::from(env_path);
}

std::env::temp_dir()
.join(harness.as_str())
.join("magic-context")
Expand Down Expand Up @@ -436,3 +444,76 @@ pub fn read_log_tail(path: &PathBuf, max_lines: usize) -> Vec<LogEntry> {
.filter_map(|line| parse_log_line(line))
.collect()
}

#[cfg(test)]
mod tests {
use super::{resolve_log_path_for, Harness};
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};

static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();

fn env_lock() -> std::sync::MutexGuard<'static, ()> {
ENV_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}

#[test]
fn resolve_log_path_for_uses_harness_fallback_when_env_unset() {
let _guard = env_lock();
std::env::remove_var("MAGIC_CONTEXT_LOG_PATH");

assert_eq!(
resolve_log_path_for(Harness::Opencode),
std::env::temp_dir()
.join("opencode")
.join("magic-context")
.join("magic-context.log")
);
assert_eq!(
resolve_log_path_for(Harness::Pi),
std::env::temp_dir()
.join("pi")
.join("magic-context")
.join("magic-context.log")
);
}

#[test]
fn resolve_log_path_for_honors_magic_context_log_path_override() {
let _guard = env_lock();
let custom = std::env::temp_dir()
.join("custom")
.join("magic-context.log");
std::env::set_var(
"MAGIC_CONTEXT_LOG_PATH",
custom.to_string_lossy().to_string(),
);

assert_eq!(
resolve_log_path_for(Harness::Opencode),
PathBuf::from(&custom)
);
assert_eq!(resolve_log_path_for(Harness::Pi), PathBuf::from(&custom));

std::env::remove_var("MAGIC_CONTEXT_LOG_PATH");
}

#[test]
fn resolve_log_path_for_ignores_blank_magic_context_log_path() {
let _guard = env_lock();
std::env::set_var("MAGIC_CONTEXT_LOG_PATH", " ");

assert_eq!(
resolve_log_path_for(Harness::Pi),
std::env::temp_dir()
.join("pi")
.join("magic-context")
.join("magic-context.log")
);

std::env::remove_var("MAGIC_CONTEXT_LOG_PATH");
}
}
2 changes: 1 addition & 1 deletion packages/docs/src/content/docs/reference/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,4 @@ Use [Configuration](/reference/configuration/) for the full generated key refere

## Logs

Optional **log tail** for `magic-context.log` with filtering, useful alongside Cache when correlating busts with plugin log lines.
Optional **log tail** for `MAGIC_CONTEXT_LOG_PATH` (fallback: `${TMPDIR}/opencode/magic-context/magic-context.log`, `pi/` for Pi) with filteringuseful alongside Cache when correlating busts with plugin log lines. Open from the sidebar **Logs** item.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export interface DreamTaskExecutorDeps {
) => Promise<RawMessageProvider | null> | RawMessageProvider | null;
language?: string;
}

/** A failed task either hot-retries (transient: provider/network/rate-limit/
* timeout/abort/lease/busy) or advances to the next cron slot (permanent:
* model-not-found, validation, parse). Classify off the error shape. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Database } from "../../shared/sqlite";
import type { Database } from "../../shared/sqlite";

/**
* Schema-mutation helpers shared by storage-db (fresh-DB init) and migrations
Expand Down
25 changes: 25 additions & 0 deletions packages/plugin/src/shared/data-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getCacheDir,
getDataDir,
getLegacyOpenCodeMagicContextStorageDir,
getMagicContextLogPath,
getMagicContextStorageDir,
getOpenCodeCacheDir,
getOpenCodeStorageDir,
Expand All @@ -18,17 +19,20 @@ const savedEnv = {
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME,
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
LOCALAPPDATA: process.env.LOCALAPPDATA,
MAGIC_CONTEXT_LOG_PATH: process.env.MAGIC_CONTEXT_LOG_PATH,
};

describe("data-path", () => {
beforeEach(() => {
process.env.XDG_CACHE_HOME = undefined;
process.env.XDG_DATA_HOME = undefined;
process.env.LOCALAPPDATA = undefined;
process.env.MAGIC_CONTEXT_LOG_PATH = undefined;
// Bun's env handling: explicit delete for unset
delete process.env.XDG_CACHE_HOME;
delete process.env.XDG_DATA_HOME;
delete process.env.LOCALAPPDATA;
delete process.env.MAGIC_CONTEXT_LOG_PATH;
});

afterEach(() => {
Expand All @@ -37,6 +41,9 @@ describe("data-path", () => {
if (savedEnv.XDG_DATA_HOME !== undefined)
process.env.XDG_DATA_HOME = savedEnv.XDG_DATA_HOME;
if (savedEnv.LOCALAPPDATA !== undefined) process.env.LOCALAPPDATA = savedEnv.LOCALAPPDATA;
if (savedEnv.MAGIC_CONTEXT_LOG_PATH !== undefined)
process.env.MAGIC_CONTEXT_LOG_PATH = savedEnv.MAGIC_CONTEXT_LOG_PATH;
else delete process.env.MAGIC_CONTEXT_LOG_PATH;
});

test("getCacheDir falls back to <homedir>/.cache when XDG_CACHE_HOME is unset (all platforms)", () => {
Expand Down Expand Up @@ -158,6 +165,24 @@ describe("data-path", () => {
path.join("/some/project/", ".cortexkit", "magic-context"),
);
});

test("getMagicContextLogPath falls back to harness temp dir when env unset", () => {
expect(getMagicContextLogPath("opencode")).toBe(
path.join(os.tmpdir(), "opencode", "magic-context", "magic-context.log"),
);
});

test("getMagicContextLogPath honors MAGIC_CONTEXT_LOG_PATH", () => {
process.env.MAGIC_CONTEXT_LOG_PATH = "/tmp/custom/magic-context.log";
expect(getMagicContextLogPath("pi")).toBe("/tmp/custom/magic-context.log");
});

test("getMagicContextLogPath ignores blank MAGIC_CONTEXT_LOG_PATH", () => {
process.env.MAGIC_CONTEXT_LOG_PATH = " ";
expect(getMagicContextLogPath("pi")).toBe(
path.join(os.tmpdir(), "pi", "magic-context", "magic-context.log"),
);
});
});

describe("ensureCortexKitArtifactGitignore", () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin/src/shared/data-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export function getMagicContextTempDir(harness: HarnessId = getHarness()): strin
* reflected in the next flush.
*/
export function getMagicContextLogPath(harness: HarnessId = getHarness()): string {
const envPath = process.env.MAGIC_CONTEXT_LOG_PATH?.trim();
if (envPath) return envPath;
return path.join(getMagicContextTempDir(harness), "magic-context.log");
}

Expand Down
Loading