context-mode 1.0.146 → 1.0.148

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 (38) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  5. package/.openclaw-plugin/package.json +1 -1
  6. package/README.md +26 -23
  7. package/bin/statusline.mjs +22 -9
  8. package/build/adapters/base.d.ts +9 -4
  9. package/build/adapters/base.js +16 -7
  10. package/build/adapters/codex/index.d.ts +8 -1
  11. package/build/adapters/codex/index.js +43 -6
  12. package/build/adapters/openclaw/index.d.ts +11 -2
  13. package/build/adapters/openclaw/index.js +12 -3
  14. package/build/adapters/pi/mcp-bridge.d.ts +8 -0
  15. package/build/adapters/pi/mcp-bridge.js +118 -15
  16. package/build/adapters/types.d.ts +11 -2
  17. package/build/cli.d.ts +2 -0
  18. package/build/cli.js +87 -20
  19. package/build/search/auto-memory.d.ts +6 -1
  20. package/build/search/auto-memory.js +11 -2
  21. package/build/server.js +346 -106
  22. package/build/session/analytics.d.ts +19 -0
  23. package/build/session/analytics.js +71 -21
  24. package/build/session/db.d.ts +81 -0
  25. package/build/session/db.js +282 -20
  26. package/build/session/extract.js +16 -0
  27. package/build/truncate.d.ts +15 -0
  28. package/build/truncate.js +28 -0
  29. package/cli.bundle.mjs +435 -350
  30. package/hooks/core/routing.mjs +4 -4
  31. package/hooks/routing-block.mjs +18 -23
  32. package/hooks/session-db.bundle.mjs +21 -19
  33. package/hooks/session-extract.bundle.mjs +2 -2
  34. package/hooks/session-helpers.mjs +13 -2
  35. package/hooks/session-snapshot.bundle.mjs +7 -7
  36. package/openclaw.plugin.json +1 -1
  37. package/package.json +4 -2
  38. package/server.bundle.mjs +383 -300
@@ -196,8 +196,14 @@ export interface HookAdapter {
196
196
  * Directory where persistent per-user memory is stored
197
197
  * (e.g., ~/.claude/memory, ~/.codex/memories). Auto-memory scans
198
198
  * *.md files in this directory.
199
+ *
200
+ * When `projectDir` is supplied, the path MUST be project-scoped (issue
201
+ * #663) so two projects running in parallel cannot read each other's
202
+ * memory. Adapters scope via `hashProjectDirCanonical(projectDir)`.
203
+ * Callers that pre-date this contract may omit `projectDir`; in that
204
+ * case the unscoped legacy path is returned.
199
205
  */
200
- getMemoryDir(): string;
206
+ getMemoryDir(projectDir?: string): string;
201
207
  /** Generate hook registration config for this platform. */
202
208
  generateHookConfig(pluginRoot: string): HookRegistration;
203
209
  /** Read current platform settings. */
@@ -223,7 +229,10 @@ export interface HookAdapter {
223
229
  getHealthChecks?(pluginRoot: string): readonly HealthCheck[];
224
230
  /** Check if the plugin is registered/enabled on this platform. */
225
231
  checkPluginRegistration(): DiagnosticResult;
226
- /** Get the installed version from this platform's registry/marketplace. */
232
+ /**
233
+ * Get the installed version from this platform's registry/marketplace, or
234
+ * "standalone" when no platform-owned plugin version exists.
235
+ */
227
236
  getInstalledVersion(): string;
228
237
  /** Configure all hooks for this platform. Returns change descriptions. */
229
238
  configureAllHooks(pluginRoot: string): string[];
package/build/cli.d.ts CHANGED
@@ -7,6 +7,8 @@
7
7
  * context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
8
8
  * context-mode upgrade → Fix hooks, permissions, and settings
9
9
  * context-mode hook <platform> <event> → Dispatch a hook script (used by platform hook configs)
10
+ * CONTEXT_MODE_DIR=/abs/path context-mode → Override sessions/content storage root
11
+ * Empty/whitespace is ignored; non-empty values must be absolute.
10
12
  *
11
13
  * Platform auto-detection: CLI detects which platform is running
12
14
  * (Claude Code, Gemini CLI, OpenCode, etc.) and uses the appropriate adapter.
package/build/cli.js CHANGED
@@ -7,6 +7,8 @@
7
7
  * context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
8
8
  * context-mode upgrade → Fix hooks, permissions, and settings
9
9
  * context-mode hook <platform> <event> → Dispatch a hook script (used by platform hook configs)
10
+ * CONTEXT_MODE_DIR=/abs/path context-mode → Override sessions/content storage root
11
+ * Empty/whitespace is ignored; non-empty values must be absolute.
10
12
  *
11
13
  * Platform auto-detection: CLI detects which platform is running
12
14
  * (Claude Code, Gemini CLI, OpenCode, etc.) and uses the appropriate adapter.
@@ -22,6 +24,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
22
24
  import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
23
25
  import { getHookScriptPaths } from "./util/hook-config.js";
24
26
  import { resolveClaudeConfigDir } from "./util/claude-config.js";
27
+ import { ensureWritableStorageDir, formatStorageDirectoryError, resolveContentStorageDir, resolveSessionStorageDir, resolveStatsStorageDir, StorageDirectoryError, } from "./session/db.js";
25
28
  // v1.0.128 — Issue #559 sibling MCP kill helpers (see PR-559-560-FIX-DESIGN.md).
26
29
  import { discoverSiblingMcpPids, killSiblingMcpServers } from "./util/sibling-mcp.js";
27
30
  // v1.0.119 — Issue #523 Layer 5 heal: post-bump assertion on .claude-plugin/plugin.json
@@ -125,8 +128,26 @@ async function hookDispatch(platform, event) {
125
128
  /* -------------------------------------------------------
126
129
  * Entry point
127
130
  * ------------------------------------------------------- */
131
+ const IN_PROCESS_PLUGIN_PLATFORMS = new Set(["opencode", "kilo"]);
132
+ const isInProcessPluginPlatform = (p) => p ? IN_PROCESS_PLUGIN_PLATFORMS.has(p) : false;
128
133
  const args = process.argv.slice(2);
129
- if (args[0] === "doctor") {
134
+ function printHelp() {
135
+ console.log([
136
+ "Usage:",
137
+ " context-mode Start MCP server (stdio)",
138
+ " context-mode doctor Diagnose runtime issues, hooks, FTS5, version",
139
+ " context-mode upgrade Fix hooks, permissions, and settings",
140
+ " context-mode hook <platform> <event> Dispatch a configured hook script",
141
+ " context-mode statusline Print Claude Code status line",
142
+ "",
143
+ "Environment:",
144
+ " CONTEXT_MODE_DIR=/absolute/path Override sessions/content storage root; empty is ignored, non-empty must be absolute",
145
+ ].join("\n"));
146
+ }
147
+ if (args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
148
+ printHelp();
149
+ }
150
+ else if (args[0] === "doctor") {
130
151
  doctor().then((code) => process.exit(code));
131
152
  }
132
153
  else if (args[0] === "upgrade") {
@@ -236,7 +257,7 @@ function cachePluginRoot(platform) {
236
257
  }
237
258
  function getPluginRoot() {
238
259
  const platform = detectPlatform().platform;
239
- if (platform === 'opencode' || platform === 'kilo') {
260
+ if (isInProcessPluginPlatform(platform)) {
240
261
  return cachePluginRoot(platform);
241
262
  }
242
263
  return defaultPluginRoot();
@@ -276,6 +297,25 @@ async function fetchLatestVersion() {
276
297
  /* -------------------------------------------------------
277
298
  * Doctor — adapter-aware diagnostics
278
299
  * ------------------------------------------------------- */
300
+ function describeStorageSource(dir) {
301
+ return dir.envVar ? dir.envVar : "adapter default";
302
+ }
303
+ function logStorageDir(dir) {
304
+ try {
305
+ ensureWritableStorageDir(dir);
306
+ p.log.success(color.green(`Storage ${dir.kind}: PASS`) +
307
+ color.dim(` — ${dir.path} (${describeStorageSource(dir)})`));
308
+ return 0;
309
+ }
310
+ catch (err) {
311
+ if (err instanceof StorageDirectoryError) {
312
+ p.log.error(color.red(`Storage ${dir.kind}: FAIL`) +
313
+ color.dim(` — ${formatStorageDirectoryError(err)}`));
314
+ return 1;
315
+ }
316
+ throw err;
317
+ }
318
+ }
279
319
  async function doctor() {
280
320
  if (process.stdout.isTTY)
281
321
  console.clear();
@@ -286,6 +326,29 @@ async function doctor() {
286
326
  p.log.info(`Platform: ${color.cyan(adapter.name)}` +
287
327
  color.dim(` (${detection.confidence} confidence — ${detection.reason})`));
288
328
  let criticalFails = 0;
329
+ try {
330
+ const sessionDir = resolveSessionStorageDir(() => adapter.getSessionDir());
331
+ const contentDir = resolveContentStorageDir(() => sessionDir.path);
332
+ const statsDir = resolveStatsStorageDir(() => sessionDir.path);
333
+ p.note([
334
+ `sessions: ${sessionDir.path} (${describeStorageSource(sessionDir)})`,
335
+ `content: ${contentDir.path} (${describeStorageSource(contentDir)})`,
336
+ `stats: ${statsDir.path} (${describeStorageSource(statsDir)})`,
337
+ ].join("\n"), "Storage paths");
338
+ criticalFails += logStorageDir(sessionDir);
339
+ criticalFails += logStorageDir(contentDir);
340
+ criticalFails += logStorageDir(statsDir);
341
+ }
342
+ catch (err) {
343
+ if (err instanceof StorageDirectoryError) {
344
+ criticalFails++;
345
+ p.log.error(color.red(`Storage ${err.kind}: FAIL`) +
346
+ color.dim(` — ${formatStorageDirectoryError(err)}`));
347
+ }
348
+ else {
349
+ throw err;
350
+ }
351
+ }
289
352
  const s = p.spinner();
290
353
  s.start("Running diagnostics");
291
354
  let runtimes;
@@ -700,7 +763,11 @@ async function doctor() {
700
763
  ` — local v${localVersion}, latest v${latestVersion}` +
701
764
  color.dim("\n Run: /context-mode:ctx-upgrade"));
702
765
  }
703
- if (installedVersion === "not installed") {
766
+ if (installedVersion === "standalone") {
767
+ p.log.info(color.dim(`${adapter.name}: standalone MCP mode`) +
768
+ " — no platform plugin version to compare");
769
+ }
770
+ else if (installedVersion === "not installed") {
704
771
  p.log.info(color.dim(`${adapter.name}: not installed`) +
705
772
  " — using standalone MCP mode");
706
773
  }
@@ -738,8 +805,8 @@ async function insight(port) {
738
805
  // Detect platform + adapter for correct session/content paths
739
806
  const detection = detectPlatform();
740
807
  const adapter = await getAdapter(detection.platform);
741
- const sessDir = adapter.getSessionDir();
742
- const contentDir = join(dirname(sessDir), "content");
808
+ const sessDir = ensureWritableStorageDir(resolveSessionStorageDir(() => adapter.getSessionDir()));
809
+ const contentDir = ensureWritableStorageDir(resolveContentStorageDir(() => sessDir));
743
810
  const cacheDir = join(dirname(sessDir), "insight-cache");
744
811
  if (!existsSync(join(insightSource, "server.mjs"))) {
745
812
  console.error("Error: Insight source not found. Try upgrading context-mode.");
@@ -1141,7 +1208,7 @@ async function upgrade(opts) {
1141
1208
  timeout: 60000,
1142
1209
  });
1143
1210
  s.stop("Dependencies ready");
1144
- if (detection.platform !== 'opencode' && detection.platform !== 'kilo') {
1211
+ if (!isInProcessPluginPlatform(detection.platform)) {
1145
1212
  // Verify native addons through the same bootstrap start.mjs imports.
1146
1213
  // On modern Node, the ABI-specific cache file is the compatibility marker;
1147
1214
  // the active binding alone may be stale from a previous Node ABI.
@@ -1203,20 +1270,20 @@ async function upgrade(opts) {
1203
1270
  color.dim(`\n Try (primary): cd "${pluginRoot}" && npm install better-sqlite3 --no-optional`) +
1204
1271
  color.dim("\n Try (fallback): /context-mode:ctx-doctor"));
1205
1272
  }
1206
- }
1207
- // Update global npm
1208
- s.start("Updating npm global package");
1209
- try {
1210
- npmExecFile(["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
1211
- stdio: "pipe",
1212
- timeout: 30000,
1213
- });
1214
- s.stop(color.green("npm global updated"));
1215
- changes.push("Updated npm global package");
1216
- }
1217
- catch {
1218
- s.stop(color.yellow("npm global update skipped"));
1219
- p.log.info(color.dim(" Could not update global npm — may need sudo or standalone install"));
1273
+ // Update global npm
1274
+ s.start("Updating npm global package");
1275
+ try {
1276
+ npmExecFile(["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
1277
+ stdio: "pipe",
1278
+ timeout: 30000,
1279
+ });
1280
+ s.stop(color.green("npm global updated"));
1281
+ changes.push("Updated npm global package");
1282
+ }
1283
+ catch {
1284
+ s.stop(color.yellow("npm global update skipped"));
1285
+ p.log.info(color.dim(" Could not update global npm — may need sudo or standalone install"));
1286
+ }
1220
1287
  }
1221
1288
  // Cleanup
1222
1289
  rmSync(tmpDir, { recursive: true, force: true });
@@ -19,7 +19,12 @@ export interface AutoMemoryResult {
19
19
  export interface AutoMemoryAdapter {
20
20
  getConfigDir(): string;
21
21
  getInstructionFiles(): string[];
22
- getMemoryDir(): string;
22
+ /**
23
+ * `projectDir` is optional for backwards compatibility with legacy
24
+ * callers — when supplied, adapters MUST return a project-scoped path
25
+ * (see HookAdapter.getMemoryDir contract, issue #663).
26
+ */
27
+ getMemoryDir(projectDir?: string): string;
23
28
  }
24
29
  /**
25
30
  * Search auto-memory files for content matching any of the given queries.
@@ -8,6 +8,7 @@
8
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
9
9
  import { join, isAbsolute } from "node:path";
10
10
  import { resolveClaudeConfigDir } from "../util/claude-config.js";
11
+ import { hashProjectDirCanonical } from "../session/db.js";
11
12
  const DEBUG = process.env.DEBUG?.includes("context-mode");
12
13
  /**
13
14
  * Search auto-memory files for content matching any of the given queries.
@@ -38,10 +39,18 @@ export function searchAutoMemory(queries, limit = 5, projectDir, configDir, adap
38
39
  // CC config trees (and empty/whitespace env doesn't poison the path).
39
40
  const adapterRelative = adapterConfigDir ? resolveAgainst(projectDir, adapterConfigDir) : null;
40
41
  const effectiveConfigDir = adapterRelative ?? configDir ?? resolveClaudeConfigDir();
41
- const adapterMemoryDir = adapter?.getMemoryDir();
42
+ // Issue #663: scope memory dir by projectDir so parallel projects can't
43
+ // read each other's auto-memory. Adapter-aware path delegates the
44
+ // scoping to the adapter; legacy adapterless fallback applies the same
45
+ // hash directly so the contract holds at both call sites.
46
+ const adapterMemoryDir = adapter?.getMemoryDir(projectDir);
47
+ const fallbackMemoryBase = join(effectiveConfigDir, "memory");
48
+ const fallbackMemoryDir = projectDir
49
+ ? join(fallbackMemoryBase, hashProjectDirCanonical(projectDir))
50
+ : fallbackMemoryBase;
42
51
  const memoryDir = adapterMemoryDir
43
52
  ? resolveAgainst(projectDir, adapterMemoryDir)
44
- : join(effectiveConfigDir, "memory");
53
+ : fallbackMemoryDir;
45
54
  // Collect candidate files
46
55
  const candidates = [];
47
56
  // 1. Project-level instruction files