context-mode 1.0.105 → 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 +185 -11
- package/build/pi-extension.js +123 -29
- package/build/server.d.ts +1 -0
- package/build/server.js +28 -2
- package/build/session/db.d.ts +12 -3
- package/build/session/db.js +19 -4
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +46 -1
- package/cli.bundle.mjs +128 -127
- 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-db.bundle.mjs +4 -3
- 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 +72 -71
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as v,renameSync as M}from"node:fs";import{tmpdir as x}from"node:os";import{join as F}from"node:path";var g=class{#t;constructor(t){this.#t=t}pragma(t){let
|
|
1
|
+
import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as v,renameSync as M}from"node:fs";import{tmpdir as x}from"node:os";import{join as F}from"node:path";var g=class{#t;constructor(t){this.#t=t}pragma(t){let s=this.#t.prepare(`PRAGMA ${t}`).all();if(!s||s.length===0)return;if(s.length>1)return s;let r=Object.values(s[0]);return r.length===1?r[0]:s[0]}exec(t){let e="",s=null;for(let o=0;o<t.length;o++){let a=t[o];if(s)e+=a,a===s&&(s=null);else if(a==="'"||a==='"')e+=a,s=a;else if(a===";"){let c=e.trim();c&&this.#t.prepare(c).run(),e=""}else e+=a}let r=e.trim();return r&&this.#t.prepare(r).run(),this}prepare(t){let e=this.#t.prepare(t);return{run:(...s)=>e.run(...s),get:(...s)=>{let r=e.get(...s);return r===null?void 0:r},all:(...s)=>e.all(...s),iterate:(...s)=>e.iterate(...s)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},h=class{#t;constructor(t){this.#t=t}pragma(t){let s=this.#t.prepare(`PRAGMA ${t}`).all();if(!s||s.length===0)return;if(s.length>1)return s;let r=Object.values(s[0]);return r.length===1?r[0]:s[0]}exec(t){return this.#t.exec(t),this}prepare(t){let e=this.#t.prepare(t);return{run:(...s)=>e.run(...s),get:(...s)=>e.get(...s),all:(...s)=>e.all(...s),iterate:(...s)=>typeof e.iterate=="function"?e.iterate(...s):e.all(...s)[Symbol.iterator]()}}transaction(t){return(...e)=>{this.#t.exec("BEGIN");try{let s=t(...e);return this.#t.exec("COMMIT"),s}catch(s){throw this.#t.exec("ROLLBACK"),s}}}close(){this.#t.close()}},d=null;function B(){if(!d){let i=I(import.meta.url);if(globalThis.Bun){let t=i(["bun","sqlite"].join(":")).Database;d=function(s,r){let o=new t(s,{readonly:r?.readonly,create:!0}),a=new g(o);return r?.timeout&&a.pragma(`busy_timeout = ${r.timeout}`),a}}else if(process.platform==="linux")try{let{DatabaseSync:t}=i(["node","sqlite"].join(":"));d=function(s,r){let o=new t(s,{readOnly:r?.readonly??!1});return new h(o)}}catch{d=i("better-sqlite3")}else d=i("better-sqlite3")}return d}function b(i){i.pragma("journal_mode = WAL"),i.pragma("synchronous = NORMAL");try{i.pragma("mmap_size = 268435456")}catch{}}function N(i){if(!U(i))for(let t of["-wal","-shm"])try{v(i+t)}catch{}}function P(i){for(let t of["","-wal","-shm"])try{v(i+t)}catch{}}function y(i){try{i.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{i.close()}catch{}}function C(i="context-mode"){return F(x(),`${i}-${process.pid}.db`)}function k(i,t=[100,500,2e3]){let e;for(let s=0;s<=t.length;s++)try{return i()}catch(r){let o=r instanceof Error?r.message:String(r);if(!o.includes("SQLITE_BUSY")&&!o.includes("database is locked"))throw r;if(e=r instanceof Error?r:new Error(o),s<t.length){let a=t[s],c=Date.now();for(;Date.now()-c<a;);}}throw new Error(`SQLITE_BUSY: database is locked after ${t.length} retries. Original error: ${e?.message}`)}function j(i){return i.includes("SQLITE_CORRUPT")||i.includes("SQLITE_NOTADB")||i.includes("database disk image is malformed")||i.includes("file is not a database")}function X(i){let t=Date.now();for(let e of["","-wal","-shm"])try{M(i+e,`${i}${e}.corrupt-${t}`)}catch{}}var m=Symbol.for("__context_mode_live_dbs__"),p=(()=>{let i=globalThis;return i[m]||(i[m]=new Set,process.on("exit",()=>{for(let t of i[m])y(t);i[m].clear()})),i[m]})(),T=class{#t;#e;constructor(t){let e=B();this.#t=t,N(t);let s;try{s=new e(t,{timeout:3e4}),b(s)}catch(r){let o=r instanceof Error?r.message:String(r);if(j(o)){X(t),N(t);try{s=new e(t,{timeout:3e4}),b(s)}catch(a){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${a instanceof Error?a.message:String(a)}`)}}else throw r}this.#e=s,p.add(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){p.delete(this.#e),y(this.#e)}withRetry(t){return k(t)}cleanup(){p.delete(this.#e),y(this.#e),P(this.#t)}};import{createHash as f}from"node:crypto";import{execFileSync as W}from"node:child_process";var l;function z(){let i=process.env.CONTEXT_MODE_SESSION_SUFFIX,t=process.cwd();if(l&&l.cwd===t&&l.envSuffix===i)return l.suffix;let e="";if(i!==void 0)e=i?`__${i}`:"";else try{let s=W("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(r=>r.startsWith("worktree "))?.replace("worktree ","")?.trim();s&&t!==s&&(e=`__${f("sha256").update(t).digest("hex").slice(0,8)}`)}catch{}return l={cwd:t,envSuffix:i,suffix:e},e}function J(){l=void 0}var D=1e3,O=5,n={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",claimLatestUnconsumedResume:"claimLatestUnconsumedResume",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions",searchEvents:"searchEvents",incrementToolCall:"incrementToolCall",getToolCallTotals:"getToolCallTotals",getToolCallByTool:"getToolCallByTool"},A=class extends T{constructor(t){super(t?.dbPath??C("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let e=this.db.pragma("table_xinfo(session_events)").find(s=>s.name==="data_hash");e&&e.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
|
|
2
2
|
CREATE TABLE IF NOT EXISTS session_events (
|
|
3
3
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
4
|
session_id TEXT NOT NULL,
|
|
@@ -46,7 +46,7 @@ import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as
|
|
|
46
46
|
);
|
|
47
47
|
|
|
48
48
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
|
|
49
|
-
`);try{let t=this.db.pragma("table_xinfo(session_events)"),
|
|
49
|
+
`);try{let t=this.db.pragma("table_xinfo(session_events)"),e=new Set(t.map(s=>s.name));e.has("project_dir")||this.db.exec("ALTER TABLE session_events ADD COLUMN project_dir TEXT NOT NULL DEFAULT ''"),e.has("attribution_source")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_source TEXT NOT NULL DEFAULT 'unknown'"),e.has("attribution_confidence")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_confidence REAL NOT NULL DEFAULT 0"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)")}catch{}}prepareStatements(){this.stmts=new Map;let t=(e,s)=>{this.stmts.set(e,this.db.prepare(s))};t(n.insertEvent,`INSERT INTO session_events (
|
|
50
50
|
session_id, type, category, priority, data,
|
|
51
51
|
project_dir, attribution_source, attribution_confidence,
|
|
52
52
|
source_hook, data_hash
|
|
@@ -89,6 +89,7 @@ import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as
|
|
|
89
89
|
WHERE id = (
|
|
90
90
|
SELECT id FROM session_resume
|
|
91
91
|
WHERE consumed = 0
|
|
92
|
+
AND session_id != ?
|
|
92
93
|
ORDER BY created_at DESC, id DESC
|
|
93
94
|
LIMIT 1
|
|
94
95
|
)
|
|
@@ -106,4 +107,4 @@ import{createRequire as I}from"node:module";import{existsSync as U,unlinkSync as
|
|
|
106
107
|
updated_at = datetime('now')`),t(n.getToolCallTotals,`SELECT COALESCE(SUM(calls), 0) AS calls,
|
|
107
108
|
COALESCE(SUM(bytes_returned), 0) AS bytes_returned
|
|
108
109
|
FROM tool_calls WHERE session_id = ?`),t(n.getToolCallByTool,`SELECT tool, calls, bytes_returned
|
|
109
|
-
FROM tool_calls WHERE session_id = ? ORDER BY calls DESC`)}insertEvent(t,s
|
|
110
|
+
FROM tool_calls WHERE session_id = ? ORDER BY calls DESC`)}insertEvent(t,e,s="PostToolUse",r){let o=f("sha256").update(e.data).digest("hex").slice(0,16).toUpperCase(),a=String(r?.projectDir??e.project_dir??"").trim(),c=String(r?.source??e.attribution_source??"unknown"),u=Number(r?.confidence??e.attribution_confidence??0),_=Number.isFinite(u)?Math.max(0,Math.min(1,u)):0,E=this.db.transaction(()=>{if(this.stmt(n.checkDuplicate).get(t,O,e.type,o))return;this.stmt(n.getEventCount).get(t).cnt>=D&&this.stmt(n.evictLowestPriority).run(t),this.stmt(n.insertEvent).run(t,e.type,e.category,e.priority,e.data,a,c,_,s,o),this.stmt(n.updateMetaLastEvent).run(t)});this.withRetry(()=>E())}bulkInsertEvents(t,e,s="PostToolUse",r){if(!e||e.length===0)return;if(e.length===1){this.insertEvent(t,e[0],s,r?.[0]);return}let o=e.map((c,u)=>{let _=f("sha256").update(c.data).digest("hex").slice(0,16).toUpperCase(),E=r?.[u],S=String(E?.projectDir??c.project_dir??"").trim(),L=String(E?.source??c.attribution_source??"unknown"),R=Number(E?.confidence??c.attribution_confidence??0),w=Number.isFinite(R)?Math.max(0,Math.min(1,R)):0;return{event:c,dataHash:_,projectDir:S,attributionSource:L,attributionConfidence:w}}),a=this.db.transaction(()=>{let c=this.stmt(n.getEventCount).get(t).cnt;for(let u of o)this.stmt(n.checkDuplicate).get(t,O,u.event.type,u.dataHash)||(c>=D?this.stmt(n.evictLowestPriority).run(t):c++,this.stmt(n.insertEvent).run(t,u.event.type,u.event.category,u.event.priority,u.event.data,u.projectDir,u.attributionSource,u.attributionConfidence,s,u.dataHash));this.stmt(n.updateMetaLastEvent).run(t)});this.withRetry(()=>a())}getEvents(t,e){let s=e?.limit??1e3,r=e?.type,o=e?.minPriority;return r&&o!==void 0?this.stmt(n.getEventsByTypeAndPriority).all(t,r,o,s):r?this.stmt(n.getEventsByType).all(t,r,s):o!==void 0?this.stmt(n.getEventsByPriority).all(t,o,s):this.stmt(n.getEvents).all(t,s)}getEventCount(t){return this.stmt(n.getEventCount).get(t).cnt}getLatestAttributedProjectDir(t){return this.stmt(n.getLatestAttributedProject).get(t)?.project_dir||null}searchEvents(t,e,s,r){try{let o=t.replace(/[%_]/g,c=>"\\"+c),a=r??null;return this.stmt(n.searchEvents).all(s,o,o,a,a,e)}catch{return[]}}ensureSession(t,e){this.stmt(n.ensureSession).run(t,e)}getSessionStats(t){return this.stmt(n.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(n.incrementCompactCount).run(t)}upsertResume(t,e,s){this.stmt(n.upsertResume).run(t,e,s??0)}getResume(t){return this.stmt(n.getResume).get(t)??null}markResumeConsumed(t){this.stmt(n.markResumeConsumed).run(t)}claimLatestUnconsumedResume(t){let e=this.stmt(n.claimLatestUnconsumedResume).get(t);return e?{sessionId:e.session_id,snapshot:e.snapshot}:null}getLatestSessionId(){try{return this.db.prepare("SELECT session_id FROM session_meta ORDER BY started_at DESC LIMIT 1").get()?.session_id??null}catch{return null}}incrementToolCall(t,e,s=0){let r=Number.isFinite(s)&&s>0?Math.round(s):0;try{this.stmt(n.incrementToolCall).run(t,e,r)}catch{}}getToolCallStats(t){try{let e=this.stmt(n.getToolCallTotals).get(t),s=this.stmt(n.getToolCallByTool).all(t),r={};for(let o of s)r[o.tool]={calls:o.calls,bytesReturned:o.bytes_returned};return{totalCalls:e?.calls??0,totalBytesReturned:e?.bytes_returned??0,byTool:r}}catch{return{totalCalls:0,totalBytesReturned:0,byTool:{}}}}deleteSession(t){this.db.transaction(()=>{this.stmt(n.deleteEvents).run(t),this.stmt(n.deleteResume).run(t),this.stmt(n.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let e=`-${t}`,s=this.stmt(n.getOldSessions).all(e);for(let{session_id:r}of s)this.deleteSession(r);return s.length}};export{A as SessionDB,J as _resetWorktreeSuffixCacheForTests,z as getWorktreeSuffix};
|
|
@@ -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";
|