mirror of
https://github.com/mksglu/context-mode.git
synced 2026-09-19 03:27:16 +08:00
feat: add runtime storage override (#617)
* feat: add runtime storage override Co-authored-by: Codex <noreply@openai.com> * test: update storage path source assertions Co-authored-by: Codex <noreply@openai.com> * test: normalize storage path expectations Co-authored-by: Codex <noreply@openai.com> * fix runtime storage override resolution Consolidate storage overrides on CONTEXT_MODE_DIR, share resolver behavior across server, hooks, and statusline, and update docs/tests around the Codex Desktop failure contract. Co-authored-by: Codex <noreply@openai.com> * fix windows storage override tests Make storage-path resolver expectations platform-aware and keep statusline multi-adapter tests on adapter-default discovery instead of the root override path. Co-authored-by: Codex <noreply@openai.com> * address runtime storage review Compose tool registration wrappers, memoize storage writability checks, report storage roots in ctx_doctor, keep legacy statusline session-dir compatibility, and fold resolver tests into existing server coverage. Co-authored-by: Codex <noreply@openai.com> * fix windows storage path test Use an absolute temp-directory fixture instead of a POSIX-rooted default path so resolver tests assert storage behavior consistently across platforms. Co-authored-by: Codex <noreply@openai.com> * fold storage resolver into session db Move CONTEXT_MODE_DIR resolver exports into the existing session DB module so hooks and statusline can use the existing session-db bundle. Drop the new storage-paths source and hook bundle entries while preserving shared storage behavior. Co-authored-by: Codex <noreply@openai.com> * consolidate default storage session roots Add the shared default session-dir helper to the existing session DB bundle boundary, route server/hooks/statusline through it, and document why storage resolution lives there. Expand storage resolver tests for the shared helper. Co-authored-by: Codex <noreply@openai.com> * fix codex default session guard Update the source assertion for the shared default-session-dir helper so the CI guard checks CODEX_HOME via configDirEnvForSessionSegments instead of the removed direct Codex config import. Co-authored-by: Codex <noreply@openai.com> * Fix Claude plugin skills path and pack integrity guard (#661) * ci: update server.bundle.mjs, cli.bundle.mjs, session hook & security bundles * ci: update install stats * ci: update install stats * ci: update install stats * Fix Claude plugin skills manifest path --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(pi): prevent mixed-width TUI over-width crashes (CJK/Korean/emoji) (#676) * fix(pi): CJK wide-character width-aware truncation in PiTextComponent (#665) PiTextComponent/truncateAnsiLine counted every JS character as width 1, but CJK ideographs occupy 2 terminal columns. This produced lines whose actual visibleWidth exceeded the requested width, triggering a pi-tui crash: 'visible width: 162 > terminal width: 147'. Root cause: truncateAnsiLine() iterated chars with visible++ (always 1) instead of accounting for east-asian-width W/F codepoints. Fix: - Add charWidth() helper that returns 2 for CJK/Hangul/fullwidth ranges. - Change truncateAnsiLine to use charWidth and check visible + w > maxWidth (not >=), so a 2-wide char still fits when exactly 2 columns remain. - Export PiTextComponent and truncateAnsiLine for testability. Tests (Slice 10 in pi-mcp-bridge.test.ts): - Pure CJK text respects requested width. - Mixed ASCII + CJK is correctly truncated. - ANSI escape sequences are preserved and not counted toward width. - The real crash line from pi-crash.log fits within terminal width 147. - Edge case: maxWidth 0 or negative returns empty string. Fixes #665 * test(asymmetric-drift): make npm pack dry-run windows-safe --------- Co-authored-by: baifan <ubuntu@BaiFanPC.localdomain> * fix(server): add z.preprocess coercions to ctx_fetch_and_index force and requests params (#679) fix(server): add z.preprocess coercions to ctx_fetch_and_index params The OpenCode/Kilo in-process native plugin bridge stringifies primitive types. Other tools already use z.preprocess(coerceBoolean/coerceJsonArray) to handle this, but ctx_fetch_and_index was missing these wrappers on its force and requests parameters. Added schema-level test verifying preprocess wrappers are present. Updated existing schema tests to match new structure. * feat(fetch): add per-call cache ttl (#666) * feat(fetch): add per-call cache ttl * test: run npm pack dry-run on Windows * fix(batch_execute): preserve heredoc commands (#657) Stops appending `2>&1` to user command strings in `runBatchCommands()` — that mutation broke heredoc terminators (`NODE` → `NODE 2>&1`). Now executes commands as-written and merges executor-captured stdout+stderr via new `combineExecOutput()` helper. Symmetric across serial + parallel paths. Adds serial + parallel regression tests for heredoc commands with stderr. Updates the nodeOptsPrefix edge-case test to reflect the new behavior. Bundles taken from `next` (CI-authoritative); regenerate on next main push. Fixes #656. Co-Authored-By: Noctivoro <nick@movermarketing.ai> * fix(server): surrogate-safe preview truncation in ctx_fetch_and_index (#659) (#660) The fetch-preview path at src/server.ts truncated `f.markdown` with `String.prototype.slice(0, FETCH_PREVIEW_LIMIT)` where the limit (3072) is a UTF-16 code-unit count. When the cut fell between the two halves of an astral-plane character (e.g. 🟡 = U+1F7E1 = 🟡), the high surrogate remained and the low surrogate was dropped. JSON.stringify then emitted the orphan as a literal `\uD83D` escape in the tool_result body, causing RFC 8259-strict consumers (the host LLM API) to reject the next request with `400 ... no low surrogate in string`. Sessions could not recover without removing the bad message from the transcript. Adds a new `charSafePrefix(str, maxChars)` export to `src/truncate.ts` mirroring the existing internal `byteSafePrefix` semantics: cap by UTF-16 code units, back off one unit if the cut would split a surrogate pair. Wires it into the fetch-preview construction. Tests cover the helper directly plus a regression that walks the exact preview-construction pattern with an emoji at the LIMIT boundary and asserts the resulting JSON contains no orphan high surrogate and round-trips through a strict parser. Other `.slice(0, N)` sites in src/ (small label/error/timestamp truncations) are out of scope for this PR — they have low bounds and their inputs are unlikely to contain emoji at the boundary. Happy to extend coverage in a follow-up if the maintainer wants to remove the class of bug entirely. Fixes #659 * fix(auto-memory): scope memory dir by projectDir to stop cross-project leak (#663) (#664) * fix(auto-memory): scope memory dir by projectDir to stop cross-project leak (#663) getMemoryDir() ignored projectDir, so every adapter (except OpenClaw, whose configDir IS the project root) returned a path shared by every project on the machine. Two terminals open in different repos read each other's .md memory files via searchAutoMemory(), then those notes contaminated ctx_search timeline results. Fix: HookAdapter.getMemoryDir now accepts an optional projectDir. When supplied, the path is scoped via hashProjectDirCanonical(projectDir) under the existing base. searchAutoMemory passes projectDir through; the adapterless legacy fallback applies the same hash directly so the contract holds at both call sites. Reuses the same canonicalization as ContentStore and searchEvents, so all three project-scoped surfaces share one identity. Backwards compat: getMemoryDir() without projectDir keeps returning the unscoped path so external adapter consumers don't break. Tests: leak canary (write under projectA scope, search from projectB, assert 0 hits) + positive control + #663 negative test pinning that the old unscoped path is no longer surfaced. * test(windows): fix two flaky Windows-only CI failures - asymmetric-drift-assert: spawnSync("npm", ...) needs shell:true on Windows because npm resolves to npm.cmd. Without it Node returned status=null/stderr=null/stdout=null and the assertion failed with no diagnostic. Also surface r.error in the assertion message. - server.test.ts "python: cap works with python scripts": replace the 10k-iteration Python loop with a single 100KB write. The cap still triggers (and stderr still gets "output capped"), but the test no longer races the 10s timeout on slow Windows CI VMs. --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Baijack-star <71923891+Baijack-star@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: ByF <222546298+ByronFinn@users.noreply.github.com> Co-authored-by: baifan <ubuntu@BaiFanPC.localdomain> Co-authored-by: LeoNardo <58056860+LeoNardo-LB@users.noreply.github.com> Co-authored-by: NgoQuocViet2001 <123613986+NgoQuocViet2001@users.noreply.github.com> Co-authored-by: /noctivoro-x <nick@movermarketing.ai> Co-authored-by: ccheng555 <ccheng5@gmail.com> Co-authored-by: Seba Breguel <62109266+sebastianbreguel@users.noreply.github.com> Co-authored-by: Mert Koseoglu <bm.ksglu@gmail.com>
This commit is contained in:
+22
-9
@@ -26,10 +26,14 @@
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, dirname, resolve } from "node:path";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { homedir } from "node:os";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import {
|
||||
ensureWritableStorageDir,
|
||||
resolveDefaultSessionDir,
|
||||
resolveSessionStorageDir,
|
||||
} from "../hooks/session-db.bundle.mjs";
|
||||
|
||||
// ── Analytics import — resolved relative to this script ─────────────────
|
||||
// statusline.mjs ships in `bin/`; the compiled analytics module lives in
|
||||
@@ -68,10 +72,10 @@ function platform() {
|
||||
|
||||
// Single-shot stderr warning latch — keep noise out of Claude Code's
|
||||
// statusline output even when our parent runs us repeatedly per session.
|
||||
let __winWarned = false;
|
||||
const __warnedKeys = new Set();
|
||||
function warnOnce(key, msg) {
|
||||
if (key === "win" && __winWarned) return;
|
||||
if (key === "win") __winWarned = true;
|
||||
if (__warnedKeys.has(key)) return;
|
||||
__warnedKeys.add(key);
|
||||
try { process.stderr.write(`context-mode statusline: ${msg}\n`); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
@@ -98,10 +102,19 @@ function readStdinJson() {
|
||||
}
|
||||
|
||||
function resolveSessionDir() {
|
||||
if (process.env.CONTEXT_MODE_SESSION_DIR) {
|
||||
return process.env.CONTEXT_MODE_SESSION_DIR;
|
||||
}
|
||||
return join(homedir(), ".claude", "context-mode", "sessions");
|
||||
return ensureWritableStorageDir(
|
||||
resolveSessionStorageDir(() => resolveDefaultSessionDir({
|
||||
configDir: ".claude",
|
||||
configDirEnv: "CLAUDE_CONFIG_DIR",
|
||||
legacySessionDirEnv: "CONTEXT_MODE_SESSION_DIR",
|
||||
onLegacySessionDir: () => {
|
||||
warnOnce(
|
||||
"legacy-session-dir",
|
||||
"CONTEXT_MODE_SESSION_DIR is deprecated; set CONTEXT_MODE_DIR to the parent context-mode root.",
|
||||
);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user