context-mode 1.0.106 → 1.0.107
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/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/adapters/copilot-base.d.ts +3 -3
- package/build/adapters/cursor/hooks.js +8 -0
- package/build/adapters/cursor/index.js +4 -1
- package/build/adapters/gemini-cli/hooks.d.ts +6 -1
- package/build/adapters/gemini-cli/hooks.js +7 -1
- package/build/adapters/gemini-cli/index.js +12 -0
- package/build/adapters/kiro/hooks.js +4 -0
- package/build/adapters/kiro/index.d.ts +9 -2
- package/build/adapters/kiro/index.js +49 -27
- package/build/adapters/opencode/index.js +6 -0
- package/build/adapters/qwen-code/index.js +18 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +0 -4
- package/build/adapters/vscode-copilot/hooks.js +6 -6
- package/build/cli.js +1 -0
- package/build/openclaw/mcp-tools.d.ts +54 -0
- package/build/openclaw/mcp-tools.js +198 -0
- package/build/openclaw-plugin.d.ts +9 -0
- package/build/openclaw-plugin.js +132 -16
- package/build/opencode-plugin.d.ts +29 -4
- package/build/opencode-plugin.js +154 -7
- package/build/pi-extension.js +123 -29
- package/build/server.d.ts +1 -0
- package/build/server.js +19 -1
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +46 -1
- package/cli.bundle.mjs +125 -125
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +13 -1
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/sessionstart.mjs +3 -1
- package/hooks/vscode-copilot/sessionstart.mjs +13 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +68 -68
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection from process env vars.
|
|
3
|
+
*
|
|
4
|
+
* Each supported platform sets a distinctive env var when invoking
|
|
5
|
+
* hook scripts; we use those to pick the correct tool-namer prefix
|
|
6
|
+
* and routing block. Falls back to "claude-code" when nothing matches
|
|
7
|
+
* so existing CC behavior is preserved.
|
|
8
|
+
*
|
|
9
|
+
* SINGLE SOURCE OF TRUTH: this table mirrors `PLATFORM_ENV_VARS` in
|
|
10
|
+
* `src/adapters/detect.ts:33-77`. Every entry has been verified against
|
|
11
|
+
* the platform's own runtime source code (full audit May 2026, see
|
|
12
|
+
* git blame). DO NOT add platform env vars here that aren't also in
|
|
13
|
+
* detect.ts — the two MUST stay in lock-step or detection will diverge
|
|
14
|
+
* between MCP-server-side and hook-script-side.
|
|
15
|
+
*
|
|
16
|
+
* Order matters — same as detect.ts. Forks listed BEFORE the fork's
|
|
17
|
+
* parent so collision detection works (e.g. cursor BEFORE vscode-copilot
|
|
18
|
+
* because Cursor inherits VSCODE_PID as a fork; antigravity BEFORE
|
|
19
|
+
* vscode-copilot for the same reason).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Mirror of `PLATFORM_ENV_VARS` in src/adapters/detect.ts:33-77.
|
|
23
|
+
// Keep in lock-step. If you change one, change the other.
|
|
24
|
+
const PLATFORM_ENV_VARS_MIRROR = [
|
|
25
|
+
["claude-code", ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]],
|
|
26
|
+
["antigravity", ["ANTIGRAVITY_CLI_ALIAS"]],
|
|
27
|
+
["cursor", ["CURSOR_TRACE_ID", "CURSOR_CLI"]],
|
|
28
|
+
["kilo", ["KILO_PID"]],
|
|
29
|
+
["opencode", ["OPENCODE", "OPENCODE_PID"]],
|
|
30
|
+
["zed", ["ZED_SESSION_ID", "ZED_TERM"]],
|
|
31
|
+
["codex", ["CODEX_THREAD_ID", "CODEX_CI"]],
|
|
32
|
+
["gemini-cli", ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]],
|
|
33
|
+
["vscode-copilot", ["VSCODE_PID", "VSCODE_CWD"]],
|
|
34
|
+
["jetbrains-copilot", ["IDEA_INITIAL_DIRECTORY"]],
|
|
35
|
+
["qwen-code", ["QWEN_PROJECT_DIR"]],
|
|
36
|
+
["pi", ["PI_PROJECT_DIR"]],
|
|
37
|
+
// openclaw — no auto-set process env vars; falls through to default
|
|
38
|
+
// kiro — no auto-set process env vars; falls through to default
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export function detectPlatformFromEnv(env = process.env) {
|
|
42
|
+
for (const [platform, vars] of PLATFORM_ENV_VARS_MIRROR) {
|
|
43
|
+
if (vars.some((v) => env[v])) return platform;
|
|
44
|
+
}
|
|
45
|
+
return "claude-code";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Re-exported for tests so they can assert against the same canonical table.
|
|
49
|
+
export { PLATFORM_ENV_VARS_MIRROR };
|
package/hooks/core/routing.mjs
CHANGED
|
@@ -133,13 +133,25 @@ export async function initSecurity(buildDir) {
|
|
|
133
133
|
* - VS Code Copilot: run_in_terminal (command field), read_file, run_vs_code_task
|
|
134
134
|
*/
|
|
135
135
|
const TOOL_ALIASES = {
|
|
136
|
-
// Gemini CLI
|
|
136
|
+
// Gemini CLI / Qwen Code (share native tool names — Qwen is Gemini fork:
|
|
137
|
+
// refs/platforms/qwen-code/packages/core/src/tools/tool-names.ts)
|
|
137
138
|
"run_shell_command": "Bash",
|
|
138
139
|
"read_file": "Read",
|
|
139
140
|
"read_many_files": "Read",
|
|
140
141
|
"grep_search": "Grep",
|
|
141
142
|
"search_file_content": "Grep",
|
|
142
143
|
"web_fetch": "WebFetch",
|
|
144
|
+
// Qwen Code additional tool names (no routing branch yet but normalized
|
|
145
|
+
// so future routing logic works without per-platform fallback):
|
|
146
|
+
"write_file": "Write",
|
|
147
|
+
"edit": "Edit",
|
|
148
|
+
"glob": "Glob",
|
|
149
|
+
"todo_write": "TodoWrite",
|
|
150
|
+
"ask_user_question": "AskUserQuestion",
|
|
151
|
+
"list_directory": "LS",
|
|
152
|
+
"save_memory": "Memory",
|
|
153
|
+
"skill": "Skill",
|
|
154
|
+
"exit_plan_mode": "ExitPlanMode",
|
|
143
155
|
// OpenCode
|
|
144
156
|
"bash": "Bash",
|
|
145
157
|
"view": "Read",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
import "../ensure-deps.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Cursor afterAgentResponse hook — fires once Cursor finishes streaming
|
|
6
|
+
* the assistant turn back to the user (per Cursor v1 hook docs:
|
|
7
|
+
* https://cursor.com/docs/agent/hooks#afteragentresponse).
|
|
8
|
+
*
|
|
9
|
+
* Input: { "conversation_id": "...", "generation_id": "...", "text": "<final assistant message>" }
|
|
10
|
+
* Output: { "additional_context": "" } (Cursor rejects empty stdout, so emit a no-op payload)
|
|
11
|
+
*
|
|
12
|
+
* The hook records the agent_response event so session analytics + the
|
|
13
|
+
* resume directive can reconstruct the last assistant turn. Mirrors
|
|
14
|
+
* stop.mjs (turn lifecycle telemetry) — afterAgentResponse fires
|
|
15
|
+
* BEFORE stop, so we capture the produced text here while stop captures
|
|
16
|
+
* loop_count + final status.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
readStdin,
|
|
21
|
+
parseStdin,
|
|
22
|
+
getSessionId,
|
|
23
|
+
getSessionDBPath,
|
|
24
|
+
getInputProjectDir,
|
|
25
|
+
CURSOR_OPTS,
|
|
26
|
+
} from "../session-helpers.mjs";
|
|
27
|
+
import { dirname } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { createSessionLoaders } from "../session-loaders.mjs";
|
|
30
|
+
|
|
31
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const { loadSessionDB } = createSessionLoaders(HOOK_DIR);
|
|
33
|
+
const OPTS = CURSOR_OPTS;
|
|
34
|
+
|
|
35
|
+
// Truncate large assistant responses before persisting — keeps the
|
|
36
|
+
// session_events table from ballooning when an agent emits long output.
|
|
37
|
+
const MAX_TEXT_BYTES = 8 * 1024;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readStdin();
|
|
41
|
+
const input = parseStdin(raw);
|
|
42
|
+
const projectDir = getInputProjectDir(input, CURSOR_OPTS);
|
|
43
|
+
|
|
44
|
+
if (projectDir && !process.env.CURSOR_CWD) {
|
|
45
|
+
process.env.CURSOR_CWD = projectDir;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const text = typeof input.text === "string" ? input.text : "";
|
|
49
|
+
const truncated = text.length > MAX_TEXT_BYTES
|
|
50
|
+
? text.slice(0, MAX_TEXT_BYTES)
|
|
51
|
+
: text;
|
|
52
|
+
|
|
53
|
+
const { SessionDB } = await loadSessionDB();
|
|
54
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
55
|
+
const db = new SessionDB({ dbPath });
|
|
56
|
+
const sessionId = getSessionId(input, OPTS);
|
|
57
|
+
|
|
58
|
+
db.ensureSession(sessionId, projectDir);
|
|
59
|
+
db.insertEvent(sessionId, {
|
|
60
|
+
type: "agent_response",
|
|
61
|
+
generation_id: input.generation_id ?? null,
|
|
62
|
+
text_bytes: text.length,
|
|
63
|
+
truncated: text.length > MAX_TEXT_BYTES,
|
|
64
|
+
text: truncated,
|
|
65
|
+
}, "AfterAgentResponse");
|
|
66
|
+
|
|
67
|
+
db.close();
|
|
68
|
+
} catch {
|
|
69
|
+
// Cursor treats stderr as hook failure; swallow and continue.
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Cursor rejects empty stdout as "no valid response", so emit a no-op
|
|
73
|
+
// additional_context payload (same convention as sessionstart.mjs).
|
|
74
|
+
process.stdout.write(JSON.stringify({ additional_context: "" }) + "\n");
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
import "../ensure-deps.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Gemini CLI BeforeAgent hook — UserPromptSubmit equivalent.
|
|
6
|
+
*
|
|
7
|
+
* Captures every user prompt so the LLM can resume from the exact point
|
|
8
|
+
* the user left off after compact / resume. Mirrors hooks/userpromptsubmit.mjs
|
|
9
|
+
* but reads `input.prompt` (Gemini wire shape, types.ts:547-549) and emits
|
|
10
|
+
* `hookSpecificOutput.hookEventName: "BeforeAgent"` (types.ts:554-559) so
|
|
11
|
+
* Gemini's hookRunner appends additionalContext to the prompt
|
|
12
|
+
* (hookRunner.ts:183-197).
|
|
13
|
+
*
|
|
14
|
+
* Must be fast (<10ms). Single SQLite write.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
readStdin,
|
|
19
|
+
parseStdin,
|
|
20
|
+
getSessionId,
|
|
21
|
+
getSessionDBPath,
|
|
22
|
+
getInputProjectDir,
|
|
23
|
+
GEMINI_OPTS,
|
|
24
|
+
} from "../session-helpers.mjs";
|
|
25
|
+
import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
|
|
26
|
+
import { dirname } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
|
|
29
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
31
|
+
const OPTS = GEMINI_OPTS;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const raw = await readStdin();
|
|
35
|
+
const input = parseStdin(raw);
|
|
36
|
+
const projectDir = getInputProjectDir(input, OPTS);
|
|
37
|
+
|
|
38
|
+
const prompt = input.prompt ?? input.message ?? "";
|
|
39
|
+
const trimmed = (prompt || "").trim();
|
|
40
|
+
|
|
41
|
+
// Skip system-generated messages — only capture genuine user prompts.
|
|
42
|
+
const isSystemMessage = trimmed.startsWith("<task-notification>")
|
|
43
|
+
|| trimmed.startsWith("<system-reminder>")
|
|
44
|
+
|| trimmed.startsWith("<context_guidance>")
|
|
45
|
+
|| trimmed.startsWith("<tool-result>");
|
|
46
|
+
|
|
47
|
+
if (trimmed.length > 0 && !isSystemMessage) {
|
|
48
|
+
const { SessionDB } = await loadSessionDB();
|
|
49
|
+
const { extractUserEvents } = await loadExtract();
|
|
50
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
51
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
52
|
+
const db = new SessionDB({ dbPath });
|
|
53
|
+
const sessionId = getSessionId(input, OPTS);
|
|
54
|
+
|
|
55
|
+
db.ensureSession(sessionId, projectDir);
|
|
56
|
+
|
|
57
|
+
// 1. Always save the raw prompt.
|
|
58
|
+
const promptEvent = {
|
|
59
|
+
type: "user_prompt",
|
|
60
|
+
category: "user-prompt",
|
|
61
|
+
data: prompt,
|
|
62
|
+
priority: 1,
|
|
63
|
+
};
|
|
64
|
+
const promptAttributions = attributeAndInsertEvents(
|
|
65
|
+
db, sessionId, [promptEvent], input, projectDir, "BeforeAgent", resolveProjectAttributions,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// 2. Extract decision/role/intent/data from user message.
|
|
69
|
+
const userEvents = extractUserEvents(trimmed);
|
|
70
|
+
const savedLastKnown = promptAttributions[0]?.projectDir || null;
|
|
71
|
+
const sessionStats = db.getSessionStats(sessionId);
|
|
72
|
+
const lastKnownProjectDir = typeof db.getLatestAttributedProjectDir === "function"
|
|
73
|
+
? db.getLatestAttributedProjectDir(sessionId)
|
|
74
|
+
: null;
|
|
75
|
+
const userAttributions = resolveProjectAttributions(userEvents, {
|
|
76
|
+
sessionOriginDir: sessionStats?.project_dir || projectDir,
|
|
77
|
+
inputProjectDir: projectDir,
|
|
78
|
+
workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
|
|
79
|
+
lastKnownProjectDir: savedLastKnown || lastKnownProjectDir,
|
|
80
|
+
});
|
|
81
|
+
for (let i = 0; i < userEvents.length; i++) {
|
|
82
|
+
db.insertEvent(sessionId, userEvents[i], "BeforeAgent", userAttributions[i]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
db.close();
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// BeforeAgent must never block the session — silent fallback.
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Emit empty additionalContext so Gemini's hookRunner treats the response as
|
|
92
|
+
// metadata (no prompt mutation). Matches the SessionStart hook's structured-
|
|
93
|
+
// output convention (sessionstart.mjs:130-135).
|
|
94
|
+
console.log(JSON.stringify({
|
|
95
|
+
hookSpecificOutput: {
|
|
96
|
+
hookEventName: "BeforeAgent",
|
|
97
|
+
additionalContext: "",
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
import "../ensure-deps.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Kiro CLI agentSpawn hook for context-mode.
|
|
6
|
+
*
|
|
7
|
+
* agentSpawn is the Kiro semantic equivalent of Claude Code's SessionStart:
|
|
8
|
+
* fires once when an agent loads. We use it to inject the routing block and
|
|
9
|
+
* (on resume/compact) the session-resume directive.
|
|
10
|
+
*
|
|
11
|
+
* Source: https://kiro.dev/docs/cli/hooks
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createRoutingBlock } from "../routing-block.mjs";
|
|
15
|
+
import { createToolNamer } from "../core/tool-naming.mjs";
|
|
16
|
+
|
|
17
|
+
const toolNamer = createToolNamer("kiro");
|
|
18
|
+
const ROUTING_BLOCK = createRoutingBlock(toolNamer);
|
|
19
|
+
import {
|
|
20
|
+
writeSessionEventsFile,
|
|
21
|
+
buildSessionDirective,
|
|
22
|
+
getSessionEvents,
|
|
23
|
+
} from "../session-directive.mjs";
|
|
24
|
+
import {
|
|
25
|
+
readStdin,
|
|
26
|
+
parseStdin,
|
|
27
|
+
getSessionId,
|
|
28
|
+
getSessionDBPath,
|
|
29
|
+
getSessionEventsPath,
|
|
30
|
+
getCleanupFlagPath,
|
|
31
|
+
getInputProjectDir,
|
|
32
|
+
KIRO_OPTS,
|
|
33
|
+
} from "../session-helpers.mjs";
|
|
34
|
+
import { createSessionLoaders } from "../session-loaders.mjs";
|
|
35
|
+
import { unlinkSync } from "node:fs";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
|
|
38
|
+
const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
39
|
+
const { loadSessionDB } = createSessionLoaders(HOOK_DIR);
|
|
40
|
+
const OPTS = KIRO_OPTS;
|
|
41
|
+
|
|
42
|
+
let additionalContext = ROUTING_BLOCK;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readStdin();
|
|
46
|
+
const input = parseStdin(raw);
|
|
47
|
+
// Kiro stdin shape mirrors codex/CC: { source, cwd, ... }. Default startup
|
|
48
|
+
// when no source provided (single-shot agent spawn).
|
|
49
|
+
const source = input.source ?? "startup";
|
|
50
|
+
const projectDir = getInputProjectDir(input, OPTS);
|
|
51
|
+
|
|
52
|
+
if (source === "compact" || source === "resume") {
|
|
53
|
+
const { SessionDB } = await loadSessionDB();
|
|
54
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
55
|
+
const db = new SessionDB({ dbPath });
|
|
56
|
+
|
|
57
|
+
if (source === "compact") {
|
|
58
|
+
const sessionId = getSessionId(input, OPTS);
|
|
59
|
+
const resume = db.getResume(sessionId);
|
|
60
|
+
if (resume && !resume.consumed) {
|
|
61
|
+
db.markResumeConsumed(sessionId);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sessionId = getSessionId(input, OPTS);
|
|
68
|
+
const events = sessionId ? getSessionEvents(db, sessionId) : [];
|
|
69
|
+
if (events.length > 0) {
|
|
70
|
+
const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
|
|
71
|
+
additionalContext += buildSessionDirective(source, eventMeta, toolNamer);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
db.close();
|
|
75
|
+
} else if (source === "startup") {
|
|
76
|
+
const { SessionDB } = await loadSessionDB();
|
|
77
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
78
|
+
const db = new SessionDB({ dbPath });
|
|
79
|
+
try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
|
|
80
|
+
|
|
81
|
+
db.cleanupOldSessions(7);
|
|
82
|
+
db.db.exec(`DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)`);
|
|
83
|
+
|
|
84
|
+
const sessionId = getSessionId(input, OPTS);
|
|
85
|
+
db.ensureSession(sessionId, projectDir);
|
|
86
|
+
|
|
87
|
+
db.close();
|
|
88
|
+
}
|
|
89
|
+
// clear => routing block only
|
|
90
|
+
} catch {
|
|
91
|
+
// Swallow errors — hook must not fail
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Kiro CLI agentSpawn JSON output shape mirrors CC SessionStart.
|
|
95
|
+
process.stdout.write(JSON.stringify({
|
|
96
|
+
hookSpecificOutput: { hookEventName: "agentSpawn", additionalContext },
|
|
97
|
+
}) + "\n");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
import "../ensure-deps.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Kiro CLI UserPromptSubmit hook — capture user prompts for continuity.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors hooks/codex/userpromptsubmit.mjs (same json-stdio paradigm).
|
|
8
|
+
* Kiro stdin: { hook_event_name: "userPromptSubmit", cwd, prompt }
|
|
9
|
+
*
|
|
10
|
+
* Source: https://kiro.dev/docs/cli/hooks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
readStdin,
|
|
15
|
+
parseStdin,
|
|
16
|
+
getSessionId,
|
|
17
|
+
getSessionDBPath,
|
|
18
|
+
getInputProjectDir,
|
|
19
|
+
KIRO_OPTS,
|
|
20
|
+
} from "../session-helpers.mjs";
|
|
21
|
+
import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
27
|
+
const OPTS = KIRO_OPTS;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const raw = await readStdin();
|
|
31
|
+
const input = parseStdin(raw);
|
|
32
|
+
const projectDir = getInputProjectDir(input, OPTS);
|
|
33
|
+
|
|
34
|
+
const prompt = input.prompt ?? input.message ?? "";
|
|
35
|
+
const trimmed = (prompt || "").trim();
|
|
36
|
+
|
|
37
|
+
const isSystemMessage = trimmed.startsWith("<task-notification>")
|
|
38
|
+
|| trimmed.startsWith("<system-reminder>")
|
|
39
|
+
|| trimmed.startsWith("<context_guidance>")
|
|
40
|
+
|| trimmed.startsWith("<tool-result>");
|
|
41
|
+
|
|
42
|
+
if (trimmed.length > 0 && !isSystemMessage) {
|
|
43
|
+
const { SessionDB } = await loadSessionDB();
|
|
44
|
+
const { extractUserEvents } = await loadExtract();
|
|
45
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
46
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
47
|
+
const db = new SessionDB({ dbPath });
|
|
48
|
+
const sessionId = getSessionId(input, OPTS);
|
|
49
|
+
|
|
50
|
+
db.ensureSession(sessionId, projectDir);
|
|
51
|
+
|
|
52
|
+
const promptEvent = {
|
|
53
|
+
type: "user_prompt",
|
|
54
|
+
category: "user-prompt",
|
|
55
|
+
data: prompt,
|
|
56
|
+
priority: 1,
|
|
57
|
+
};
|
|
58
|
+
const promptAttributions = attributeAndInsertEvents(
|
|
59
|
+
db, sessionId, [promptEvent], input, projectDir, "UserPromptSubmit", resolveProjectAttributions,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const userEvents = extractUserEvents(trimmed);
|
|
63
|
+
const savedLastKnown = promptAttributions[0]?.projectDir || null;
|
|
64
|
+
const sessionStats = db.getSessionStats(sessionId);
|
|
65
|
+
const lastKnownProjectDir = typeof db.getLatestAttributedProjectDir === "function"
|
|
66
|
+
? db.getLatestAttributedProjectDir(sessionId)
|
|
67
|
+
: null;
|
|
68
|
+
const userAttributions = resolveProjectAttributions(userEvents, {
|
|
69
|
+
sessionOriginDir: sessionStats?.project_dir || projectDir,
|
|
70
|
+
inputProjectDir: projectDir,
|
|
71
|
+
workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
|
|
72
|
+
lastKnownProjectDir: savedLastKnown || lastKnownProjectDir,
|
|
73
|
+
});
|
|
74
|
+
for (let i = 0; i < userEvents.length; i++) {
|
|
75
|
+
db.insertEvent(sessionId, userEvents[i], "UserPromptSubmit", userAttributions[i]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
db.close();
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Kiro hooks must not block the session.
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Kiro CLI userPromptSubmit accepts JSON output for additionalContext (see docs).
|
|
85
|
+
// We don't inject context here — that's the agentSpawn hook's job.
|
|
86
|
+
process.stdout.write(JSON.stringify({
|
|
87
|
+
hookSpecificOutput: { hookEventName: "userPromptSubmit", additionalContext: "" },
|
|
88
|
+
}) + "\n");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function s(t){return t==null?"":String(t)}function f(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function b(t){let{tool_name:e,tool_input:o,tool_response:i}=t,
|
|
2
|
-
response: ${s(i)}`:"";return[{type:"mcp",category:"mcp",data:s(`${
|
|
1
|
+
function s(t){return t==null?"":String(t)}function f(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function b(t){let{tool_name:e,tool_input:o,tool_response:i}=t,n=[];if(e==="Read"){let r=String(o.file_path??"");return(/(?:CLAUDE|AGENTS(?:\.override)?|GEMINI|QWEN|KIRO)\.md$/i.test(r)||/\/copilot-instructions\.md$/i.test(r)||/\/context-mode\.mdc$/i.test(r)||/\.claude[\\/]/i.test(r)||/[\\/]memor(?:y|ies)[\\/][^\\/]+\.md$/i.test(r))&&(n.push({type:"rule",category:"rule",data:s(r),priority:1}),i&&i.length>0&&n.push({type:"rule_content",category:"rule",data:s(i),priority:1})),n.push({type:"file_read",category:"file",data:s(r),priority:1}),n}if(e==="Edit"){let r=String(o.file_path??"");return n.push({type:"file_edit",category:"file",data:s(r),priority:1}),n}if(e==="NotebookEdit"){let r=String(o.notebook_path??"");return n.push({type:"file_edit",category:"file",data:s(r),priority:1}),n}if(e==="Write"){let r=String(o.file_path??"");return n.push({type:"file_write",category:"file",data:s(r),priority:1}),n}if(e==="Glob"){let r=String(o.pattern??"");return n.push({type:"file_glob",category:"file",data:s(r),priority:3}),n}if(e==="Grep"){let r=String(o.pattern??""),a=String(o.path??"");return n.push({type:"file_search",category:"file",data:s(`${r} in ${a}`),priority:3}),n}return n}function y(t){if(t.tool_name!=="Bash")return[];let o=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!o)return[];let i=o[2]??o[3]??o[4]??"";return[{type:"cwd",category:"cwd",data:s(i),priority:2}]}function h(t){let{tool_name:e,tool_input:o,tool_response:i,tool_output:n}=t,r=String(i??""),a=n?.isError===!0;return!(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(r))&&!a?[]:[{type:"error_tool",category:"error",data:s(r),priority:2}]}var _=[{pattern:/\bgit\s+checkout\b/,operation:"branch"},{pattern:/\bgit\s+commit\b/,operation:"commit"},{pattern:/\bgit\s+merge\s+\S+/,operation:"merge"},{pattern:/\bgit\s+rebase\b/,operation:"rebase"},{pattern:/\bgit\s+stash\b/,operation:"stash"},{pattern:/\bgit\s+push\b/,operation:"push"},{pattern:/\bgit\s+pull\b/,operation:"pull"},{pattern:/\bgit\s+log\b/,operation:"log"},{pattern:/\bgit\s+diff\b/,operation:"diff"},{pattern:/\bgit\s+status\b/,operation:"status"},{pattern:/\bgit\s+branch\b/,operation:"branch"},{pattern:/\bgit\s+reset\b/,operation:"reset"},{pattern:/\bgit\s+add\b/,operation:"add"},{pattern:/\bgit\s+cherry-pick\b/,operation:"cherry-pick"},{pattern:/\bgit\s+tag\b/,operation:"tag"},{pattern:/\bgit\s+fetch\b/,operation:"fetch"},{pattern:/\bgit\s+clone\b/,operation:"clone"},{pattern:/\bgit\s+worktree\b/,operation:"worktree"}];function m(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),o=_.find(i=>i.pattern.test(e));return o?[{type:"git",category:"git",data:s(o.operation),priority:2}]:[]}function S(t){return new Set(["TodoWrite","TaskCreate","TaskUpdate"]).has(t.tool_name)?[{type:t.tool_name==="TaskUpdate"?"task_update":t.tool_name==="TaskCreate"?"task_create":"task",category:"task",data:s(JSON.stringify(t.tool_input)),priority:1}]:[]}function E(t){if(t.tool_name==="EnterPlanMode")return[{type:"plan_enter",category:"plan",data:"entered plan mode",priority:2}];if(t.tool_name==="ExitPlanMode"){let e=[],o=t.tool_input.allowedPrompts,i=Array.isArray(o)&&o.length>0?`exited plan mode (allowed: ${f(o.map(r=>typeof r=="object"&&r!==null&&"prompt"in r?String(r.prompt):String(r)).join(", "))})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:s(i),priority:2});let n=String(t.tool_response??"").toLowerCase();return n.includes("approved")||n.includes("approve")?e.push({type:"plan_approved",category:"plan",data:"plan approved by user",priority:1}):(n.includes("rejected")||n.includes("decline")||n.includes("denied"))&&e.push({type:"plan_rejected",category:"plan",data:s(`plan rejected: ${t.tool_response??""}`),priority:2}),e}if(t.tool_name==="Write"||t.tool_name==="Edit"){let e=String(t.tool_input.file_path??"");if(/[/\\]\.claude[/\\]plans[/\\]/.test(e))return[{type:"plan_file_write",category:"plan",data:s(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return[]}var k=[/\bsource\s+\S*activate\b/,/\bexport\s+\w+=/,/\bnvm\s+use\b/,/\bpyenv\s+(shell|local|global)\b/,/\bconda\s+activate\b/,/\brbenv\s+(shell|local|global)\b/,/\bnpm\s+install\b/,/\bnpm\s+ci\b/,/\bpip\s+install\b/,/\bbun\s+install\b/,/\byarn\s+(add|install)\b/,/\bpnpm\s+(add|install)\b/,/\bcargo\s+(install|add)\b/,/\bgo\s+(install|get)\b/,/\brustup\b/,/\basdf\b/,/\bvolta\b/,/\bdeno\s+install\b/];function v(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!k.some(n=>n.test(e)))return[];let i=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:s(i),priority:2}]}function x(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:s(e),priority:2}]}function w(t){if(!t.tool_response?.includes("Error")&&!t.tool_output?.isError)return[];let e=String(t.tool_response||""),o=[/not supported/i,/cannot/i,/does not support/i,/FAIL/i,/refused/i,/permission denied/i,/incompatible/i];for(let i of o){let n=e.match(i);if(n){let r=e.toLowerCase().indexOf(n[0].toLowerCase()),a=e.slice(Math.max(0,r-50),Math.min(e.length,r+200)).trim();return[{type:"constraint_discovered",category:"constraint",data:s(a),priority:2}]}}return[]}function R(t){if(t.tool_name!=="Agent")return[];let e=s(String(t.tool_input.prompt??t.tool_input.description??"")),o=t.tool_response?s(String(t.tool_response)):"",i=o.length>0;return[{type:i?"subagent_completed":"subagent_launched",category:"subagent",data:s(i?`[completed] ${e} \u2192 ${o}`:`[launched] ${e}`),priority:i?2:3}]}function A(t){let{tool_name:e,tool_input:o,tool_response:i}=t;if(!e.startsWith("mcp__"))return[];let n=e.split("__"),r=n[n.length-1]||e,a=Object.values(o).find(d=>typeof d=="string"),p=a?`: ${s(String(a))}`:"",u=i&&i.length>0?`
|
|
2
|
+
response: ${s(i)}`:"";return[{type:"mcp",category:"mcp",data:s(`${r}${p}${u}`),priority:3}]}var T=2048;function I(t,e){if(Buffer.byteLength(t,"utf8")<=e)return{value:t,truncated:!1};let o=Buffer.from(t,"utf8"),i=e;for(;i>0&&(o[i]&192)===128;)i--;return{value:o.subarray(0,i).toString("utf8"),truncated:!0}}var $=/(authorization|auth_token|access_token|refresh_token|bearer|token|secret|password|passwd|pwd|api[-_]?key|apikey|cookie|set-cookie|signature|private[-_]?key|client[-_]?secret|x[-_]?api[-_]?key)/i,H="[REDACTED]";function g(t,e=new WeakSet){if(t==null||typeof t!="object")return t;if(e.has(t))return"[CIRCULAR]";e.add(t);let o;if(Array.isArray(t))o=t.map(i=>g(i,e));else{let i={};for(let[n,r]of Object.entries(t))$.test(n)?i[n]=H:i[n]=g(r,e);o=i}return e.delete(t),o}function N(t){let{tool_name:e,tool_input:o}=t;if(!e.startsWith("mcp__"))return[];let i=g(o??{}),n;try{n=JSON.stringify(i)}catch{n="{}"}let{value:r,truncated:a}=I(n,T),p=a?`{"tool_name":${JSON.stringify(e)},"params_raw":${JSON.stringify(r)},"truncated":true}`:`{"tool_name":${JSON.stringify(e)},"params":${r}}`;return[{type:"mcp_tool_call",category:"mcp_tool_call",data:s(p),priority:4}]}function B(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,o=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",i=s(String(t.tool_response??"")),n=o?`Q: ${s(o)} \u2192 A: ${i}`:`answer: ${i}`;return[{type:"decision_question",category:"decision",data:s(n),priority:2}]}function O(t){if(t.tool_name!=="Agent")return[];if(!t.tool_response||t.tool_response.length===0)return[];let e=t.tool_response.length>500?t.tool_response.slice(0,500):t.tool_response;return[{type:"agent_finding",category:"agent-finding",data:s(e),priority:2}]}function C(t){let e=[f(t.tool_input),s(t.tool_response)].join(" ");if(e.length===0)return[];let o=new Set,i=e.match(/https?:\/\/[^\s)]+/g);if(i)for(let r of i)r=r.replace(/["'})\],;.]+$/,""),/localhost|127\.0\.0\.1/i.test(r)||o.add(r);let n=e.match(/(?<!\w)#(\d+)/g);if(n)for(let r of n)o.add(r);return o.size===0?[]:[{type:"external_ref",category:"external-ref",data:s(Array.from(o).join(", ")),priority:3}]}function P(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:s(`entered worktree: ${e}`),priority:2}]}var L=[/\b(don'?t|do not|never|always|instead|rather|prefer)\b/i,/\b(use|switch to|go with|pick|choose)\s+\w+\s+(instead|over|not)\b/i,/\b(no,?\s+(use|do|try|make))\b/i,/\b(hayır|hayir|evet|böyle|boyle|degil|değil|yerine|kullan)\b/i];function M(t){return L.some(o=>o.test(t))?[{type:"decision",category:"decision",data:s(t),priority:2}]:[]}var j=[/\b(act as|you are|behave like|pretend|role of|persona)\b/i,/\b(senior|staff|principal|lead)\s+(engineer|developer|architect)\b/i,/\b(gibi davran|rolünde|olarak çalış)\b/i];function W(t){return j.some(o=>o.test(t))?[{type:"role",category:"role",data:s(t),priority:3}]:[]}var D=[{mode:"investigate",pattern:/\b(why|how does|explain|understand|what is|analyze|debug|look into)\b/i},{mode:"implement",pattern:/\b(create|add|build|implement|write|make|develop|fix)\b/i},{mode:"discuss",pattern:/\b(think about|consider|should we|what if|pros and cons|opinion)\b/i},{mode:"review",pattern:/\b(review|check|audit|verify|test|validate)\b/i}];function F(t){let e=D.find(({pattern:o})=>o.test(t));return e?[{type:"intent",category:"intent",data:s(e.mode),priority:4}]:[]}var G=[/\bblocked on\b/i,/\bwaiting for\b/i,/\bneed\s+\S+\s+before\b/i,/\bcan'?t proceed until\b/i,/\bdepends on\b/i,/\bblocked\b/i,/\bbekliyor\b/i,/\bbekliyorum\b/i],U=[/\bunblocked\b/i,/\bresolved\b/i,/\bgot the\s+\S+/i,/\bis ready now\b/i,/\bcan proceed\b/i];function J(t){let e=[];return U.some(n=>n.test(t))?(e.push({type:"blocker_resolved",category:"blocked-on",data:s(t),priority:2}),e):(G.some(n=>n.test(t))&&e.push({type:"blocker",category:"blocked-on",data:s(t),priority:2}),e)}function q(t){return t.length<=1024?[]:[{type:"data",category:"data",data:s(t),priority:4}]}var c=null;function z(t){let{tool_name:e,tool_response:o,tool_output:i}=t,n=String(o??""),r=i?.isError===!0;if(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(n)||r)return c={tool:e,error:n.slice(0,200),callsSince:0},[];if(!c)return[];if(c.callsSince++,c.callsSince>10)return c=null,[];let p=e===c.tool,u=c.tool==="Read"&&(e==="Edit"||e==="Write");if(p||u){let d={type:"error_resolved",category:"error-resolution",data:s(`Error in ${c.tool}: ${c.error} \u2192 Fixed`),priority:2};return c=null,[d]}return[]}function Z(){c=null}var l=[];function K(t){return`${t.length}:${t.slice(0,20)}`}function Q(t){let{tool_name:e,tool_input:o}=t,i=K(JSON.stringify(o).slice(0,200));if(l.push({tool:e,inputHash:i}),l.length>50&&l.splice(0,l.length-50),l.length<3)return[];let n=0;for(let r=l.length-1;r>=0&&(l[r].tool===e&&l[r].inputHash===i);r--)n++;return n>=3?(l.splice(l.length-n),[{type:"retry_detected",category:"iteration-loop",data:s(`${e} called ${n} times with similar input`),priority:2}]):[]}function X(){l.length=0}var V={run_shell_command:"Bash",read_file:"Read",read_many_files:"Read",grep_search:"Grep",search_file_content:"Grep",web_fetch:"WebFetch",write_file:"Write",edit:"Edit",glob:"Glob",todo_write:"TodoWrite",ask_user_question:"AskUserQuestion",list_directory:"LS",save_memory:"Memory",skill:"Skill",exit_plan_mode:"ExitPlanMode",agent:"Agent",bash:"Bash",view:"Read",grep:"Grep",fetch:"WebFetch",shell:"Bash",shell_command:"Bash",exec_command:"Bash","container.exec":"Bash",local_shell:"Bash",grep_files:"Grep"};function Y(t){let e=V[t.tool_name];return!e||e===t.tool_name?t:{...t,tool_name:e}}function tt(t){try{let e=Y(t),o=[];return o.push(...b(e)),o.push(...y(e)),o.push(...h(e)),o.push(...m(e)),o.push(...v(e)),o.push(...S(e)),o.push(...E(e)),o.push(...x(e)),o.push(...R(e)),o.push(...A(e)),o.push(...N(e)),o.push(...B(e)),o.push(...w(e)),o.push(...P(e)),o.push(...O(e)),o.push(...C(e)),o.push(...z(e)),o.push(...Q(e)),o}catch{return[]}}function et(t){try{let e=[];return e.push(...M(t)),e.push(...W(t)),e.push(...F(t)),e.push(...J(t)),e.push(...q(t)),e}catch{return[]}}export{tt as extractEvents,et as extractUserEvents,Z as resetErrorResolutionState,X as resetIterationLoopState};
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -17,9 +17,11 @@ import "./ensure-deps.mjs";
|
|
|
17
17
|
|
|
18
18
|
import { createRoutingBlock } from "./routing-block.mjs";
|
|
19
19
|
import { createToolNamer } from "./core/tool-naming.mjs";
|
|
20
|
+
import { detectPlatformFromEnv } from "./core/platform-detect.mjs";
|
|
20
21
|
import { buildAutoInjection } from "./auto-injection.mjs";
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
+
const detectedPlatform = detectPlatformFromEnv();
|
|
24
|
+
const toolNamer = createToolNamer(detectedPlatform);
|
|
23
25
|
const ROUTING_BLOCK = createRoutingBlock(toolNamer);
|
|
24
26
|
import { readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath, resolveConfigDir } from "./session-helpers.mjs";
|
|
25
27
|
import { writeSessionEventsFile, buildSessionDirective, getSessionEvents } from "./session-directive.mjs";
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
import "../suppress-stderr.mjs";
|
|
3
3
|
import "../ensure-deps.mjs";
|
|
4
4
|
/**
|
|
5
|
-
* VS Code Copilot SessionStart hook for context-mode
|
|
5
|
+
* VS Code Copilot SessionStart hook for context-mode (v1.0.107)
|
|
6
|
+
*
|
|
7
|
+
* Created to close the v1.0.107 audit-flagged path bug: hooks.ts:98
|
|
8
|
+
* was resolving SessionStart to the Claude-Code generic top-level
|
|
9
|
+
* `hooks/sessionstart.mjs`. With the fix, the path now resolves
|
|
10
|
+
* to this file. Mirrors the JetBrains Copilot hook (same shape,
|
|
11
|
+
* same Microsoft Copilot wire contract).
|
|
6
12
|
*
|
|
7
13
|
* Session lifecycle management:
|
|
8
|
-
* - "startup" → Cleanup old sessions, capture
|
|
14
|
+
* - "startup" → Cleanup old sessions, capture .github/copilot-instructions.md as rule events
|
|
9
15
|
* - "compact" → Write events file, inject session knowledge directive
|
|
10
16
|
* - "resume" → Load previous session events, inject directive
|
|
11
17
|
* - "clear" → No action needed
|
|
@@ -24,8 +30,7 @@ import {
|
|
|
24
30
|
} from "../session-helpers.mjs";
|
|
25
31
|
import { join } from "node:path";
|
|
26
32
|
import { readFileSync, unlinkSync } from "node:fs";
|
|
27
|
-
import { fileURLToPath
|
|
28
|
-
import { homedir } from "node:os";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
29
34
|
|
|
30
35
|
const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
31
36
|
const { loadSessionDB } = createSessionLoaders(HOOK_DIR);
|
|
@@ -63,10 +68,7 @@ try {
|
|
|
63
68
|
const dbPath = getSessionDBPath(OPTS);
|
|
64
69
|
const db = new SessionDB({ dbPath });
|
|
65
70
|
|
|
66
|
-
// Filter events to the session being resumed
|
|
67
|
-
// getLatestSessionEvents(db) leaks events from any other session whose
|
|
68
|
-
// session_meta.started_at is more recent — observed cross-session bleed
|
|
69
|
-
// when a different session started after this one and before the resume.
|
|
71
|
+
// Filter events to the session being resumed (cross-session bleed guard).
|
|
70
72
|
const sessionId = getSessionId(input, OPTS);
|
|
71
73
|
const events = sessionId ? getSessionEvents(db, sessionId) : [];
|
|
72
74
|
if (events.length > 0) {
|
|
@@ -88,12 +90,9 @@ try {
|
|
|
88
90
|
const projectDir = getProjectDir(OPTS);
|
|
89
91
|
db.ensureSession(sessionId, projectDir);
|
|
90
92
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
new VSCodeCopilotAdapter().writeRoutingInstructions(projectDir, join(HOOK_DIR, "..", ".."));
|
|
95
|
-
} catch { /* best effort — don't block session start */ }
|
|
96
|
-
|
|
93
|
+
// VSCode Copilot's canonical project-level instruction file.
|
|
94
|
+
// Captured as rule_content events so they survive compact and become
|
|
95
|
+
// searchable via ctx_search() — same pattern as Claude Code captures CLAUDE.md.
|
|
97
96
|
const ruleFilePaths = [
|
|
98
97
|
join(projectDir, ".github", "copilot-instructions.md"),
|
|
99
98
|
];
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.107",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.107",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|