context-mode 1.0.110 → 1.0.112
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/index.ts +3 -2
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +152 -34
- package/bin/statusline.mjs +144 -127
- package/build/adapters/base.d.ts +8 -5
- package/build/adapters/base.js +8 -18
- package/build/adapters/claude-code/index.d.ts +24 -3
- package/build/adapters/claude-code/index.js +44 -11
- package/build/adapters/codex/hooks.d.ts +10 -5
- package/build/adapters/codex/hooks.js +10 -5
- package/build/adapters/codex/index.d.ts +17 -5
- package/build/adapters/codex/index.js +337 -37
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/cursor/index.d.ts +6 -0
- package/build/adapters/cursor/index.js +83 -2
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +29 -6
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +75 -0
- package/build/adapters/omp/plugin.js +220 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +198 -0
- package/build/adapters/openclaw/plugin.d.ts +130 -0
- package/build/adapters/openclaw/plugin.js +629 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/plugin.d.ts +145 -0
- package/build/adapters/opencode/plugin.js +457 -0
- package/build/adapters/pi/extension.d.ts +26 -0
- package/build/adapters/pi/extension.js +552 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +113 -0
- package/build/adapters/pi/mcp-bridge.js +251 -0
- package/build/adapters/types.d.ts +11 -6
- package/build/cli.js +186 -170
- package/build/db-base.d.ts +15 -2
- package/build/db-base.js +50 -5
- package/build/executor.d.ts +2 -0
- package/build/executor.js +15 -2
- package/build/opencode-plugin.js +1 -1
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.js +64 -5
- package/build/search/auto-memory.js +6 -4
- package/build/security.js +30 -10
- package/build/server.d.ts +23 -1
- package/build/server.js +652 -174
- package/build/session/analytics.d.ts +404 -1
- package/build/session/analytics.js +1347 -42
- package/build/session/db.d.ts +114 -5
- package/build/session/db.js +275 -27
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +1 -0
- package/build/session/extract.js +79 -12
- package/build/session/purge.d.ts +111 -0
- package/build/session/purge.js +138 -0
- package/build/store.d.ts +7 -0
- package/build/store.js +69 -6
- package/build/util/claude-config.d.ts +26 -0
- package/build/util/claude-config.js +91 -0
- package/build/util/hook-config.d.ts +4 -0
- package/build/util/hook-config.js +39 -0
- package/cli.bundle.mjs +411 -208
- package/configs/antigravity/GEMINI.md +0 -3
- package/configs/claude-code/CLAUDE.md +1 -4
- package/configs/codex/AGENTS.md +1 -4
- package/configs/codex/config.toml +3 -0
- package/configs/codex/hooks.json +8 -0
- package/configs/cursor/context-mode.mdc +0 -3
- package/configs/gemini-cli/GEMINI.md +0 -3
- package/configs/jetbrains-copilot/copilot-instructions.md +0 -3
- package/configs/kilo/AGENTS.md +0 -3
- package/configs/kiro/KIRO.md +0 -3
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +0 -3
- package/configs/opencode/AGENTS.md +0 -3
- package/configs/pi/AGENTS.md +0 -3
- package/configs/qwen-code/QWEN.md +1 -4
- package/configs/vscode-copilot/copilot-instructions.md +0 -3
- package/configs/zed/AGENTS.md +0 -3
- package/hooks/codex/posttooluse.mjs +9 -2
- package/hooks/codex/precompact.mjs +69 -0
- package/hooks/codex/sessionstart.mjs +13 -9
- package/hooks/codex/stop.mjs +1 -2
- package/hooks/codex/userpromptsubmit.mjs +1 -2
- package/hooks/core/routing.mjs +237 -18
- package/hooks/cursor/afteragentresponse.mjs +1 -1
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +1 -1
- package/hooks/cursor/sessionstart.mjs +5 -5
- package/hooks/cursor/stop.mjs +1 -1
- package/hooks/ensure-deps.mjs +12 -13
- package/hooks/gemini-cli/aftertool.mjs +1 -1
- package/hooks/gemini-cli/beforeagent.mjs +1 -1
- package/hooks/gemini-cli/precompress.mjs +3 -2
- package/hooks/gemini-cli/sessionstart.mjs +9 -9
- package/hooks/jetbrains-copilot/posttooluse.mjs +1 -1
- package/hooks/jetbrains-copilot/precompact.mjs +3 -2
- package/hooks/jetbrains-copilot/sessionstart.mjs +9 -9
- package/hooks/kiro/agentspawn.mjs +5 -5
- package/hooks/kiro/posttooluse.mjs +2 -2
- package/hooks/kiro/userpromptsubmit.mjs +1 -1
- package/hooks/posttooluse.mjs +45 -0
- package/hooks/precompact.mjs +17 -0
- package/hooks/pretooluse.mjs +23 -0
- package/hooks/routing-block.mjs +0 -12
- package/hooks/run-hook.mjs +16 -3
- package/hooks/session-db.bundle.mjs +27 -18
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +101 -64
- package/hooks/sessionstart.mjs +51 -2
- package/hooks/vscode-copilot/posttooluse.mjs +1 -1
- package/hooks/vscode-copilot/precompact.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +9 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +14 -8
- package/server.bundle.mjs +349 -147
- package/skills/UPSTREAM-CREDITS.md +0 -51
- package/skills/context-mode-ops/SKILL.md +0 -299
- package/skills/context-mode-ops/agent-teams.md +0 -198
- package/skills/context-mode-ops/communication.md +0 -224
- package/skills/context-mode-ops/marketing.md +0 -124
- package/skills/context-mode-ops/release.md +0 -214
- package/skills/context-mode-ops/review-pr.md +0 -269
- package/skills/context-mode-ops/tdd.md +0 -329
- package/skills/context-mode-ops/triage-issue.md +0 -266
- package/skills/context-mode-ops/validation.md +0 -307
- package/skills/diagnose/SKILL.md +0 -122
- package/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
- package/skills/grill-me/SKILL.md +0 -15
- package/skills/grill-with-docs/ADR-FORMAT.md +0 -47
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -77
- package/skills/grill-with-docs/SKILL.md +0 -93
- package/skills/improve-codebase-architecture/DEEPENING.md +0 -37
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
- package/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
- package/skills/improve-codebase-architecture/SKILL.md +0 -76
- package/skills/tdd/SKILL.md +0 -114
- package/skills/tdd/deep-modules.md +0 -33
- package/skills/tdd/interface-design.md +0 -31
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/refactoring.md +0 -10
- package/skills/tdd/tests.md +0 -61
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* adapters/codex/hooks — Codex CLI hook definitions.
|
|
3
3
|
*
|
|
4
|
-
* Codex CLI hooks
|
|
5
|
-
*
|
|
4
|
+
* Codex CLI hooks run behind the current `hooks` feature flag surface.
|
|
5
|
+
* Prefer `[features].hooks`; the legacy `[features].codex_hooks` alias is still
|
|
6
|
+
* accepted in current Codex builds.
|
|
7
|
+
* 6 hook events: PreToolUse, PostToolUse, PreCompact, SessionStart,
|
|
8
|
+
* UserPromptSubmit, Stop. PreCompact is runtime-gated on Codex builds that emit
|
|
9
|
+
* the event.
|
|
6
10
|
* Same JSON stdin/stdout wire protocol as Claude Code.
|
|
7
11
|
*
|
|
8
|
-
* Config: ~/.codex/hooks.json
|
|
9
|
-
* MCP: full support via [mcp_servers] in
|
|
12
|
+
* Config: $CODEX_HOME/hooks.json or ~/.codex/hooks.json.
|
|
13
|
+
* MCP: full support via [mcp_servers] in $CODEX_HOME/config.toml.
|
|
10
14
|
*
|
|
11
15
|
* Known limitations:
|
|
12
16
|
* - PreToolUse: deny works, updatedInput not yet supported (openai/codex#18491)
|
|
@@ -16,10 +20,11 @@
|
|
|
16
20
|
// ─────────────────────────────────────────────────────────
|
|
17
21
|
// Hook type constants
|
|
18
22
|
// ─────────────────────────────────────────────────────────
|
|
19
|
-
/** Codex CLI hook types — mirrors Claude Code's
|
|
23
|
+
/** Codex CLI hook types — mirrors Claude Code's continuity events. */
|
|
20
24
|
export const HOOK_TYPES = {
|
|
21
25
|
PRE_TOOL_USE: "PreToolUse",
|
|
22
26
|
POST_TOOL_USE: "PostToolUse",
|
|
27
|
+
PRE_COMPACT: "PreCompact",
|
|
23
28
|
SESSION_START: "SessionStart",
|
|
24
29
|
USER_PROMPT_SUBMIT: "UserPromptSubmit",
|
|
25
30
|
STOP: "Stop",
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Implements HookAdapter for Codex CLI's JSON stdin/stdout paradigm.
|
|
5
5
|
*
|
|
6
6
|
* Codex CLI hook specifics:
|
|
7
|
-
* -
|
|
7
|
+
* - 6 hook events: PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, Stop
|
|
8
8
|
* - Same wire protocol as Claude Code (JSON stdin → stdout)
|
|
9
|
-
* - Config: ~/.codex
|
|
10
|
-
* - Session dir:
|
|
9
|
+
* - Config: $CODEX_HOME or ~/.codex (hooks.json + config.toml)
|
|
10
|
+
* - Session dir: $CODEX_HOME/context-mode/sessions/
|
|
11
11
|
*
|
|
12
12
|
* Hook dispatch is stable in Codex CLI. PreToolUse deny decisions work,
|
|
13
13
|
* while input rewriting remains blocked on upstream updatedInput support.
|
|
@@ -28,16 +28,19 @@ export declare class CodexAdapter extends BaseAdapter implements HookAdapter {
|
|
|
28
28
|
formatPostToolUseResponse(response: PostToolUseResponse): unknown;
|
|
29
29
|
formatPreCompactResponse(response: PreCompactResponse): unknown;
|
|
30
30
|
formatSessionStartResponse(response: SessionStartResponse): unknown;
|
|
31
|
+
getConfigDir(_projectDir?: string): string;
|
|
31
32
|
getSettingsPath(): string;
|
|
33
|
+
getSessionDir(): string;
|
|
32
34
|
getInstructionFiles(): string[];
|
|
33
35
|
getMemoryDir(): string;
|
|
34
|
-
generateHookConfig(
|
|
36
|
+
generateHookConfig(_pluginRoot: string): HookRegistration;
|
|
35
37
|
readSettings(): Record<string, unknown> | null;
|
|
36
38
|
writeSettings(_settings: Record<string, unknown>): void;
|
|
37
39
|
validateHooks(_pluginRoot: string): DiagnosticResult[];
|
|
38
40
|
checkPluginRegistration(): DiagnosticResult;
|
|
39
41
|
getInstalledVersion(): string;
|
|
40
|
-
configureAllHooks(
|
|
42
|
+
configureAllHooks(pluginRoot: string): string[];
|
|
43
|
+
backupSettings(): string | null;
|
|
41
44
|
setHookPermissions(_pluginRoot: string): string[];
|
|
42
45
|
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
43
46
|
getRoutingInstructions(): string;
|
|
@@ -49,6 +52,15 @@ export declare class CodexAdapter extends BaseAdapter implements HookAdapter {
|
|
|
49
52
|
* platform omits cwd from the wire payload.
|
|
50
53
|
*/
|
|
51
54
|
private getProjectDir;
|
|
55
|
+
getHooksPath(): string;
|
|
56
|
+
private backupFile;
|
|
57
|
+
private readHooksConfig;
|
|
58
|
+
private writeHooksConfig;
|
|
59
|
+
private upsertManagedHookEntry;
|
|
60
|
+
private isExpectedHookEntry;
|
|
61
|
+
private isManagedContextModeEntry;
|
|
62
|
+
private entryContainsManagedCommand;
|
|
63
|
+
private normalizeCommand;
|
|
52
64
|
/**
|
|
53
65
|
* Extract session ID from Codex CLI hook input.
|
|
54
66
|
* Priority: session_id field > fallback to ppid.
|
|
@@ -4,21 +4,91 @@
|
|
|
4
4
|
* Implements HookAdapter for Codex CLI's JSON stdin/stdout paradigm.
|
|
5
5
|
*
|
|
6
6
|
* Codex CLI hook specifics:
|
|
7
|
-
* -
|
|
7
|
+
* - 6 hook events: PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, Stop
|
|
8
8
|
* - Same wire protocol as Claude Code (JSON stdin → stdout)
|
|
9
|
-
* - Config: ~/.codex
|
|
10
|
-
* - Session dir:
|
|
9
|
+
* - Config: $CODEX_HOME or ~/.codex (hooks.json + config.toml)
|
|
10
|
+
* - Session dir: $CODEX_HOME/context-mode/sessions/
|
|
11
11
|
*
|
|
12
12
|
* Hook dispatch is stable in Codex CLI. PreToolUse deny decisions work,
|
|
13
13
|
* while input rewriting remains blocked on upstream updatedInput support.
|
|
14
14
|
* Track: https://github.com/openai/codex/issues/18491
|
|
15
15
|
*/
|
|
16
|
-
import { readFileSync, } from "node:fs";
|
|
17
|
-
import { resolve, dirname } from "node:path";
|
|
16
|
+
import { readFileSync, writeFileSync, accessSync, copyFileSync, constants, mkdirSync, } from "node:fs";
|
|
17
|
+
import { resolve, dirname, join } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
-
import { homedir } from "node:os";
|
|
20
19
|
import { BaseAdapter } from "../base.js";
|
|
21
|
-
import {
|
|
20
|
+
import { resolveCodexConfigDir } from "./paths.js";
|
|
21
|
+
const PRE_TOOL_USE_MATCHER_PATTERN = "local_shell|shell|shell_command|exec_command|container.exec|functions\\.exec_command|Bash|Shell|apply_patch|functions\\.apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__.*__ctx_execute|mcp__.*__ctx_execute_file|mcp__.*__ctx_batch_execute|mcp__.*__ctx_fetch_and_index|mcp__.*__ctx_search|mcp__.*__ctx_index";
|
|
22
|
+
const CODEX_HOOK_COMMANDS = {
|
|
23
|
+
PreToolUse: "context-mode hook codex pretooluse",
|
|
24
|
+
PostToolUse: "context-mode hook codex posttooluse",
|
|
25
|
+
SessionStart: "context-mode hook codex sessionstart",
|
|
26
|
+
PreCompact: "context-mode hook codex precompact",
|
|
27
|
+
UserPromptSubmit: "context-mode hook codex userpromptsubmit",
|
|
28
|
+
Stop: "context-mode hook codex stop",
|
|
29
|
+
};
|
|
30
|
+
const LEGACY_HOOK_PATH_SUFFIXES = {
|
|
31
|
+
PreToolUse: ["hooks/pretooluse.mjs", "hooks/codex/pretooluse.mjs"],
|
|
32
|
+
PostToolUse: ["hooks/posttooluse.mjs", "hooks/codex/posttooluse.mjs"],
|
|
33
|
+
SessionStart: ["hooks/sessionstart.mjs", "hooks/codex/sessionstart.mjs"],
|
|
34
|
+
PreCompact: ["hooks/precompact.mjs", "hooks/codex/precompact.mjs"],
|
|
35
|
+
UserPromptSubmit: ["hooks/userpromptsubmit.mjs", "hooks/codex/userpromptsubmit.mjs"],
|
|
36
|
+
Stop: ["hooks/stop.mjs", "hooks/codex/stop.mjs"],
|
|
37
|
+
};
|
|
38
|
+
function getTomlSection(raw, sectionName) {
|
|
39
|
+
const lines = raw.split(/\r?\n/);
|
|
40
|
+
let inSection = false;
|
|
41
|
+
const body = [];
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const section = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
|
|
44
|
+
if (section) {
|
|
45
|
+
if (inSection)
|
|
46
|
+
break;
|
|
47
|
+
inSection = section[1]?.trim() === sectionName;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inSection)
|
|
51
|
+
body.push(line);
|
|
52
|
+
}
|
|
53
|
+
return inSection ? body.join("\n") : null;
|
|
54
|
+
}
|
|
55
|
+
function hasCodexHooksFeature(raw) {
|
|
56
|
+
const features = getTomlSection(raw, "features");
|
|
57
|
+
return features !== null && /^\s*hooks\s*=\s*true\s*(?:#.*)?$/mi.test(features);
|
|
58
|
+
}
|
|
59
|
+
function hasDeprecatedCodexHooksFeature(raw) {
|
|
60
|
+
const features = getTomlSection(raw, "features");
|
|
61
|
+
return features !== null && /^\s*codex_hooks\s*=\s*true\s*(?:#.*)?$/mi.test(features);
|
|
62
|
+
}
|
|
63
|
+
function ensureCodexHooksFeature(raw) {
|
|
64
|
+
if (hasCodexHooksFeature(raw))
|
|
65
|
+
return { text: raw, changed: false };
|
|
66
|
+
const newline = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
67
|
+
const lines = raw.split(/\r?\n/);
|
|
68
|
+
const featuresIndex = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
|
|
69
|
+
if (featuresIndex === -1) {
|
|
70
|
+
const prefix = raw.length > 0 && !raw.endsWith("\n") ? newline : "";
|
|
71
|
+
return {
|
|
72
|
+
text: `${raw}${prefix}[features]${newline}hooks = true${newline}`,
|
|
73
|
+
changed: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
let endIndex = lines.length;
|
|
77
|
+
for (let i = featuresIndex + 1; i < lines.length; i++) {
|
|
78
|
+
if (/^\s*\[[^\]]+\]\s*(?:#.*)?$/.test(lines[i] ?? "")) {
|
|
79
|
+
endIndex = i;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (let i = featuresIndex + 1; i < endIndex; i++) {
|
|
84
|
+
if (/^\s*hooks\s*=/.test(lines[i] ?? "")) {
|
|
85
|
+
lines[i] = "hooks = true";
|
|
86
|
+
return { text: lines.join(newline), changed: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
lines.splice(featuresIndex + 1, 0, "hooks = true");
|
|
90
|
+
return { text: lines.join(newline), changed: true };
|
|
91
|
+
}
|
|
22
92
|
// ─────────────────────────────────────────────────────────
|
|
23
93
|
// Adapter implementation
|
|
24
94
|
// ─────────────────────────────────────────────────────────
|
|
@@ -31,7 +101,7 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
31
101
|
capabilities = {
|
|
32
102
|
preToolUse: true,
|
|
33
103
|
postToolUse: true,
|
|
34
|
-
preCompact:
|
|
104
|
+
preCompact: true,
|
|
35
105
|
sessionStart: true,
|
|
36
106
|
canModifyArgs: false,
|
|
37
107
|
canModifyOutput: false,
|
|
@@ -124,13 +194,9 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
124
194
|
return {};
|
|
125
195
|
}
|
|
126
196
|
formatPreCompactResponse(response) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
additionalContext: response.context,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
197
|
+
// Codex PreCompact currently accepts only universal hook fields.
|
|
198
|
+
// The hook script stores snapshots in context-mode's DB; SessionStart
|
|
199
|
+
// injects them after compaction.
|
|
134
200
|
return {};
|
|
135
201
|
}
|
|
136
202
|
formatSessionStartResponse(response) {
|
|
@@ -145,26 +211,40 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
145
211
|
return {};
|
|
146
212
|
}
|
|
147
213
|
// ── Configuration ──────────────────────────────────────
|
|
214
|
+
getConfigDir(_projectDir) {
|
|
215
|
+
return resolveCodexConfigDir();
|
|
216
|
+
}
|
|
148
217
|
getSettingsPath() {
|
|
149
|
-
return
|
|
218
|
+
return join(this.getConfigDir(), "config.toml");
|
|
219
|
+
}
|
|
220
|
+
getSessionDir() {
|
|
221
|
+
const dir = join(this.getConfigDir(), "context-mode", "sessions");
|
|
222
|
+
mkdirSync(dir, { recursive: true });
|
|
223
|
+
return dir;
|
|
150
224
|
}
|
|
225
|
+
// C2 narrowing (2026-05): the historical `getSessionDBPath` /
|
|
226
|
+
// `getSessionEventsPath` overrides were removed. Both delegated to the
|
|
227
|
+
// same canonical helpers (`resolveSessionDbPath` / `hashProjectDirCanonical`
|
|
228
|
+
// + `getWorktreeSuffix`) which already normalize the path internally —
|
|
229
|
+
// the explicit `normalizeWorktreePath` here was a no-op. Callers now reach
|
|
230
|
+
// the helpers directly through `adapter.getSessionDir()`.
|
|
151
231
|
getInstructionFiles() {
|
|
152
232
|
// Codex CLI honors AGENTS.md plus an optional override file.
|
|
153
233
|
return ["AGENTS.md", "AGENTS.override.md"];
|
|
154
234
|
}
|
|
155
235
|
getMemoryDir() {
|
|
156
236
|
// Codex uses "memories" (plural), not the default "memory".
|
|
157
|
-
return
|
|
237
|
+
return join(this.getConfigDir(), "memories");
|
|
158
238
|
}
|
|
159
|
-
generateHookConfig(
|
|
239
|
+
generateHookConfig(_pluginRoot) {
|
|
160
240
|
return {
|
|
161
241
|
PreToolUse: [
|
|
162
242
|
{
|
|
163
|
-
matcher:
|
|
243
|
+
matcher: PRE_TOOL_USE_MATCHER_PATTERN,
|
|
164
244
|
hooks: [
|
|
165
245
|
{
|
|
166
246
|
type: "command",
|
|
167
|
-
command:
|
|
247
|
+
command: CODEX_HOOK_COMMANDS.PreToolUse,
|
|
168
248
|
},
|
|
169
249
|
],
|
|
170
250
|
},
|
|
@@ -175,7 +255,7 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
175
255
|
hooks: [
|
|
176
256
|
{
|
|
177
257
|
type: "command",
|
|
178
|
-
command:
|
|
258
|
+
command: CODEX_HOOK_COMMANDS.PostToolUse,
|
|
179
259
|
},
|
|
180
260
|
],
|
|
181
261
|
},
|
|
@@ -186,7 +266,18 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
186
266
|
hooks: [
|
|
187
267
|
{
|
|
188
268
|
type: "command",
|
|
189
|
-
command:
|
|
269
|
+
command: CODEX_HOOK_COMMANDS.SessionStart,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
PreCompact: [
|
|
275
|
+
{
|
|
276
|
+
matcher: "",
|
|
277
|
+
hooks: [
|
|
278
|
+
{
|
|
279
|
+
type: "command",
|
|
280
|
+
command: CODEX_HOOK_COMMANDS.PreCompact,
|
|
190
281
|
},
|
|
191
282
|
],
|
|
192
283
|
},
|
|
@@ -197,7 +288,7 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
197
288
|
hooks: [
|
|
198
289
|
{
|
|
199
290
|
type: "command",
|
|
200
|
-
command:
|
|
291
|
+
command: CODEX_HOOK_COMMANDS.UserPromptSubmit,
|
|
201
292
|
},
|
|
202
293
|
],
|
|
203
294
|
},
|
|
@@ -208,7 +299,7 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
208
299
|
hooks: [
|
|
209
300
|
{
|
|
210
301
|
type: "command",
|
|
211
|
-
command:
|
|
302
|
+
command: CODEX_HOOK_COMMANDS.Stop,
|
|
212
303
|
},
|
|
213
304
|
],
|
|
214
305
|
},
|
|
@@ -235,13 +326,81 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
235
326
|
}
|
|
236
327
|
// ── Diagnostics (doctor) ─────────────────────────────────
|
|
237
328
|
validateHooks(_pluginRoot) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
329
|
+
const results = [];
|
|
330
|
+
try {
|
|
331
|
+
const raw = readFileSync(this.getSettingsPath(), "utf-8");
|
|
332
|
+
const enabled = hasCodexHooksFeature(raw);
|
|
333
|
+
const deprecatedOnly = !enabled && hasDeprecatedCodexHooksFeature(raw);
|
|
334
|
+
results.push({
|
|
335
|
+
check: "Codex hooks feature flag",
|
|
336
|
+
status: enabled ? "pass" : "fail",
|
|
337
|
+
message: enabled
|
|
338
|
+
? `[features].hooks enabled in ${this.getSettingsPath()}`
|
|
339
|
+
: deprecatedOnly
|
|
340
|
+
? `[features].codex_hooks is deprecated; [features].hooks is missing in ${this.getSettingsPath()}`
|
|
341
|
+
: `[features].hooks missing from ${this.getSettingsPath()}`,
|
|
342
|
+
...(enabled ? {} : { fix: "context-mode upgrade" }),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
results.push({
|
|
347
|
+
check: "Codex hooks feature flag",
|
|
348
|
+
status: "warn",
|
|
349
|
+
message: `Could not read ${this.getSettingsPath()}`,
|
|
350
|
+
fix: "context-mode upgrade",
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
const hookConfig = this.readHooksConfig();
|
|
354
|
+
if (!hookConfig.ok) {
|
|
355
|
+
if (hookConfig.reason === "missing") {
|
|
356
|
+
return results.concat([{
|
|
357
|
+
check: "Hooks config",
|
|
358
|
+
status: "fail",
|
|
359
|
+
message: `No readable ${this.getHooksPath()} found`,
|
|
360
|
+
fix: "Copy configs/codex/hooks.json to hooks.json or run context-mode upgrade",
|
|
361
|
+
}]);
|
|
362
|
+
}
|
|
363
|
+
if (hookConfig.reason === "invalid_json") {
|
|
364
|
+
return results.concat([{
|
|
365
|
+
check: "Hooks config",
|
|
366
|
+
status: "fail",
|
|
367
|
+
message: `${this.getHooksPath()} is not valid JSON: ${hookConfig.error}`,
|
|
368
|
+
fix: "Repair hooks.json so it contains valid JSON, then rerun context-mode upgrade if needed",
|
|
369
|
+
}]);
|
|
370
|
+
}
|
|
371
|
+
return results.concat([{
|
|
372
|
+
check: "Hooks config",
|
|
373
|
+
status: "fail",
|
|
374
|
+
message: `Could not read ${this.getHooksPath()}: ${hookConfig.error}`,
|
|
375
|
+
fix: "Check permissions and file accessibility for hooks.json, then rerun context-mode upgrade if needed",
|
|
376
|
+
}]);
|
|
377
|
+
}
|
|
378
|
+
if (!hookConfig.config.hooks) {
|
|
379
|
+
return results.concat([{
|
|
380
|
+
check: "Hooks config",
|
|
381
|
+
status: "fail",
|
|
382
|
+
message: `${this.getHooksPath()} is missing the top-level hooks object`,
|
|
383
|
+
fix: `Update ${this.getHooksPath()} to match configs/codex/hooks.json`,
|
|
384
|
+
}]);
|
|
385
|
+
}
|
|
386
|
+
const expected = this.generateHookConfig("");
|
|
387
|
+
return results.concat(Object.entries(expected).map(([hookName, entries]) => {
|
|
388
|
+
const actualEntries = hookConfig.config.hooks?.[hookName];
|
|
389
|
+
const expectedEntry = entries[0];
|
|
390
|
+
const ok = Array.isArray(actualEntries)
|
|
391
|
+
&& actualEntries.some((entry) => this.isExpectedHookEntry(hookName, entry, expectedEntry));
|
|
392
|
+
const missingStatus = hookName === "PreCompact" ? "warn" : "fail";
|
|
393
|
+
return {
|
|
394
|
+
check: `${hookName} hook`,
|
|
395
|
+
status: ok ? "pass" : missingStatus,
|
|
396
|
+
message: ok
|
|
397
|
+
? `${hookName} hook configured in ${this.getHooksPath()}`
|
|
398
|
+
: hookName === "PreCompact"
|
|
399
|
+
? `${hookName} hook missing or not pointing to context-mode; compaction snapshots require a Codex build that emits PreCompact`
|
|
400
|
+
: `${hookName} hook missing or not pointing to context-mode`,
|
|
401
|
+
fix: ok ? undefined : `Update ${this.getHooksPath()} to match configs/codex/hooks.json`,
|
|
402
|
+
};
|
|
403
|
+
}));
|
|
245
404
|
}
|
|
246
405
|
checkPluginRegistration() {
|
|
247
406
|
// Check for context-mode in [mcp_servers] section of config.toml
|
|
@@ -261,21 +420,21 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
261
420
|
check: "MCP registration",
|
|
262
421
|
status: "fail",
|
|
263
422
|
message: "[mcp_servers] section exists but context-mode not found",
|
|
264
|
-
fix:
|
|
423
|
+
fix: `Add context-mode to [mcp_servers] in ${this.getSettingsPath()}`,
|
|
265
424
|
};
|
|
266
425
|
}
|
|
267
426
|
return {
|
|
268
427
|
check: "MCP registration",
|
|
269
428
|
status: "fail",
|
|
270
429
|
message: "No [mcp_servers] section in config.toml",
|
|
271
|
-
fix:
|
|
430
|
+
fix: `Add [mcp_servers.context-mode] to ${this.getSettingsPath()}`,
|
|
272
431
|
};
|
|
273
432
|
}
|
|
274
433
|
catch {
|
|
275
434
|
return {
|
|
276
435
|
check: "MCP registration",
|
|
277
436
|
status: "warn",
|
|
278
|
-
message:
|
|
437
|
+
message: `Could not read ${this.getSettingsPath()}`,
|
|
279
438
|
};
|
|
280
439
|
}
|
|
281
440
|
}
|
|
@@ -284,9 +443,69 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
284
443
|
return "not installed";
|
|
285
444
|
}
|
|
286
445
|
// ── Upgrade ────────────────────────────────────────────
|
|
287
|
-
configureAllHooks(
|
|
288
|
-
|
|
289
|
-
|
|
446
|
+
configureAllHooks(pluginRoot) {
|
|
447
|
+
const hookConfig = this.readHooksConfig();
|
|
448
|
+
const changes = [];
|
|
449
|
+
let hookFile;
|
|
450
|
+
if (hookConfig.ok) {
|
|
451
|
+
hookFile = hookConfig.config;
|
|
452
|
+
}
|
|
453
|
+
else if (hookConfig.reason === "missing") {
|
|
454
|
+
hookFile = { hooks: {} };
|
|
455
|
+
}
|
|
456
|
+
else if (hookConfig.reason === "invalid_json") {
|
|
457
|
+
const backupPath = this.backupFile(this.getHooksPath(), ".broken");
|
|
458
|
+
changes.push(`Backed up malformed Codex hooks to ${backupPath}`);
|
|
459
|
+
hookFile = { hooks: {} };
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
throw new Error(`Failed to update ${this.getHooksPath()}: ${hookConfig.error}`);
|
|
463
|
+
}
|
|
464
|
+
const hooks = hookFile.hooks && typeof hookFile.hooks === "object" && !Array.isArray(hookFile.hooks)
|
|
465
|
+
? hookFile.hooks
|
|
466
|
+
: {};
|
|
467
|
+
const desiredHooks = this.generateHookConfig(pluginRoot);
|
|
468
|
+
for (const [hookName, entries] of Object.entries(desiredHooks)) {
|
|
469
|
+
this.upsertManagedHookEntry(hooks, hookName, entries[0], changes);
|
|
470
|
+
}
|
|
471
|
+
if (changes.length > 0) {
|
|
472
|
+
hookFile.hooks = hooks;
|
|
473
|
+
this.writeHooksConfig(hookFile);
|
|
474
|
+
changes.push(`Wrote native Codex hooks to ${this.getHooksPath()}`);
|
|
475
|
+
}
|
|
476
|
+
const settingsPath = this.getSettingsPath();
|
|
477
|
+
let settingsRaw = "";
|
|
478
|
+
try {
|
|
479
|
+
settingsRaw = readFileSync(settingsPath, "utf-8");
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
settingsRaw = "";
|
|
483
|
+
}
|
|
484
|
+
const enabledSettings = ensureCodexHooksFeature(settingsRaw);
|
|
485
|
+
if (enabledSettings.changed) {
|
|
486
|
+
const newline = enabledSettings.text.includes("\r\n") ? "\r\n" : "\n";
|
|
487
|
+
const text = enabledSettings.text.endsWith("\n")
|
|
488
|
+
? enabledSettings.text
|
|
489
|
+
: `${enabledSettings.text}${newline}`;
|
|
490
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
491
|
+
writeFileSync(settingsPath, text, "utf-8");
|
|
492
|
+
changes.push("Enabled Codex hooks feature flag");
|
|
493
|
+
}
|
|
494
|
+
return changes;
|
|
495
|
+
}
|
|
496
|
+
backupSettings() {
|
|
497
|
+
let firstBackupPath = null;
|
|
498
|
+
for (const settingsPath of [this.getHooksPath(), this.getSettingsPath()]) {
|
|
499
|
+
try {
|
|
500
|
+
accessSync(settingsPath, constants.R_OK);
|
|
501
|
+
const backupPath = this.backupFile(settingsPath);
|
|
502
|
+
firstBackupPath ??= backupPath;
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return firstBackupPath;
|
|
290
509
|
}
|
|
291
510
|
setHookPermissions(_pluginRoot) {
|
|
292
511
|
// Hook permissions are set during plugin install
|
|
@@ -316,6 +535,87 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
316
535
|
getProjectDir(input) {
|
|
317
536
|
return input.cwd ?? process.env.CODEX_PROJECT_DIR ?? process.cwd();
|
|
318
537
|
}
|
|
538
|
+
getHooksPath() {
|
|
539
|
+
return join(this.getConfigDir(), "hooks.json");
|
|
540
|
+
}
|
|
541
|
+
backupFile(filePath, suffix = "") {
|
|
542
|
+
const backupPath = suffix
|
|
543
|
+
? `${filePath}${suffix}-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`
|
|
544
|
+
: `${filePath}.bak`;
|
|
545
|
+
copyFileSync(filePath, backupPath);
|
|
546
|
+
return backupPath;
|
|
547
|
+
}
|
|
548
|
+
readHooksConfig() {
|
|
549
|
+
const hooksPath = this.getHooksPath();
|
|
550
|
+
try {
|
|
551
|
+
return { ok: true, config: JSON.parse(readFileSync(hooksPath, "utf-8")) };
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
556
|
+
? String(error.code ?? "")
|
|
557
|
+
: "";
|
|
558
|
+
if (code === "ENOENT") {
|
|
559
|
+
return { ok: false, reason: "missing" };
|
|
560
|
+
}
|
|
561
|
+
if (error instanceof SyntaxError) {
|
|
562
|
+
return { ok: false, reason: "invalid_json", error: message };
|
|
563
|
+
}
|
|
564
|
+
return { ok: false, reason: "read_error", error: message };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
writeHooksConfig(config) {
|
|
568
|
+
const hooksPath = this.getHooksPath();
|
|
569
|
+
mkdirSync(dirname(hooksPath), { recursive: true });
|
|
570
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
571
|
+
}
|
|
572
|
+
upsertManagedHookEntry(hooks, hookName, expectedEntry, changes) {
|
|
573
|
+
const currentEntries = Array.isArray(hooks[hookName]) ? [...hooks[hookName]] : [];
|
|
574
|
+
const managedIndices = currentEntries
|
|
575
|
+
.map((entry, index) => this.isManagedContextModeEntry(hookName, entry) ? index : -1)
|
|
576
|
+
.filter((index) => index >= 0);
|
|
577
|
+
if (managedIndices.length === 0) {
|
|
578
|
+
currentEntries.push(expectedEntry);
|
|
579
|
+
hooks[hookName] = currentEntries;
|
|
580
|
+
changes.push(`Added ${hookName} hook`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const primaryIndex = managedIndices[0];
|
|
584
|
+
if (JSON.stringify(currentEntries[primaryIndex]) !== JSON.stringify(expectedEntry)) {
|
|
585
|
+
currentEntries[primaryIndex] = expectedEntry;
|
|
586
|
+
changes.push(`Updated ${hookName} hook`);
|
|
587
|
+
}
|
|
588
|
+
for (const duplicateIndex of managedIndices.slice(1).reverse()) {
|
|
589
|
+
currentEntries.splice(duplicateIndex, 1);
|
|
590
|
+
changes.push(`Removed duplicate ${hookName} context-mode hook`);
|
|
591
|
+
}
|
|
592
|
+
hooks[hookName] = currentEntries;
|
|
593
|
+
}
|
|
594
|
+
isExpectedHookEntry(hookName, entry, expectedEntry) {
|
|
595
|
+
if (!entry || typeof entry !== "object")
|
|
596
|
+
return false;
|
|
597
|
+
if (hookName === "PreToolUse" && entry.matcher !== expectedEntry.matcher) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
return this.entryContainsManagedCommand(hookName, entry);
|
|
601
|
+
}
|
|
602
|
+
isManagedContextModeEntry(hookName, entry) {
|
|
603
|
+
if (!entry || typeof entry !== "object")
|
|
604
|
+
return false;
|
|
605
|
+
return this.entryContainsManagedCommand(hookName, entry);
|
|
606
|
+
}
|
|
607
|
+
entryContainsManagedCommand(hookName, entry) {
|
|
608
|
+
const normalizedCommands = (Array.isArray(entry.hooks) ? entry.hooks : [])
|
|
609
|
+
.map((hook) => this.normalizeCommand(hook.command))
|
|
610
|
+
.filter((command) => command.length > 0);
|
|
611
|
+
const expectedCliCommand = this.normalizeCommand(CODEX_HOOK_COMMANDS[hookName] ?? "");
|
|
612
|
+
const legacySuffixes = LEGACY_HOOK_PATH_SUFFIXES[hookName] ?? [];
|
|
613
|
+
return normalizedCommands.some((command) => command.includes(expectedCliCommand)
|
|
614
|
+
|| legacySuffixes.some((suffix) => command.includes(suffix)));
|
|
615
|
+
}
|
|
616
|
+
normalizeCommand(command) {
|
|
617
|
+
return (command ?? "").replace(/\\/g, "/");
|
|
618
|
+
}
|
|
319
619
|
/**
|
|
320
620
|
* Extract session ID from Codex CLI hook input.
|
|
321
621
|
* Priority: session_id field > fallback to ppid.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveCodexConfigDir(): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export function resolveCodexConfigDir() {
|
|
4
|
+
const envVal = process.env.CODEX_HOME;
|
|
5
|
+
if (envVal) {
|
|
6
|
+
if (envVal.startsWith("~")) {
|
|
7
|
+
return resolve(homedir(), envVal.replace(/^~[/\\]?/, ""));
|
|
8
|
+
}
|
|
9
|
+
return resolve(envVal);
|
|
10
|
+
}
|
|
11
|
+
return resolve(homedir(), ".codex");
|
|
12
|
+
}
|
|
@@ -43,6 +43,12 @@ export declare class CursorAdapter extends BaseAdapter implements HookAdapter {
|
|
|
43
43
|
readSettings(): Record<string, unknown> | null;
|
|
44
44
|
writeSettings(settings: Record<string, unknown>): void;
|
|
45
45
|
validateHooks(_pluginRoot: string): DiagnosticResult[];
|
|
46
|
+
/**
|
|
47
|
+
* Detects context-mode plugin installations under Cursor's plugin directories.
|
|
48
|
+
* Returns absolute paths to any `.cursor-plugin/plugin.json` files whose
|
|
49
|
+
* `name` matches `context-mode`.
|
|
50
|
+
*/
|
|
51
|
+
private detectPluginInstalls;
|
|
46
52
|
checkPluginRegistration(): DiagnosticResult;
|
|
47
53
|
getInstalledVersion(): string;
|
|
48
54
|
configureAllHooks(_pluginRoot: string): string[];
|