context-mode 1.0.111 → 1.0.113

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 (153) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/index.ts +3 -2
  4. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  5. package/.openclaw-plugin/package.json +1 -1
  6. package/README.md +152 -34
  7. package/bin/statusline.mjs +144 -127
  8. package/build/adapters/base.d.ts +8 -5
  9. package/build/adapters/base.js +8 -18
  10. package/build/adapters/claude-code/index.d.ts +24 -3
  11. package/build/adapters/claude-code/index.js +44 -11
  12. package/build/adapters/codex/hooks.d.ts +10 -5
  13. package/build/adapters/codex/hooks.js +10 -5
  14. package/build/adapters/codex/index.d.ts +17 -5
  15. package/build/adapters/codex/index.js +337 -37
  16. package/build/adapters/codex/paths.d.ts +1 -0
  17. package/build/adapters/codex/paths.js +12 -0
  18. package/build/adapters/cursor/index.d.ts +6 -0
  19. package/build/adapters/cursor/index.js +83 -2
  20. package/build/adapters/detect.d.ts +1 -1
  21. package/build/adapters/detect.js +29 -6
  22. package/build/adapters/omp/index.d.ts +65 -0
  23. package/build/adapters/omp/index.js +182 -0
  24. package/build/adapters/omp/plugin.d.ts +75 -0
  25. package/build/adapters/omp/plugin.js +220 -0
  26. package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
  27. package/build/adapters/openclaw/mcp-tools.js +198 -0
  28. package/build/adapters/openclaw/plugin.d.ts +130 -0
  29. package/build/adapters/openclaw/plugin.js +629 -0
  30. package/build/adapters/openclaw/workspace-router.d.ts +29 -0
  31. package/build/adapters/openclaw/workspace-router.js +64 -0
  32. package/build/adapters/opencode/plugin.d.ts +145 -0
  33. package/build/adapters/opencode/plugin.js +457 -0
  34. package/build/adapters/pi/extension.d.ts +26 -0
  35. package/build/adapters/pi/extension.js +552 -0
  36. package/build/adapters/pi/index.d.ts +57 -0
  37. package/build/adapters/pi/index.js +173 -0
  38. package/build/adapters/pi/mcp-bridge.d.ts +113 -0
  39. package/build/adapters/pi/mcp-bridge.js +251 -0
  40. package/build/adapters/types.d.ts +11 -6
  41. package/build/cli.js +186 -170
  42. package/build/db-base.d.ts +15 -2
  43. package/build/db-base.js +50 -5
  44. package/build/executor.d.ts +2 -0
  45. package/build/executor.js +15 -2
  46. package/build/runPool.d.ts +36 -0
  47. package/build/runPool.js +51 -0
  48. package/build/runtime.js +64 -5
  49. package/build/search/auto-memory.js +6 -4
  50. package/build/security.js +30 -10
  51. package/build/server.d.ts +23 -1
  52. package/build/server.js +662 -182
  53. package/build/session/analytics.d.ts +404 -1
  54. package/build/session/analytics.js +1347 -42
  55. package/build/session/db.d.ts +114 -5
  56. package/build/session/db.js +275 -27
  57. package/build/session/event-emit.d.ts +48 -0
  58. package/build/session/event-emit.js +101 -0
  59. package/build/session/extract.d.ts +1 -0
  60. package/build/session/extract.js +79 -12
  61. package/build/session/purge.d.ts +111 -0
  62. package/build/session/purge.js +138 -0
  63. package/build/store.d.ts +7 -0
  64. package/build/store.js +69 -6
  65. package/build/util/claude-config.d.ts +26 -0
  66. package/build/util/claude-config.js +91 -0
  67. package/build/util/hook-config.d.ts +4 -0
  68. package/build/util/hook-config.js +39 -0
  69. package/build/util/project-dir.d.ts +49 -0
  70. package/build/util/project-dir.js +67 -0
  71. package/cli.bundle.mjs +411 -208
  72. package/configs/antigravity/GEMINI.md +0 -3
  73. package/configs/claude-code/CLAUDE.md +1 -4
  74. package/configs/codex/AGENTS.md +1 -4
  75. package/configs/codex/config.toml +3 -0
  76. package/configs/codex/hooks.json +8 -0
  77. package/configs/cursor/context-mode.mdc +0 -3
  78. package/configs/gemini-cli/GEMINI.md +0 -3
  79. package/configs/jetbrains-copilot/copilot-instructions.md +0 -3
  80. package/configs/kilo/AGENTS.md +0 -3
  81. package/configs/kiro/KIRO.md +0 -3
  82. package/configs/omp/SYSTEM.md +85 -0
  83. package/configs/omp/mcp.json +7 -0
  84. package/configs/openclaw/AGENTS.md +0 -3
  85. package/configs/opencode/AGENTS.md +0 -3
  86. package/configs/pi/AGENTS.md +0 -3
  87. package/configs/qwen-code/QWEN.md +1 -4
  88. package/configs/vscode-copilot/copilot-instructions.md +0 -3
  89. package/configs/zed/AGENTS.md +0 -3
  90. package/hooks/codex/posttooluse.mjs +9 -2
  91. package/hooks/codex/precompact.mjs +69 -0
  92. package/hooks/codex/sessionstart.mjs +13 -9
  93. package/hooks/codex/stop.mjs +1 -2
  94. package/hooks/codex/userpromptsubmit.mjs +1 -2
  95. package/hooks/core/routing.mjs +237 -18
  96. package/hooks/cursor/afteragentresponse.mjs +1 -1
  97. package/hooks/cursor/hooks.json +31 -0
  98. package/hooks/cursor/posttooluse.mjs +1 -1
  99. package/hooks/cursor/sessionstart.mjs +5 -5
  100. package/hooks/cursor/stop.mjs +1 -1
  101. package/hooks/ensure-deps.mjs +12 -13
  102. package/hooks/gemini-cli/aftertool.mjs +1 -1
  103. package/hooks/gemini-cli/beforeagent.mjs +1 -1
  104. package/hooks/gemini-cli/precompress.mjs +3 -2
  105. package/hooks/gemini-cli/sessionstart.mjs +9 -9
  106. package/hooks/jetbrains-copilot/posttooluse.mjs +1 -1
  107. package/hooks/jetbrains-copilot/precompact.mjs +3 -2
  108. package/hooks/jetbrains-copilot/sessionstart.mjs +9 -9
  109. package/hooks/kiro/agentspawn.mjs +5 -5
  110. package/hooks/kiro/posttooluse.mjs +2 -2
  111. package/hooks/kiro/userpromptsubmit.mjs +1 -1
  112. package/hooks/posttooluse.mjs +45 -0
  113. package/hooks/precompact.mjs +17 -0
  114. package/hooks/pretooluse.mjs +23 -0
  115. package/hooks/routing-block.mjs +0 -12
  116. package/hooks/run-hook.mjs +16 -3
  117. package/hooks/session-db.bundle.mjs +27 -18
  118. package/hooks/session-extract.bundle.mjs +2 -2
  119. package/hooks/session-helpers.mjs +101 -64
  120. package/hooks/sessionstart.mjs +51 -2
  121. package/hooks/vscode-copilot/posttooluse.mjs +1 -1
  122. package/hooks/vscode-copilot/precompact.mjs +3 -2
  123. package/hooks/vscode-copilot/sessionstart.mjs +9 -9
  124. package/openclaw.plugin.json +1 -1
  125. package/package.json +14 -8
  126. package/server.bundle.mjs +349 -147
  127. package/start.mjs +16 -4
  128. package/skills/UPSTREAM-CREDITS.md +0 -51
  129. package/skills/context-mode-ops/SKILL.md +0 -299
  130. package/skills/context-mode-ops/agent-teams.md +0 -198
  131. package/skills/context-mode-ops/communication.md +0 -224
  132. package/skills/context-mode-ops/marketing.md +0 -124
  133. package/skills/context-mode-ops/release.md +0 -214
  134. package/skills/context-mode-ops/review-pr.md +0 -269
  135. package/skills/context-mode-ops/tdd.md +0 -329
  136. package/skills/context-mode-ops/triage-issue.md +0 -266
  137. package/skills/context-mode-ops/validation.md +0 -307
  138. package/skills/diagnose/SKILL.md +0 -122
  139. package/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
  140. package/skills/grill-me/SKILL.md +0 -15
  141. package/skills/grill-with-docs/ADR-FORMAT.md +0 -47
  142. package/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -77
  143. package/skills/grill-with-docs/SKILL.md +0 -93
  144. package/skills/improve-codebase-architecture/DEEPENING.md +0 -37
  145. package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
  146. package/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
  147. package/skills/improve-codebase-architecture/SKILL.md +0 -76
  148. package/skills/tdd/SKILL.md +0 -114
  149. package/skills/tdd/deep-modules.md +0 -33
  150. package/skills/tdd/interface-design.md +0 -31
  151. package/skills/tdd/mocking.md +0 -59
  152. package/skills/tdd/refactoring.md +0 -10
  153. package/skills/tdd/tests.md +0 -61
@@ -4,11 +4,12 @@
4
4
  * Native Cursor hooks use lower-camel hook names and flat command entries in
5
5
  * `.cursor/hooks.json` / `~/.cursor/hooks.json`.
6
6
  */
7
- import { readFileSync, writeFileSync, mkdirSync, accessSync, chmodSync, constants, existsSync, } from "node:fs";
7
+ import { readFileSync, writeFileSync, mkdirSync, accessSync, chmodSync, constants, existsSync, readdirSync, } from "node:fs";
8
8
  import { execSync } from "node:child_process";
9
9
  import { resolve, join } from "node:path";
10
10
  import { homedir } from "node:os";
11
11
  import { BaseAdapter } from "../base.js";
12
+ import { resolveClaudeConfigDir } from "../../util/claude-config.js";
12
13
  import { HOOK_TYPES as CURSOR_HOOK_NAMES, HOOK_SCRIPTS as CURSOR_HOOK_SCRIPTS, PRE_TOOL_USE_MATCHER_PATTERN, REQUIRED_HOOKS, OPTIONAL_HOOKS, isContextModeHook, buildHookCommand, } from "./hooks.js";
13
14
  const CURSOR_ENTERPRISE_HOOKS_PATH = "/Library/Application Support/Cursor/hooks.json";
14
15
  export class CursorAdapter extends BaseAdapter {
@@ -263,8 +264,72 @@ export class CursorAdapter extends BaseAdapter {
263
264
  message: "Claude-compatible hooks detected; native Cursor hooks are the supported configuration",
264
265
  });
265
266
  }
267
+ const pluginInstalls = this.detectPluginInstalls();
268
+ if (pluginInstalls.length > 0) {
269
+ const nativeHasContextMode = loaded
270
+ ? Object.entries(loaded.config.hooks ?? {}).some(([type, entries]) => Array.isArray(entries) && entries.some((entry) => isContextModeHook(entry, type)))
271
+ : false;
272
+ if (nativeHasContextMode && loaded) {
273
+ results.push({
274
+ check: "Plugin/native hook duplication",
275
+ status: "warn",
276
+ message: `context-mode plugin detected at ${pluginInstalls[0]} alongside native hooks in ${loaded.path} — ` +
277
+ `each event will fire twice. Remove one configuration to avoid duplicate routing.`,
278
+ fix: "Remove the native .cursor/hooks.json entries OR uninstall the plugin",
279
+ });
280
+ }
281
+ else {
282
+ results.push({
283
+ check: "Plugin install",
284
+ status: "pass",
285
+ message: `context-mode plugin installed at ${pluginInstalls[0]}`,
286
+ });
287
+ }
288
+ }
266
289
  return results;
267
290
  }
291
+ /**
292
+ * Detects context-mode plugin installations under Cursor's plugin directories.
293
+ * Returns absolute paths to any `.cursor-plugin/plugin.json` files whose
294
+ * `name` matches `context-mode`.
295
+ */
296
+ detectPluginInstalls() {
297
+ const roots = [
298
+ join(homedir(), ".cursor", "plugins", "local"),
299
+ join(homedir(), ".cursor", "plugins", "cache"),
300
+ ];
301
+ const found = [];
302
+ for (const root of roots) {
303
+ try {
304
+ accessSync(root, constants.F_OK);
305
+ }
306
+ catch {
307
+ continue;
308
+ }
309
+ // Plugins live one directory deep: <root>/<name>/.cursor-plugin/plugin.json
310
+ let entries = [];
311
+ try {
312
+ entries = readdirSync(root);
313
+ }
314
+ catch {
315
+ continue;
316
+ }
317
+ for (const name of entries) {
318
+ const manifestPath = join(root, name, ".cursor-plugin", "plugin.json");
319
+ try {
320
+ const raw = readFileSync(manifestPath, "utf-8");
321
+ const parsed = JSON.parse(raw);
322
+ if (parsed?.name === "context-mode") {
323
+ found.push(manifestPath);
324
+ }
325
+ }
326
+ catch {
327
+ continue;
328
+ }
329
+ }
330
+ }
331
+ return found;
332
+ }
268
333
  checkPluginRegistration() {
269
334
  const mcpPaths = [resolve(".cursor", "mcp.json"), join(homedir(), ".cursor", "mcp.json")];
270
335
  for (const configPath of mcpPaths) {
@@ -294,6 +359,19 @@ export class CursorAdapter extends BaseAdapter {
294
359
  continue;
295
360
  }
296
361
  }
362
+ // #489 round-3 — pure plugin install (Marketplace) bundles MCP registration
363
+ // inside the plugin package. No native mcp.json exists, but the plugin
364
+ // manifest under ~/.cursor/plugins/{local,cache}/<name>/.cursor-plugin/plugin.json
365
+ // is enough to consider context-mode registered. Without this, doctor
366
+ // self-contradicts: `Plugin install: pass` alongside `MCP registration: warn`.
367
+ const pluginInstalls = this.detectPluginInstalls();
368
+ if (pluginInstalls.length > 0) {
369
+ return {
370
+ check: "MCP registration",
371
+ status: "pass",
372
+ message: `context-mode registered via plugin manifest at ${pluginInstalls[0]}`,
373
+ };
374
+ }
297
375
  return {
298
376
  check: "MCP registration",
299
377
  status: "warn",
@@ -412,10 +490,13 @@ export class CursorAdapter extends BaseAdapter {
412
490
  return null;
413
491
  }
414
492
  hasClaudeCompatibilityHooks() {
493
+ // Issue #460 round-3: probe the resolved CC config dir (honors
494
+ // $CLAUDE_CONFIG_DIR) instead of the literal ~/.claude so users
495
+ // who relocated their CC config still trigger the compat path.
415
496
  const compatPaths = [
416
497
  resolve(".claude", "settings.json"),
417
498
  resolve(".claude", "settings.local.json"),
418
- join(homedir(), ".claude", "settings.json"),
499
+ join(resolveClaudeConfigDir(), "settings.json"),
419
500
  ];
420
501
  return compatPaths.some((configPath) => existsSync(configPath));
421
502
  }
@@ -23,7 +23,7 @@ import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
23
23
  * Single source of truth — consumed by detectPlatform() below and by
24
24
  * tests that need to clear platform-related env vars deterministically.
25
25
  */
26
- export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["antigravity", readonly ["ANTIGRAVITY_CLI_ALIAS"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["kilo", readonly ["KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["zed", readonly ["ZED_SESSION_ID", "ZED_TERM"]], readonly ["codex", readonly ["CODEX_THREAD_ID", "CODEX_CI"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR"]], readonly ["pi", readonly ["PI_PROJECT_DIR"]]];
26
+ export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["antigravity", readonly ["ANTIGRAVITY_CLI_ALIAS"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["kilo", readonly ["KILO", "KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["zed", readonly ["ZED_SESSION_ID", "ZED_TERM"]], readonly ["codex", readonly ["CODEX_THREAD_ID", "CODEX_CI"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR"]], readonly ["omp", readonly ["PI_CODING_AGENT_DIR"]], readonly ["pi", readonly ["PI_PROJECT_DIR"]]];
27
27
  /**
28
28
  * Sync map from platform identifier → home-relative path segments where that
29
29
  * platform stores its config. Mirrors the `super([...])` argument passed by
@@ -39,10 +39,9 @@ export const PLATFORM_ENV_VARS = [
39
39
  // 800+ hits in major OSS detection libs (Vercel Next.js, Bun, Google
40
40
  // gemini-cli, Nx, CrewAI).
41
41
  ["cursor", ["CURSOR_TRACE_ID", "CURSOR_CLI"]],
42
- // kilo (OpenCode fork) — Kilo-Org/kilocode packages/opencode/src/index.ts:140
43
- // sets `process.env.KILO_PID = String(process.pid)`. Bare KILO is NEVER set
44
- // (verified). Kilo also sets OPENCODE=1 (fork) — listed before opencode.
45
- ["kilo", ["KILO_PID"]],
42
+ // kilo (OpenCode fork) — Kilo-Org/kilocode packages/opencode/src/index.ts:138 + 139
43
+ // sets `process.env.KILO = 1` + `process.env.KILO_PID = String(process.pid)`.
44
+ ["kilo", ["KILO", "KILO_PID"]],
46
45
  // opencode — sst/opencode packages/opencode/src/index.ts:108-109 sets
47
46
  // OPENCODE=1 + OPENCODE_PID=<pid> on every CLI invocation.
48
47
  ["opencode", ["OPENCODE", "OPENCODE_PID"]],
@@ -64,7 +63,11 @@ export const PLATFORM_ENV_VARS = [
64
63
  // qwen-code — QWEN_PROJECT_DIR per QwenLM/qwen-code docs/users/features/hooks.md.
65
64
  // (QWEN_SESSION_ID removed — 0 hits in qwen-code repository.)
66
65
  ["qwen-code", ["QWEN_PROJECT_DIR"]],
67
- // pi — PI_PROJECT_DIR consumed by src/pi-extension.ts:154 + src/server.ts:153
66
+ // omp (can1357/oh-my-pi). PI_CODING_AGENT_DIR is the upstream
67
+ // agent-dir override per `packages/utils/src/dirs.ts:193`. Listed
68
+ // BEFORE pi so OMP is not misclassified as Pi when both are installed.
69
+ ["omp", ["PI_CODING_AGENT_DIR"]],
70
+ // pi — PI_PROJECT_DIR consumed by src/adapters/pi/extension.ts:154 + src/server.ts:153
68
71
  // — implies the Pi runtime sets it before invoking the extension.
69
72
  ["pi", ["PI_PROJECT_DIR"]],
70
73
  // openclaw — removed (runtime never sets OPENCLAW_HOME or OPENCLAW_CLI;
@@ -92,6 +95,7 @@ export function getSessionDirSegments(platform) {
92
95
  case "vscode-copilot": return [".vscode"];
93
96
  case "kiro": return [".kiro"];
94
97
  case "pi": return [".pi"];
98
+ case "omp": return [".omp"];
95
99
  case "qwen-code": return [".qwen"];
96
100
  case "kilo": return [".config", "kilo"];
97
101
  case "opencode": return [".config", "opencode"];
@@ -131,7 +135,7 @@ export function detectPlatform(clientInfo) {
131
135
  if (platformOverride) {
132
136
  const validPlatforms = [
133
137
  "claude-code", "gemini-cli", "kilo", "opencode", "codex",
134
- "vscode-copilot", "jetbrains-copilot", "cursor", "antigravity", "kiro", "pi", "zed", "qwen-code",
138
+ "vscode-copilot", "jetbrains-copilot", "cursor", "antigravity", "kiro", "pi", "omp", "zed", "qwen-code",
135
139
  ];
136
140
  if (validPlatforms.includes(platformOverride)) {
137
141
  return {
@@ -188,6 +192,14 @@ export function detectPlatform(clientInfo) {
188
192
  reason: "~/.kiro/ directory exists",
189
193
  };
190
194
  }
195
+ // OMP listed BEFORE pi: shared ~/.pi history with OMP-only ~/.omp/ marker.
196
+ if (existsSync(resolve(home, ".omp"))) {
197
+ return {
198
+ platform: "omp",
199
+ confidence: "medium",
200
+ reason: "~/.omp/ directory exists",
201
+ };
202
+ }
191
203
  if (existsSync(resolve(home, ".pi"))) {
192
204
  return {
193
205
  platform: "pi",
@@ -300,6 +312,17 @@ export async function getAdapter(platform) {
300
312
  const { QwenCodeAdapter } = await import("./qwen-code/index.js");
301
313
  return new QwenCodeAdapter();
302
314
  }
315
+ case "omp": {
316
+ const { OMPAdapter } = await import("./omp/index.js");
317
+ return new OMPAdapter();
318
+ }
319
+ case "pi": {
320
+ // Issue #473 follow-up: without this case, getAdapter("pi") fell
321
+ // through to ClaudeCodeAdapter and Pi sessions wrote into
322
+ // ~/.claude/context-mode/. PiAdapter pins storage to ~/.pi/.
323
+ const { PiAdapter } = await import("./pi/index.js");
324
+ return new PiAdapter();
325
+ }
303
326
  default: {
304
327
  // Unsupported platform — fall back to Claude Code adapter
305
328
  // (MCP server works everywhere, hooks may not)
@@ -0,0 +1,65 @@
1
+ /**
2
+ * adapters/omp — Oh My Pi (OMP) platform adapter.
3
+ *
4
+ * OMP integration facts (verified against can1357/oh-my-pi @ v3.20.1):
5
+ * - MCP config: `~/.omp/agent/mcp.json` (global) or `<project>/.omp/mcp.json`
6
+ * (project), per `packages/utils/src/dirs.ts` `getMCPConfigPath()` and
7
+ * `docs/mcp-config.md` "Preferred config locations".
8
+ * - Agent-dir env override: `PI_CODING_AGENT_DIR` — `packages/utils/src/dirs.ts`:
9
+ * `let dirs = new DirResolver(process.env.PI_CODING_AGENT_DIR);`
10
+ * (No `OMP_*` runtime env exists; `.env`-file `OMP_*` keys are mirrored to
11
+ * `PI_*` BEFORE process.env is read.)
12
+ * - System-prompt file: `SYSTEM.md` (project `.omp/SYSTEM.md` precedence,
13
+ * global `~/.omp/agent/SYSTEM.md` fallback). NOT `PI.md` — no `PI.md`
14
+ * loader exists upstream. OMP also auto-discovers `AGENTS.md` via
15
+ * `packages/coding-agent/src/discovery/agents-md.ts`.
16
+ * - Hook surface: OMP DOES expose pre/post tool-call hooks
17
+ * (`~/.omp/agent/hooks/{pre,post}/*.ts`, `omp.on("tool_call", ...)`).
18
+ * This adapter ships MCP-only delivery for now; wiring native OMP
19
+ * hooks is future work tracked separately.
20
+ *
21
+ * Why a dedicated adapter rather than reusing pi:
22
+ * OMP and Pi share a runtime surface but different storage roots
23
+ * (`~/.omp/agent/` vs `~/.pi/`). Without an OMP adapter, OMP users
24
+ * running through a Claude-installed harness silently land their
25
+ * context-mode data under `~/.claude/context-mode/` (issue #473).
26
+ */
27
+ import { BaseAdapter } from "../base.js";
28
+ import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
29
+ export declare class OMPAdapter extends BaseAdapter implements HookAdapter {
30
+ constructor();
31
+ readonly name = "OMP";
32
+ readonly paradigm: HookParadigm;
33
+ readonly capabilities: PlatformCapabilities;
34
+ parsePreToolUseInput(_raw: unknown): PreToolUseEvent;
35
+ parsePostToolUseInput(_raw: unknown): PostToolUseEvent;
36
+ parsePreCompactInput(_raw: unknown): PreCompactEvent;
37
+ parseSessionStartInput(_raw: unknown): SessionStartEvent;
38
+ formatPreToolUseResponse(_response: PreToolUseResponse): unknown;
39
+ formatPostToolUseResponse(_response: PostToolUseResponse): unknown;
40
+ formatPreCompactResponse(_response: PreCompactResponse): unknown;
41
+ formatSessionStartResponse(_response: SessionStartResponse): unknown;
42
+ /**
43
+ * Resolve OMP agent root, honoring `PI_CODING_AGENT_DIR` when set
44
+ * (the upstream OMP convention — see `packages/utils/src/dirs.ts`)
45
+ * and falling back to `~/.omp/agent`.
46
+ */
47
+ private getAgentDir;
48
+ getSettingsPath(): string;
49
+ /**
50
+ * OMP nests its config under the agent dir. Always absolute.
51
+ * `_projectDir` accepted for interface symmetry but unused — home-rooted.
52
+ */
53
+ getConfigDir(_projectDir?: string): string;
54
+ getInstructionFiles(): string[];
55
+ generateHookConfig(_pluginRoot: string): HookRegistration;
56
+ readSettings(): Record<string, unknown> | null;
57
+ writeSettings(settings: Record<string, unknown>): void;
58
+ validateHooks(_pluginRoot: string): DiagnosticResult[];
59
+ checkPluginRegistration(): DiagnosticResult;
60
+ getInstalledVersion(): string;
61
+ configureAllHooks(_pluginRoot: string): string[];
62
+ setHookPermissions(_pluginRoot: string): string[];
63
+ updatePluginRegistry(_pluginRoot: string, _version: string): void;
64
+ getRoutingInstructions(): string;
65
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * adapters/omp — Oh My Pi (OMP) platform adapter.
3
+ *
4
+ * OMP integration facts (verified against can1357/oh-my-pi @ v3.20.1):
5
+ * - MCP config: `~/.omp/agent/mcp.json` (global) or `<project>/.omp/mcp.json`
6
+ * (project), per `packages/utils/src/dirs.ts` `getMCPConfigPath()` and
7
+ * `docs/mcp-config.md` "Preferred config locations".
8
+ * - Agent-dir env override: `PI_CODING_AGENT_DIR` — `packages/utils/src/dirs.ts`:
9
+ * `let dirs = new DirResolver(process.env.PI_CODING_AGENT_DIR);`
10
+ * (No `OMP_*` runtime env exists; `.env`-file `OMP_*` keys are mirrored to
11
+ * `PI_*` BEFORE process.env is read.)
12
+ * - System-prompt file: `SYSTEM.md` (project `.omp/SYSTEM.md` precedence,
13
+ * global `~/.omp/agent/SYSTEM.md` fallback). NOT `PI.md` — no `PI.md`
14
+ * loader exists upstream. OMP also auto-discovers `AGENTS.md` via
15
+ * `packages/coding-agent/src/discovery/agents-md.ts`.
16
+ * - Hook surface: OMP DOES expose pre/post tool-call hooks
17
+ * (`~/.omp/agent/hooks/{pre,post}/*.ts`, `omp.on("tool_call", ...)`).
18
+ * This adapter ships MCP-only delivery for now; wiring native OMP
19
+ * hooks is future work tracked separately.
20
+ *
21
+ * Why a dedicated adapter rather than reusing pi:
22
+ * OMP and Pi share a runtime surface but different storage roots
23
+ * (`~/.omp/agent/` vs `~/.pi/`). Without an OMP adapter, OMP users
24
+ * running through a Claude-installed harness silently land their
25
+ * context-mode data under `~/.claude/context-mode/` (issue #473).
26
+ */
27
+ import { readFileSync, writeFileSync, mkdirSync, } from "node:fs";
28
+ import { resolve, dirname } from "node:path";
29
+ import { homedir } from "node:os";
30
+ import { BaseAdapter } from "../base.js";
31
+ // ─────────────────────────────────────────────────────────
32
+ // Adapter implementation
33
+ // ─────────────────────────────────────────────────────────
34
+ export class OMPAdapter extends BaseAdapter {
35
+ constructor() {
36
+ super([".omp"]);
37
+ }
38
+ name = "OMP";
39
+ paradigm = "mcp-only";
40
+ capabilities = {
41
+ preToolUse: false,
42
+ postToolUse: false,
43
+ preCompact: false,
44
+ sessionStart: false,
45
+ canModifyArgs: false,
46
+ canModifyOutput: false,
47
+ canInjectSessionContext: false,
48
+ };
49
+ // ── Input parsing ──────────────────────────────────────
50
+ // OMP does not support hooks. These methods exist to satisfy the
51
+ // interface contract but will throw if called.
52
+ parsePreToolUseInput(_raw) {
53
+ throw new Error("OMP hooks not wired by this adapter (MCP-only delivery)");
54
+ }
55
+ parsePostToolUseInput(_raw) {
56
+ throw new Error("OMP hooks not wired by this adapter (MCP-only delivery)");
57
+ }
58
+ parsePreCompactInput(_raw) {
59
+ throw new Error("OMP hooks not wired by this adapter (MCP-only delivery)");
60
+ }
61
+ parseSessionStartInput(_raw) {
62
+ throw new Error("OMP hooks not wired by this adapter (MCP-only delivery)");
63
+ }
64
+ // ── Response formatting ────────────────────────────────
65
+ // OMP does not support hooks. Return undefined for all responses.
66
+ formatPreToolUseResponse(_response) {
67
+ return undefined;
68
+ }
69
+ formatPostToolUseResponse(_response) {
70
+ return undefined;
71
+ }
72
+ formatPreCompactResponse(_response) {
73
+ return undefined;
74
+ }
75
+ formatSessionStartResponse(_response) {
76
+ return undefined;
77
+ }
78
+ // ── Configuration ──────────────────────────────────────
79
+ /**
80
+ * Resolve OMP agent root, honoring `PI_CODING_AGENT_DIR` when set
81
+ * (the upstream OMP convention — see `packages/utils/src/dirs.ts`)
82
+ * and falling back to `~/.omp/agent`.
83
+ */
84
+ getAgentDir() {
85
+ return process.env.PI_CODING_AGENT_DIR
86
+ ?? resolve(homedir(), ".omp", "agent");
87
+ }
88
+ getSettingsPath() {
89
+ return resolve(this.getAgentDir(), "mcp.json");
90
+ }
91
+ /**
92
+ * OMP nests its config under the agent dir. Always absolute.
93
+ * `_projectDir` accepted for interface symmetry but unused — home-rooted.
94
+ */
95
+ getConfigDir(_projectDir) {
96
+ return this.getAgentDir();
97
+ }
98
+ getInstructionFiles() {
99
+ // SYSTEM.md is the OMP-native system-prompt file (see
100
+ // can1357/oh-my-pi README "Custom System Prompt"). AGENTS.md is also
101
+ // auto-discovered by the universal discovery layer.
102
+ return ["SYSTEM.md", "AGENTS.md"];
103
+ }
104
+ generateHookConfig(_pluginRoot) {
105
+ return {};
106
+ }
107
+ readSettings() {
108
+ try {
109
+ const raw = readFileSync(this.getSettingsPath(), "utf-8");
110
+ return JSON.parse(raw);
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ writeSettings(settings) {
117
+ const settingsPath = this.getSettingsPath();
118
+ mkdirSync(dirname(settingsPath), { recursive: true });
119
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
120
+ }
121
+ // ── Diagnostics (doctor) ─────────────────────────────────
122
+ validateHooks(_pluginRoot) {
123
+ return [
124
+ {
125
+ check: "Hook support",
126
+ status: "warn",
127
+ message: "context-mode delivers via MCP for OMP. " +
128
+ "Native OMP pre/post tool-call hooks are not yet wired by this adapter.",
129
+ },
130
+ ];
131
+ }
132
+ checkPluginRegistration() {
133
+ try {
134
+ const raw = readFileSync(this.getSettingsPath(), "utf-8");
135
+ const config = JSON.parse(raw);
136
+ const mcpServers = config?.mcpServers ?? {};
137
+ if ("context-mode" in mcpServers) {
138
+ return {
139
+ check: "MCP registration",
140
+ status: "pass",
141
+ message: "context-mode found in mcpServers config",
142
+ };
143
+ }
144
+ return {
145
+ check: "MCP registration",
146
+ status: "fail",
147
+ message: "context-mode not found in mcpServers",
148
+ fix: `Add context-mode to mcpServers in ${this.getSettingsPath()}`,
149
+ };
150
+ }
151
+ catch {
152
+ return {
153
+ check: "MCP registration",
154
+ status: "warn",
155
+ message: `Could not read ${this.getSettingsPath()}`,
156
+ };
157
+ }
158
+ }
159
+ getInstalledVersion() {
160
+ try {
161
+ const pkgPath = resolve(this.getAgentDir(), "extensions", "context-mode", "package.json");
162
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
163
+ return pkg.version ?? "unknown";
164
+ }
165
+ catch {
166
+ return "not installed";
167
+ }
168
+ }
169
+ // ── Upgrade ────────────────────────────────────────────
170
+ configureAllHooks(_pluginRoot) {
171
+ return [];
172
+ }
173
+ setHookPermissions(_pluginRoot) {
174
+ return [];
175
+ }
176
+ updatePluginRegistry(_pluginRoot, _version) {
177
+ // OMP MCP server registry is managed via mcp.json
178
+ }
179
+ getRoutingInstructions() {
180
+ return "# context-mode\n\nUse context-mode MCP tools (execute, execute_file, batch_execute, fetch_and_index, search) instead of run_command/view_file for data-heavy operations.";
181
+ }
182
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Oh My Pi (OMP) plugin entry point for context-mode.
3
+ *
4
+ * Mirrors the Pi extension shape (`src/adapters/pi/extension.ts`) for
5
+ * the four OMP hook events that materially protect the context window
6
+ * and persist session continuity:
7
+ *
8
+ * - session_start — initialize the session row in our DB
9
+ * - tool_call — hard-block curl/wget/inline-HTTP in bash
10
+ * - tool_result — extract structured events into the session DB
11
+ * - session_before_compact — persist a resume snapshot before compaction
12
+ *
13
+ * Loaded by OMP via the `omp` (or `pi`) field in package.json — see
14
+ * upstream loader at refs/platforms/oh-my-pi/packages/coding-agent/src/
15
+ * extensibility/plugins/loader.ts:75:
16
+ * `const manifest: PluginManifest | undefined = pluginPkg.omp || pluginPkg.pi;`
17
+ * Hook factory contract from refs/.../extensibility/hooks/types.ts:809:
18
+ * `export type HookFactory = (pi: HookAPI) => void;`
19
+ *
20
+ * OMP differs from Pi in two ways that justify a dedicated plugin file:
21
+ * 1. Storage roots at ~/.omp/context-mode/ via OMPAdapter, not ~/.pi/
22
+ * 2. OMP has native MCP support (mcp.json), so no MCP bridge is needed
23
+ * — the bridge that Pi's extension ships (mcp-bridge.ts) is dead weight
24
+ * under OMP and is intentionally omitted here.
25
+ */
26
+ export declare function _resetOmpPluginStateForTests(): void;
27
+ /**
28
+ * Return the current session ID picked by the most recent session_start
29
+ * handler. Test-only — production code reads `_sessionId` directly via
30
+ * the closure. The shared SQLite DB at `~/.omp/context-mode/` survives
31
+ * between tests, so `getLatestSessionId()` cannot disambiguate which
32
+ * row belongs to "this" test when multiple tests insert in the same
33
+ * second; tests use this getter instead.
34
+ */
35
+ export declare function _getOmpPluginSessionIdForTests(): string;
36
+ type ToolCallEvent = {
37
+ toolName: string;
38
+ toolCallId?: string;
39
+ input?: Record<string, unknown>;
40
+ };
41
+ type ToolResultEvent = {
42
+ toolName: string;
43
+ toolCallId?: string;
44
+ input?: Record<string, unknown>;
45
+ content?: Array<{
46
+ type: string;
47
+ text?: string;
48
+ }>;
49
+ isError?: boolean;
50
+ };
51
+ type ToolCallEventResult = {
52
+ block?: boolean;
53
+ reason?: string;
54
+ };
55
+ type HookEventCtx = Record<string, unknown> | undefined;
56
+ type HookHandler<E, R = void> = (event: E, ctx: HookEventCtx) => R | undefined | Promise<R | undefined>;
57
+ export interface MinimalHookAPI {
58
+ on(event: "session_start", handler: HookHandler<{
59
+ type: "session_start";
60
+ }>): void;
61
+ on(event: "session_before_compact", handler: HookHandler<{
62
+ type: "session_before_compact";
63
+ }>): void;
64
+ on(event: "tool_call", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;
65
+ on(event: "tool_result", handler: HookHandler<ToolResultEvent>): void;
66
+ on(event: string, handler: (...args: unknown[]) => unknown): void;
67
+ }
68
+ /**
69
+ * OMP plugin default export. Called once by the OMP runtime per
70
+ * upstream `extensibility/plugins/loader.ts` after `omp plugin install
71
+ * context-mode`. Subsequent `pi.on(...)` registrations route the four
72
+ * lifecycle events to our SessionDB-backed handlers below.
73
+ */
74
+ export default function ompPlugin(pi: MinimalHookAPI): void;
75
+ export {};