pybao-cli 1.3.3
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/LICENSE +201 -0
- package/README.md +440 -0
- package/README.zh-CN.md +338 -0
- package/cli-acp.js +82 -0
- package/cli.js +105 -0
- package/dist/REPL-WPV32MTF.js +42 -0
- package/dist/REPL-WPV32MTF.js.map +7 -0
- package/dist/acp-75HO2LBV.js +1357 -0
- package/dist/acp-75HO2LBV.js.map +7 -0
- package/dist/agentsValidate-6Z57ARKC.js +373 -0
- package/dist/agentsValidate-6Z57ARKC.js.map +7 -0
- package/dist/ask-NXXXCGY4.js +125 -0
- package/dist/ask-NXXXCGY4.js.map +7 -0
- package/dist/autoUpdater-PJMGNPUG.js +17 -0
- package/dist/autoUpdater-PJMGNPUG.js.map +7 -0
- package/dist/chunk-27GYWUY2.js +72 -0
- package/dist/chunk-27GYWUY2.js.map +7 -0
- package/dist/chunk-3DFBSQIT.js +23 -0
- package/dist/chunk-3DFBSQIT.js.map +7 -0
- package/dist/chunk-3KNGJX7Q.js +794 -0
- package/dist/chunk-3KNGJX7Q.js.map +7 -0
- package/dist/chunk-3PDD7M4T.js +164 -0
- package/dist/chunk-3PDD7M4T.js.map +7 -0
- package/dist/chunk-3ZNSAB7B.js +515 -0
- package/dist/chunk-3ZNSAB7B.js.map +7 -0
- package/dist/chunk-4SNFQYCY.js +511 -0
- package/dist/chunk-4SNFQYCY.js.map +7 -0
- package/dist/chunk-4XPNRLJG.js +1609 -0
- package/dist/chunk-4XPNRLJG.js.map +7 -0
- package/dist/chunk-5P7HBXTD.js +12 -0
- package/dist/chunk-5P7HBXTD.js.map +7 -0
- package/dist/chunk-6RZIUY5K.js +191 -0
- package/dist/chunk-6RZIUY5K.js.map +7 -0
- package/dist/chunk-6WELHKDA.js +240 -0
- package/dist/chunk-6WELHKDA.js.map +7 -0
- package/dist/chunk-7AAE6EO2.js +145 -0
- package/dist/chunk-7AAE6EO2.js.map +7 -0
- package/dist/chunk-A3BVXXA3.js +47 -0
- package/dist/chunk-A3BVXXA3.js.map +7 -0
- package/dist/chunk-A6PUMROK.js +152 -0
- package/dist/chunk-A6PUMROK.js.map +7 -0
- package/dist/chunk-BH3Y62E3.js +11 -0
- package/dist/chunk-BH3Y62E3.js.map +7 -0
- package/dist/chunk-BJSWTHRM.js +16 -0
- package/dist/chunk-BJSWTHRM.js.map +7 -0
- package/dist/chunk-BQA2EOUU.js +124 -0
- package/dist/chunk-BQA2EOUU.js.map +7 -0
- package/dist/chunk-CZZKRPE2.js +19 -0
- package/dist/chunk-CZZKRPE2.js.map +7 -0
- package/dist/chunk-ERMQRV55.js +24 -0
- package/dist/chunk-ERMQRV55.js.map +7 -0
- package/dist/chunk-HB2P6645.js +34 -0
- package/dist/chunk-HB2P6645.js.map +7 -0
- package/dist/chunk-HIRIJ2LQ.js +1256 -0
- package/dist/chunk-HIRIJ2LQ.js.map +7 -0
- package/dist/chunk-ICTEVBLN.js +735 -0
- package/dist/chunk-ICTEVBLN.js.map +7 -0
- package/dist/chunk-JKGOGSFT.js +128 -0
- package/dist/chunk-JKGOGSFT.js.map +7 -0
- package/dist/chunk-JZDE77EH.js +836 -0
- package/dist/chunk-JZDE77EH.js.map +7 -0
- package/dist/chunk-M624LT6O.js +17 -0
- package/dist/chunk-M624LT6O.js.map +7 -0
- package/dist/chunk-OMELVAJD.js +96 -0
- package/dist/chunk-OMELVAJD.js.map +7 -0
- package/dist/chunk-OUXHGDLH.js +95 -0
- package/dist/chunk-OUXHGDLH.js.map +7 -0
- package/dist/chunk-PCXUZ6AT.js +249 -0
- package/dist/chunk-PCXUZ6AT.js.map +7 -0
- package/dist/chunk-Q24ZGKIE.js +1097 -0
- package/dist/chunk-Q24ZGKIE.js.map +7 -0
- package/dist/chunk-QBHEERCF.js +30254 -0
- package/dist/chunk-QBHEERCF.js.map +7 -0
- package/dist/chunk-QIHB5PYM.js +472 -0
- package/dist/chunk-QIHB5PYM.js.map +7 -0
- package/dist/chunk-RQVLBMP7.js +24 -0
- package/dist/chunk-RQVLBMP7.js.map +7 -0
- package/dist/chunk-SWYJOV5E.js +490 -0
- package/dist/chunk-SWYJOV5E.js.map +7 -0
- package/dist/chunk-T6GVXTNQ.js +21 -0
- package/dist/chunk-T6GVXTNQ.js.map +7 -0
- package/dist/chunk-T7GPUZVK.js +766 -0
- package/dist/chunk-T7GPUZVK.js.map +7 -0
- package/dist/chunk-TXFCNQDE.js +2934 -0
- package/dist/chunk-TXFCNQDE.js.map +7 -0
- package/dist/chunk-UNNVICVU.js +95 -0
- package/dist/chunk-UNNVICVU.js.map +7 -0
- package/dist/chunk-UUNVJZWA.js +515 -0
- package/dist/chunk-UUNVJZWA.js.map +7 -0
- package/dist/chunk-VRGR4ZTQ.js +49 -0
- package/dist/chunk-VRGR4ZTQ.js.map +7 -0
- package/dist/chunk-VTVTEE5N.js +2613 -0
- package/dist/chunk-VTVTEE5N.js.map +7 -0
- package/dist/chunk-WPTPPOYN.js +936 -0
- package/dist/chunk-WPTPPOYN.js.map +7 -0
- package/dist/chunk-XXFY63TM.js +196 -0
- package/dist/chunk-XXFY63TM.js.map +7 -0
- package/dist/chunk-Z3HMXDXP.js +654 -0
- package/dist/chunk-Z3HMXDXP.js.map +7 -0
- package/dist/chunk-ZJGXEWKF.js +138 -0
- package/dist/chunk-ZJGXEWKF.js.map +7 -0
- package/dist/cli-RFYBXM7F.js +3917 -0
- package/dist/cli-RFYBXM7F.js.map +7 -0
- package/dist/commands-YOXMODDO.js +46 -0
- package/dist/commands-YOXMODDO.js.map +7 -0
- package/dist/config-5OPX3H2K.js +81 -0
- package/dist/config-5OPX3H2K.js.map +7 -0
- package/dist/context-THRRBPFP.js +30 -0
- package/dist/context-THRRBPFP.js.map +7 -0
- package/dist/costTracker-ELNBZ2DN.js +19 -0
- package/dist/costTracker-ELNBZ2DN.js.map +7 -0
- package/dist/customCommands-4XOZH44N.js +25 -0
- package/dist/customCommands-4XOZH44N.js.map +7 -0
- package/dist/env-EL4KBHMB.js +22 -0
- package/dist/env-EL4KBHMB.js.map +7 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +7 -0
- package/dist/kodeAgentSessionId-PROTVRBR.js +13 -0
- package/dist/kodeAgentSessionId-PROTVRBR.js.map +7 -0
- package/dist/kodeAgentSessionLoad-UMPV7MC3.js +18 -0
- package/dist/kodeAgentSessionLoad-UMPV7MC3.js.map +7 -0
- package/dist/kodeAgentSessionResume-YJS4FVQM.js +16 -0
- package/dist/kodeAgentSessionResume-YJS4FVQM.js.map +7 -0
- package/dist/kodeAgentStreamJson-3T26CHCP.js +13 -0
- package/dist/kodeAgentStreamJson-3T26CHCP.js.map +7 -0
- package/dist/kodeAgentStreamJsonSession-BZS2VDCY.js +131 -0
- package/dist/kodeAgentStreamJsonSession-BZS2VDCY.js.map +7 -0
- package/dist/kodeAgentStructuredStdio-TNB6U6SP.js +10 -0
- package/dist/kodeAgentStructuredStdio-TNB6U6SP.js.map +7 -0
- package/dist/kodeHooks-VUAWIY2D.js +36 -0
- package/dist/kodeHooks-VUAWIY2D.js.map +7 -0
- package/dist/llm-A3BCM4Q2.js +3118 -0
- package/dist/llm-A3BCM4Q2.js.map +7 -0
- package/dist/llmLazy-ZJSRLZVD.js +15 -0
- package/dist/llmLazy-ZJSRLZVD.js.map +7 -0
- package/dist/loader-HZQBWO74.js +28 -0
- package/dist/loader-HZQBWO74.js.map +7 -0
- package/dist/mcp-XKOJ55B2.js +49 -0
- package/dist/mcp-XKOJ55B2.js.map +7 -0
- package/dist/mentionProcessor-ANYU5MLF.js +211 -0
- package/dist/mentionProcessor-ANYU5MLF.js.map +7 -0
- package/dist/messages-75DL5XBP.js +63 -0
- package/dist/messages-75DL5XBP.js.map +7 -0
- package/dist/model-OPJGJZRC.js +30 -0
- package/dist/model-OPJGJZRC.js.map +7 -0
- package/dist/openai-DT54BAFP.js +29 -0
- package/dist/openai-DT54BAFP.js.map +7 -0
- package/dist/outputStyles-TPFVI52O.js +28 -0
- package/dist/outputStyles-TPFVI52O.js.map +7 -0
- package/dist/package.json +4 -0
- package/dist/pluginRuntime-W74PYSZ4.js +218 -0
- package/dist/pluginRuntime-W74PYSZ4.js.map +7 -0
- package/dist/pluginValidation-FALYRVI2.js +17 -0
- package/dist/pluginValidation-FALYRVI2.js.map +7 -0
- package/dist/prompts-J4TPRMJ3.js +48 -0
- package/dist/prompts-J4TPRMJ3.js.map +7 -0
- package/dist/query-K3QKBVDN.js +50 -0
- package/dist/query-K3QKBVDN.js.map +7 -0
- package/dist/responsesStreaming-HMB74TRD.js +10 -0
- package/dist/responsesStreaming-HMB74TRD.js.map +7 -0
- package/dist/ripgrep-XJGSUBG7.js +17 -0
- package/dist/ripgrep-XJGSUBG7.js.map +7 -0
- package/dist/skillMarketplace-AUGKNCPW.js +37 -0
- package/dist/skillMarketplace-AUGKNCPW.js.map +7 -0
- package/dist/state-DQYRXKTG.js +16 -0
- package/dist/state-DQYRXKTG.js.map +7 -0
- package/dist/theme-MS5HDUBJ.js +14 -0
- package/dist/theme-MS5HDUBJ.js.map +7 -0
- package/dist/toolPermissionContext-GYD5LYFK.js +17 -0
- package/dist/toolPermissionContext-GYD5LYFK.js.map +7 -0
- package/dist/toolPermissionSettings-4MPZVYDR.js +18 -0
- package/dist/toolPermissionSettings-4MPZVYDR.js.map +7 -0
- package/dist/tools-QW6SIJLJ.js +47 -0
- package/dist/tools-QW6SIJLJ.js.map +7 -0
- package/dist/userInput-F2PGBRFU.js +311 -0
- package/dist/userInput-F2PGBRFU.js.map +7 -0
- package/dist/uuid-GYYCQ6QK.js +9 -0
- package/dist/uuid-GYYCQ6QK.js.map +7 -0
- package/dist/yoga.wasm +0 -0
- package/package.json +136 -0
- package/scripts/binary-utils.cjs +62 -0
- package/scripts/cli-acp-wrapper.cjs +82 -0
- package/scripts/cli-wrapper.cjs +105 -0
- package/scripts/postinstall.js +144 -0
- package/yoga.wasm +0 -0
|
@@ -0,0 +1,1256 @@
|
|
|
1
|
+
import { createRequire as __pybCreateRequire } from "node:module";
|
|
2
|
+
const require = __pybCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
getKodeAgentSessionId
|
|
5
|
+
} from "./chunk-T6GVXTNQ.js";
|
|
6
|
+
import {
|
|
7
|
+
loadSettingsWithLegacyFallback
|
|
8
|
+
} from "./chunk-7AAE6EO2.js";
|
|
9
|
+
import {
|
|
10
|
+
getSessionPlugins
|
|
11
|
+
} from "./chunk-BJSWTHRM.js";
|
|
12
|
+
import {
|
|
13
|
+
getCwd,
|
|
14
|
+
logError
|
|
15
|
+
} from "./chunk-TXFCNQDE.js";
|
|
16
|
+
|
|
17
|
+
// src/utils/session/kodeHooks.ts
|
|
18
|
+
import { spawn } from "child_process";
|
|
19
|
+
import { readFileSync, statSync } from "fs";
|
|
20
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
21
|
+
import { tmpdir } from "os";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import { minimatch } from "minimatch";
|
|
24
|
+
var cache = /* @__PURE__ */ new Map();
|
|
25
|
+
var pluginHooksCache = /* @__PURE__ */ new Map();
|
|
26
|
+
var sessionStartCache = /* @__PURE__ */ new Map();
|
|
27
|
+
var HOOK_RUNTIME_STATE_KEY = "__kodeHookRuntimeState";
|
|
28
|
+
function getHookRuntimeState(toolUseContext) {
|
|
29
|
+
const existing = toolUseContext?.[HOOK_RUNTIME_STATE_KEY];
|
|
30
|
+
if (existing && typeof existing === "object" && Array.isArray(existing.queuedSystemMessages) && Array.isArray(existing.queuedAdditionalContexts)) {
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
const created = {
|
|
34
|
+
transcriptPath: void 0,
|
|
35
|
+
queuedSystemMessages: [],
|
|
36
|
+
queuedAdditionalContexts: []
|
|
37
|
+
};
|
|
38
|
+
if (toolUseContext && typeof toolUseContext === "object") {
|
|
39
|
+
;
|
|
40
|
+
toolUseContext[HOOK_RUNTIME_STATE_KEY] = created;
|
|
41
|
+
}
|
|
42
|
+
return created;
|
|
43
|
+
}
|
|
44
|
+
function updateHookTranscriptForMessages(toolUseContext, messages) {
|
|
45
|
+
const state = getHookRuntimeState(toolUseContext);
|
|
46
|
+
const sessionId = getKodeAgentSessionId();
|
|
47
|
+
const dir = join(tmpdir(), "kode-hooks-transcripts");
|
|
48
|
+
try {
|
|
49
|
+
mkdirSync(dir, { recursive: true });
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
if (!state.transcriptPath) {
|
|
53
|
+
state.transcriptPath = join(dir, `${sessionId}.transcript.txt`);
|
|
54
|
+
}
|
|
55
|
+
const lines = [];
|
|
56
|
+
for (const msg of Array.isArray(messages) ? messages : []) {
|
|
57
|
+
if (!msg || typeof msg !== "object") continue;
|
|
58
|
+
if (msg.type !== "user" && msg.type !== "assistant") continue;
|
|
59
|
+
if (msg.type === "user") {
|
|
60
|
+
const content2 = msg?.message?.content;
|
|
61
|
+
if (typeof content2 === "string") {
|
|
62
|
+
lines.push(`user: ${content2}`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(content2)) {
|
|
66
|
+
const parts2 = [];
|
|
67
|
+
for (const block of content2) {
|
|
68
|
+
if (!block || typeof block !== "object") continue;
|
|
69
|
+
if (block.type === "text") parts2.push(String(block.text ?? ""));
|
|
70
|
+
if (block.type === "tool_result")
|
|
71
|
+
parts2.push(`[tool_result] ${String(block.content ?? "")}`);
|
|
72
|
+
}
|
|
73
|
+
lines.push(`user: ${parts2.join("")}`);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const content = msg?.message?.content;
|
|
78
|
+
if (typeof content === "string") {
|
|
79
|
+
lines.push(`assistant: ${content}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(content)) continue;
|
|
83
|
+
const parts = [];
|
|
84
|
+
for (const block of content) {
|
|
85
|
+
if (!block || typeof block !== "object") continue;
|
|
86
|
+
if (block.type === "text") parts.push(String(block.text ?? ""));
|
|
87
|
+
if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
88
|
+
parts.push(
|
|
89
|
+
`[tool_use:${String(block.name ?? "")}] ${hookValueForPrompt(block.input)}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (block.type === "mcp_tool_use") {
|
|
93
|
+
parts.push(
|
|
94
|
+
`[mcp_tool_use:${String(block.name ?? "")}] ${hookValueForPrompt(block.input)}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
lines.push(`assistant: ${parts.join("")}`);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
writeFileSync(state.transcriptPath, lines.join("\n") + "\n", "utf8");
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function drainHookSystemPromptAdditions(toolUseContext) {
|
|
106
|
+
const state = getHookRuntimeState(toolUseContext);
|
|
107
|
+
const systemMessages = state.queuedSystemMessages.splice(
|
|
108
|
+
0,
|
|
109
|
+
state.queuedSystemMessages.length
|
|
110
|
+
);
|
|
111
|
+
const contexts = state.queuedAdditionalContexts.splice(
|
|
112
|
+
0,
|
|
113
|
+
state.queuedAdditionalContexts.length
|
|
114
|
+
);
|
|
115
|
+
const additions = [];
|
|
116
|
+
if (systemMessages.length > 0) {
|
|
117
|
+
additions.push(
|
|
118
|
+
["\n# Hook system messages", ...systemMessages.map((m) => m.trim())].filter(Boolean).join("\n\n")
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (contexts.length > 0) {
|
|
122
|
+
additions.push(
|
|
123
|
+
["\n# Hook additional context", ...contexts.map((m) => m.trim())].filter(Boolean).join("\n\n")
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return additions;
|
|
127
|
+
}
|
|
128
|
+
function getHookTranscriptPath(toolUseContext) {
|
|
129
|
+
return getHookRuntimeState(toolUseContext).transcriptPath;
|
|
130
|
+
}
|
|
131
|
+
function queueHookSystemMessages(toolUseContext, messages) {
|
|
132
|
+
const state = getHookRuntimeState(toolUseContext);
|
|
133
|
+
for (const msg of messages) {
|
|
134
|
+
const trimmed = String(msg ?? "").trim();
|
|
135
|
+
if (trimmed) state.queuedSystemMessages.push(trimmed);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function queueHookAdditionalContexts(toolUseContext, contexts) {
|
|
139
|
+
const state = getHookRuntimeState(toolUseContext);
|
|
140
|
+
for (const ctx of contexts) {
|
|
141
|
+
const trimmed = String(ctx ?? "").trim();
|
|
142
|
+
if (trimmed) state.queuedAdditionalContexts.push(trimmed);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function isCommandHook(value) {
|
|
146
|
+
return value !== null && typeof value === "object" && value.type === "command" && typeof value.command === "string" && Boolean(value.command.trim());
|
|
147
|
+
}
|
|
148
|
+
function isPromptHook(value) {
|
|
149
|
+
return value !== null && typeof value === "object" && value.type === "prompt" && typeof value.prompt === "string" && Boolean(value.prompt.trim());
|
|
150
|
+
}
|
|
151
|
+
function isHook(value) {
|
|
152
|
+
return isCommandHook(value) || isPromptHook(value);
|
|
153
|
+
}
|
|
154
|
+
function parseHookMatchers(value) {
|
|
155
|
+
if (!Array.isArray(value)) return [];
|
|
156
|
+
const out = [];
|
|
157
|
+
for (const item of value) {
|
|
158
|
+
if (!item || typeof item !== "object") continue;
|
|
159
|
+
const matcher = typeof item.matcher === "string" ? item.matcher.trim() : "";
|
|
160
|
+
const effectiveMatcher = matcher || "*";
|
|
161
|
+
const hooksRaw = item.hooks;
|
|
162
|
+
const hooks = Array.isArray(hooksRaw) ? hooksRaw.filter(isHook) : [];
|
|
163
|
+
if (hooks.length === 0) continue;
|
|
164
|
+
out.push({ matcher: effectiveMatcher, hooks });
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function parseHooksByEvent(rawHooks) {
|
|
169
|
+
if (!rawHooks || typeof rawHooks !== "object") return {};
|
|
170
|
+
const hooks = rawHooks;
|
|
171
|
+
return {
|
|
172
|
+
PreToolUse: parseHookMatchers(hooks.PreToolUse),
|
|
173
|
+
PostToolUse: parseHookMatchers(hooks.PostToolUse),
|
|
174
|
+
Stop: parseHookMatchers(hooks.Stop),
|
|
175
|
+
SubagentStop: parseHookMatchers(hooks.SubagentStop),
|
|
176
|
+
UserPromptSubmit: parseHookMatchers(hooks.UserPromptSubmit),
|
|
177
|
+
SessionStart: parseHookMatchers(hooks.SessionStart),
|
|
178
|
+
SessionEnd: parseHookMatchers(hooks.SessionEnd)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function loadInlinePluginHooksByEvent(plugin) {
|
|
182
|
+
const manifestHooks = plugin.manifest?.hooks;
|
|
183
|
+
if (!manifestHooks || typeof manifestHooks !== "object" || Array.isArray(manifestHooks))
|
|
184
|
+
return null;
|
|
185
|
+
const hookObj = manifestHooks.hooks && typeof manifestHooks.hooks === "object" && !Array.isArray(manifestHooks.hooks) ? manifestHooks.hooks : manifestHooks;
|
|
186
|
+
const cacheKey = `${plugin.manifestPath}#inlineHooks`;
|
|
187
|
+
try {
|
|
188
|
+
const stat = statSync(plugin.manifestPath);
|
|
189
|
+
const cached = pluginHooksCache.get(cacheKey);
|
|
190
|
+
if (cached && cached.mtimeMs === stat.mtimeMs) return cached.byEvent;
|
|
191
|
+
const byEvent = parseHooksByEvent(hookObj);
|
|
192
|
+
pluginHooksCache.set(cacheKey, { mtimeMs: stat.mtimeMs, byEvent });
|
|
193
|
+
return byEvent;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
logError(err);
|
|
196
|
+
pluginHooksCache.delete(cacheKey);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function loadSettingsMatchers(projectDir, event) {
|
|
201
|
+
const loaded = loadSettingsWithLegacyFallback({
|
|
202
|
+
destination: "projectSettings",
|
|
203
|
+
projectDir,
|
|
204
|
+
migrateToPrimary: true
|
|
205
|
+
});
|
|
206
|
+
const settingsPath = loaded.usedPath;
|
|
207
|
+
if (!settingsPath) return [];
|
|
208
|
+
try {
|
|
209
|
+
const stat = statSync(settingsPath);
|
|
210
|
+
const cached = cache.get(settingsPath);
|
|
211
|
+
if (cached && cached.mtimeMs === stat.mtimeMs)
|
|
212
|
+
return cached.byEvent[event] ?? [];
|
|
213
|
+
const parsed = loaded.settings;
|
|
214
|
+
const byEvent = parseHooksByEvent(parsed?.hooks);
|
|
215
|
+
cache.set(settingsPath, { mtimeMs: stat.mtimeMs, byEvent });
|
|
216
|
+
return byEvent[event] ?? [];
|
|
217
|
+
} catch {
|
|
218
|
+
cache.delete(settingsPath);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function matcherMatchesTool(matcher, toolName) {
|
|
223
|
+
if (!matcher) return false;
|
|
224
|
+
if (matcher === "*" || matcher === "all") return true;
|
|
225
|
+
if (matcher === toolName) return true;
|
|
226
|
+
try {
|
|
227
|
+
if (minimatch(toolName, matcher, { dot: true, nocase: false })) return true;
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
if (new RegExp(matcher).test(toolName)) return true;
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
function buildShellCommand(command) {
|
|
237
|
+
if (process.platform === "win32") {
|
|
238
|
+
return ["cmd.exe", "/d", "/s", "/c", command];
|
|
239
|
+
}
|
|
240
|
+
return ["/bin/sh", "-c", command];
|
|
241
|
+
}
|
|
242
|
+
async function runCommandHook(args) {
|
|
243
|
+
const cmd = buildShellCommand(args.command);
|
|
244
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
245
|
+
cwd: args.cwd,
|
|
246
|
+
env: { ...process.env, ...args.env ?? {} },
|
|
247
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
248
|
+
windowsHide: true
|
|
249
|
+
});
|
|
250
|
+
let wasAborted = false;
|
|
251
|
+
const onAbort = () => {
|
|
252
|
+
wasAborted = true;
|
|
253
|
+
try {
|
|
254
|
+
proc.kill();
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
if (args.signal) {
|
|
259
|
+
if (args.signal.aborted) onAbort();
|
|
260
|
+
args.signal.addEventListener("abort", onAbort, { once: true });
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const input = JSON.stringify(args.stdinJson);
|
|
264
|
+
try {
|
|
265
|
+
proc.stdin?.write(input);
|
|
266
|
+
proc.stdin?.end();
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
let stdout = "";
|
|
270
|
+
let stderr = "";
|
|
271
|
+
const collect = (stream, append) => {
|
|
272
|
+
if (!stream) {
|
|
273
|
+
return { done: Promise.resolve(), cleanup: () => {
|
|
274
|
+
} };
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
;
|
|
278
|
+
stream.setEncoding?.("utf8");
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
let resolveDone = null;
|
|
282
|
+
const done = new Promise((resolve) => {
|
|
283
|
+
resolveDone = resolve;
|
|
284
|
+
});
|
|
285
|
+
const finish = () => {
|
|
286
|
+
cleanup();
|
|
287
|
+
if (!resolveDone) return;
|
|
288
|
+
resolveDone();
|
|
289
|
+
resolveDone = null;
|
|
290
|
+
};
|
|
291
|
+
const onData = (chunk) => {
|
|
292
|
+
append(
|
|
293
|
+
typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk)
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
const onError = () => finish();
|
|
297
|
+
const cleanup = () => {
|
|
298
|
+
stream.off("data", onData);
|
|
299
|
+
stream.off("end", finish);
|
|
300
|
+
stream.off("close", finish);
|
|
301
|
+
stream.off("error", onError);
|
|
302
|
+
};
|
|
303
|
+
stream.on("data", onData);
|
|
304
|
+
stream.once("end", finish);
|
|
305
|
+
stream.once("close", finish);
|
|
306
|
+
stream.once("error", onError);
|
|
307
|
+
return { done, cleanup };
|
|
308
|
+
};
|
|
309
|
+
const stdoutCollector = collect(proc.stdout, (chunk) => {
|
|
310
|
+
stdout += chunk;
|
|
311
|
+
});
|
|
312
|
+
const stderrCollector = collect(proc.stderr, (chunk) => {
|
|
313
|
+
stderr += chunk;
|
|
314
|
+
});
|
|
315
|
+
const exitCode = await new Promise((resolve) => {
|
|
316
|
+
proc.once("exit", (code, signal) => {
|
|
317
|
+
if (typeof code === "number") return resolve(code);
|
|
318
|
+
if (signal) return resolve(143);
|
|
319
|
+
return resolve(0);
|
|
320
|
+
});
|
|
321
|
+
proc.once("error", () => resolve(1));
|
|
322
|
+
});
|
|
323
|
+
await Promise.race([
|
|
324
|
+
Promise.allSettled([stdoutCollector.done, stderrCollector.done]),
|
|
325
|
+
new Promise((resolve) => setTimeout(resolve, 250))
|
|
326
|
+
]);
|
|
327
|
+
stdoutCollector.cleanup();
|
|
328
|
+
stderrCollector.cleanup();
|
|
329
|
+
return {
|
|
330
|
+
exitCode: wasAborted && exitCode === 0 ? 143 : exitCode,
|
|
331
|
+
stdout,
|
|
332
|
+
stderr
|
|
333
|
+
};
|
|
334
|
+
} finally {
|
|
335
|
+
if (args.signal) {
|
|
336
|
+
try {
|
|
337
|
+
args.signal.removeEventListener("abort", onAbort);
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function mergeAbortSignals(signals) {
|
|
344
|
+
const controller = new AbortController();
|
|
345
|
+
const onAbort = () => controller.abort();
|
|
346
|
+
const cleanups = [];
|
|
347
|
+
for (const signal of signals) {
|
|
348
|
+
if (!signal) continue;
|
|
349
|
+
if (signal.aborted) {
|
|
350
|
+
controller.abort();
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
354
|
+
cleanups.push(() => {
|
|
355
|
+
try {
|
|
356
|
+
signal.removeEventListener("abort", onAbort);
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
signal: controller.signal,
|
|
363
|
+
cleanup: () => cleanups.forEach((fn) => fn())
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function withHookTimeout(args) {
|
|
367
|
+
const timeoutMs = typeof args.timeoutSeconds === "number" && Number.isFinite(args.timeoutSeconds) ? Math.max(0, Math.floor(args.timeoutSeconds * 1e3)) : args.fallbackTimeoutMs;
|
|
368
|
+
const timeoutSignal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(timeoutMs) : (() => {
|
|
369
|
+
const controller = new AbortController();
|
|
370
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
371
|
+
const signal = controller.signal;
|
|
372
|
+
signal.__cleanup = () => clearTimeout(timer);
|
|
373
|
+
return signal;
|
|
374
|
+
})();
|
|
375
|
+
const merged = mergeAbortSignals([args.parentSignal, timeoutSignal]);
|
|
376
|
+
const timeoutCleanup = typeof timeoutSignal.__cleanup === "function" ? timeoutSignal.__cleanup : () => {
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
signal: merged.signal,
|
|
380
|
+
cleanup: () => {
|
|
381
|
+
merged.cleanup();
|
|
382
|
+
timeoutCleanup();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function coerceHookMessage(stdout, stderr) {
|
|
387
|
+
const s = (stderr || "").trim();
|
|
388
|
+
if (s) return s;
|
|
389
|
+
const o = (stdout || "").trim();
|
|
390
|
+
if (o) return o;
|
|
391
|
+
return "Hook blocked the tool call.";
|
|
392
|
+
}
|
|
393
|
+
function coerceHookPermissionMode(mode) {
|
|
394
|
+
if (mode === "acceptEdits" || mode === "bypassPermissions") return "allow";
|
|
395
|
+
return "ask";
|
|
396
|
+
}
|
|
397
|
+
function extractFirstJsonObject(text) {
|
|
398
|
+
let start = -1;
|
|
399
|
+
let depth = 0;
|
|
400
|
+
let inString = false;
|
|
401
|
+
let escaped = false;
|
|
402
|
+
for (let i = 0; i < text.length; i++) {
|
|
403
|
+
const ch = text[i];
|
|
404
|
+
if (start === -1) {
|
|
405
|
+
if (ch === "{") {
|
|
406
|
+
start = i;
|
|
407
|
+
depth = 1;
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (inString) {
|
|
412
|
+
if (escaped) {
|
|
413
|
+
escaped = false;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (ch === "\\") {
|
|
417
|
+
escaped = true;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (ch === '"') {
|
|
421
|
+
inString = false;
|
|
422
|
+
}
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (ch === '"') {
|
|
426
|
+
inString = true;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (ch === "{") {
|
|
430
|
+
depth++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (ch === "}") {
|
|
434
|
+
depth--;
|
|
435
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
function parseSessionStartAdditionalContext(stdout) {
|
|
441
|
+
const trimmed = String(stdout ?? "").trim();
|
|
442
|
+
if (!trimmed) return null;
|
|
443
|
+
const jsonStr = extractFirstJsonObject(trimmed) ?? trimmed;
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(jsonStr);
|
|
446
|
+
const additional = parsed && typeof parsed === "object" && parsed.hookSpecificOutput && typeof parsed.hookSpecificOutput.additionalContext === "string" ? String(parsed.hookSpecificOutput.additionalContext) : null;
|
|
447
|
+
return additional && additional.trim() ? additional : null;
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function tryParseHookJson(stdout) {
|
|
453
|
+
const trimmed = String(stdout ?? "").trim();
|
|
454
|
+
if (!trimmed) return null;
|
|
455
|
+
const jsonStr = extractFirstJsonObject(trimmed) ?? trimmed;
|
|
456
|
+
try {
|
|
457
|
+
const parsed = JSON.parse(jsonStr);
|
|
458
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function normalizePermissionDecision(value) {
|
|
464
|
+
if (typeof value !== "string") return null;
|
|
465
|
+
const normalized = value.trim().toLowerCase();
|
|
466
|
+
if (normalized === "allow" || normalized === "approve") return "allow";
|
|
467
|
+
if (normalized === "deny" || normalized === "block") return "deny";
|
|
468
|
+
if (normalized === "ask") return "ask";
|
|
469
|
+
if (normalized === "passthrough" || normalized === "continue")
|
|
470
|
+
return "passthrough";
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function normalizeStopDecision(value) {
|
|
474
|
+
if (typeof value !== "string") return null;
|
|
475
|
+
const normalized = value.trim().toLowerCase();
|
|
476
|
+
if (normalized === "approve" || normalized === "allow") return "approve";
|
|
477
|
+
if (normalized === "block" || normalized === "deny") return "block";
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
function hookValueForPrompt(value) {
|
|
481
|
+
if (value === null || value === void 0) return "";
|
|
482
|
+
if (typeof value === "string") return value;
|
|
483
|
+
try {
|
|
484
|
+
return JSON.stringify(value, null, 2);
|
|
485
|
+
} catch {
|
|
486
|
+
return String(value);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function interpolatePromptHookTemplate(template, hookInput) {
|
|
490
|
+
return String(template ?? "").replaceAll("$TOOL_INPUT", hookValueForPrompt(hookInput.tool_input)).replaceAll("$TOOL_RESULT", hookValueForPrompt(hookInput.tool_result)).replaceAll("$TOOL_RESPONSE", hookValueForPrompt(hookInput.tool_response)).replaceAll("$USER_PROMPT", hookValueForPrompt(hookInput.user_prompt)).replaceAll("$PROMPT", hookValueForPrompt(hookInput.prompt)).replaceAll("$REASON", hookValueForPrompt(hookInput.reason));
|
|
491
|
+
}
|
|
492
|
+
function extractAssistantText(message) {
|
|
493
|
+
const content = message?.message?.content;
|
|
494
|
+
if (typeof content === "string") return content;
|
|
495
|
+
if (!Array.isArray(content)) return "";
|
|
496
|
+
return content.filter((b) => b && typeof b === "object" && b.type === "text").map((b) => String(b.text ?? "")).join("");
|
|
497
|
+
}
|
|
498
|
+
async function runPromptHook(args) {
|
|
499
|
+
const { signal, cleanup } = withHookTimeout({
|
|
500
|
+
timeoutSeconds: args.hook.timeout,
|
|
501
|
+
parentSignal: args.parentSignal,
|
|
502
|
+
fallbackTimeoutMs: args.fallbackTimeoutMs
|
|
503
|
+
});
|
|
504
|
+
try {
|
|
505
|
+
const { queryQuick } = await import("./llmLazy-ZJSRLZVD.js");
|
|
506
|
+
const systemPrompt = [
|
|
507
|
+
"You are executing a Kode prompt hook.",
|
|
508
|
+
"Return a single JSON object only (no markdown, no prose).",
|
|
509
|
+
`hook_event_name: ${args.hookEvent}`,
|
|
510
|
+
"Valid fields include:",
|
|
511
|
+
"- systemMessage: string",
|
|
512
|
+
'- decision: "approve" | "block" (Stop/SubagentStop only)',
|
|
513
|
+
"- reason: string (Stop/SubagentStop only)",
|
|
514
|
+
'- hookSpecificOutput.permissionDecision: "allow" | "deny" | "ask" | "passthrough" (PreToolUse only)',
|
|
515
|
+
"- hookSpecificOutput.updatedInput: object (PreToolUse only)",
|
|
516
|
+
"- hookSpecificOutput.additionalContext: string (SessionStart/any)"
|
|
517
|
+
];
|
|
518
|
+
const promptText = interpolatePromptHookTemplate(
|
|
519
|
+
args.hook.prompt,
|
|
520
|
+
args.hookInput
|
|
521
|
+
);
|
|
522
|
+
const userPrompt = `${promptText}
|
|
523
|
+
|
|
524
|
+
# Hook input JSON
|
|
525
|
+
${hookValueForPrompt(args.hookInput)}`;
|
|
526
|
+
const response = await queryQuick({
|
|
527
|
+
systemPrompt,
|
|
528
|
+
userPrompt,
|
|
529
|
+
signal
|
|
530
|
+
});
|
|
531
|
+
return { exitCode: 0, stdout: extractAssistantText(response), stderr: "" };
|
|
532
|
+
} catch (err) {
|
|
533
|
+
return {
|
|
534
|
+
exitCode: 1,
|
|
535
|
+
stdout: "",
|
|
536
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
537
|
+
};
|
|
538
|
+
} finally {
|
|
539
|
+
cleanup();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function applyEnvFileToProcessEnv(envFilePath) {
|
|
543
|
+
let raw;
|
|
544
|
+
try {
|
|
545
|
+
raw = readFileSync(envFilePath, "utf8");
|
|
546
|
+
} catch {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const lines = raw.split(/\r?\n/);
|
|
550
|
+
for (const line of lines) {
|
|
551
|
+
const trimmed = line.trim();
|
|
552
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
553
|
+
const withoutExport = trimmed.startsWith("export ") ? trimmed.slice("export ".length).trim() : trimmed;
|
|
554
|
+
const eq = withoutExport.indexOf("=");
|
|
555
|
+
if (eq <= 0) continue;
|
|
556
|
+
const key = withoutExport.slice(0, eq).trim();
|
|
557
|
+
let value = withoutExport.slice(eq + 1).trim();
|
|
558
|
+
if (!key) continue;
|
|
559
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
560
|
+
value = value.slice(1, -1);
|
|
561
|
+
}
|
|
562
|
+
process.env[key] = value;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function loadPluginMatchers(projectDir, event) {
|
|
566
|
+
const plugins = getSessionPlugins();
|
|
567
|
+
if (plugins.length === 0) return [];
|
|
568
|
+
const out = [];
|
|
569
|
+
for (const plugin of plugins) {
|
|
570
|
+
for (const hookPath of plugin.hooksFiles ?? []) {
|
|
571
|
+
try {
|
|
572
|
+
const stat = statSync(hookPath);
|
|
573
|
+
const cached = pluginHooksCache.get(hookPath);
|
|
574
|
+
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
575
|
+
out.push(
|
|
576
|
+
...(cached.byEvent[event] ?? []).map((m) => ({
|
|
577
|
+
matcher: m.matcher,
|
|
578
|
+
hooks: m.hooks.map((h) => ({ ...h, pluginRoot: plugin.rootDir }))
|
|
579
|
+
}))
|
|
580
|
+
);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
const raw = readFileSync(hookPath, "utf8");
|
|
584
|
+
const parsed = JSON.parse(raw);
|
|
585
|
+
const hookObj = parsed && typeof parsed === "object" && parsed.hooks ? parsed.hooks : parsed;
|
|
586
|
+
const byEvent = parseHooksByEvent(hookObj);
|
|
587
|
+
pluginHooksCache.set(hookPath, { mtimeMs: stat.mtimeMs, byEvent });
|
|
588
|
+
out.push(
|
|
589
|
+
...(byEvent[event] ?? []).map((m) => ({
|
|
590
|
+
matcher: m.matcher,
|
|
591
|
+
hooks: m.hooks.map((h) => ({ ...h, pluginRoot: plugin.rootDir }))
|
|
592
|
+
}))
|
|
593
|
+
);
|
|
594
|
+
} catch (err) {
|
|
595
|
+
logError(err);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const inlineByEvent = loadInlinePluginHooksByEvent(plugin);
|
|
600
|
+
if (inlineByEvent?.[event]) {
|
|
601
|
+
out.push(
|
|
602
|
+
...(inlineByEvent[event] ?? []).map((m) => ({
|
|
603
|
+
matcher: m.matcher,
|
|
604
|
+
hooks: m.hooks.map((h) => ({ ...h, pluginRoot: plugin.rootDir }))
|
|
605
|
+
}))
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
function parseSessionStartHooks(value) {
|
|
612
|
+
if (!Array.isArray(value)) return [];
|
|
613
|
+
const out = [];
|
|
614
|
+
for (const item of value) {
|
|
615
|
+
if (!item || typeof item !== "object") continue;
|
|
616
|
+
const hooksRaw = item.hooks;
|
|
617
|
+
const hooks = Array.isArray(hooksRaw) ? hooksRaw.filter(isCommandHook) : [];
|
|
618
|
+
out.push(...hooks);
|
|
619
|
+
}
|
|
620
|
+
return out;
|
|
621
|
+
}
|
|
622
|
+
async function getSessionStartAdditionalContext(args) {
|
|
623
|
+
const sessionId = getKodeAgentSessionId();
|
|
624
|
+
const cached = sessionStartCache.get(sessionId);
|
|
625
|
+
if (cached) return cached.additionalContext;
|
|
626
|
+
const projectDir = args?.cwd ?? getCwd();
|
|
627
|
+
const plugins = getSessionPlugins();
|
|
628
|
+
if (plugins.length === 0) {
|
|
629
|
+
sessionStartCache.set(sessionId, { additionalContext: "" });
|
|
630
|
+
return "";
|
|
631
|
+
}
|
|
632
|
+
const envFileDir = mkdtempSync(join(tmpdir(), "kode-env-"));
|
|
633
|
+
const envFilePath = join(envFileDir, `${sessionId}.env`);
|
|
634
|
+
try {
|
|
635
|
+
writeFileSync(envFilePath, "", "utf8");
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
const additionalContexts = [];
|
|
639
|
+
try {
|
|
640
|
+
for (const plugin of plugins) {
|
|
641
|
+
for (const hookPath of plugin.hooksFiles ?? []) {
|
|
642
|
+
let hookObj;
|
|
643
|
+
try {
|
|
644
|
+
const raw = readFileSync(hookPath, "utf8");
|
|
645
|
+
const parsed = JSON.parse(raw);
|
|
646
|
+
hookObj = parsed && typeof parsed === "object" && parsed.hooks ? parsed.hooks : parsed;
|
|
647
|
+
} catch {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const hooks = parseSessionStartHooks(hookObj?.SessionStart).map((h) => ({
|
|
651
|
+
...h,
|
|
652
|
+
pluginRoot: plugin.rootDir
|
|
653
|
+
}));
|
|
654
|
+
if (hooks.length === 0) continue;
|
|
655
|
+
for (const hook of hooks) {
|
|
656
|
+
const payload = {
|
|
657
|
+
session_id: sessionId,
|
|
658
|
+
cwd: projectDir,
|
|
659
|
+
hook_event_name: "SessionStart",
|
|
660
|
+
permission_mode: coerceHookPermissionMode(args?.permissionMode)
|
|
661
|
+
};
|
|
662
|
+
const result = await runCommandHook({
|
|
663
|
+
command: hook.command,
|
|
664
|
+
stdinJson: payload,
|
|
665
|
+
cwd: projectDir,
|
|
666
|
+
env: {
|
|
667
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
668
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {},
|
|
669
|
+
CLAUDE_ENV_FILE: envFilePath
|
|
670
|
+
},
|
|
671
|
+
signal: args?.signal
|
|
672
|
+
});
|
|
673
|
+
if (result.exitCode !== 0) continue;
|
|
674
|
+
const injected = parseSessionStartAdditionalContext(result.stdout);
|
|
675
|
+
if (injected) additionalContexts.push(injected);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const inlineHooks = plugin.manifest?.hooks;
|
|
679
|
+
if (inlineHooks && typeof inlineHooks === "object" && !Array.isArray(inlineHooks)) {
|
|
680
|
+
const hookObj = inlineHooks.hooks && typeof inlineHooks.hooks === "object" && !Array.isArray(inlineHooks.hooks) ? inlineHooks.hooks : inlineHooks;
|
|
681
|
+
const hooks = parseSessionStartHooks(
|
|
682
|
+
hookObj?.SessionStart
|
|
683
|
+
).map((h) => ({
|
|
684
|
+
...h,
|
|
685
|
+
pluginRoot: plugin.rootDir
|
|
686
|
+
}));
|
|
687
|
+
if (hooks.length > 0) {
|
|
688
|
+
for (const hook of hooks) {
|
|
689
|
+
const payload = {
|
|
690
|
+
session_id: sessionId,
|
|
691
|
+
cwd: projectDir,
|
|
692
|
+
hook_event_name: "SessionStart",
|
|
693
|
+
permission_mode: coerceHookPermissionMode(args?.permissionMode)
|
|
694
|
+
};
|
|
695
|
+
const result = await runCommandHook({
|
|
696
|
+
command: hook.command,
|
|
697
|
+
stdinJson: payload,
|
|
698
|
+
cwd: projectDir,
|
|
699
|
+
env: {
|
|
700
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
701
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {},
|
|
702
|
+
CLAUDE_ENV_FILE: envFilePath
|
|
703
|
+
},
|
|
704
|
+
signal: args?.signal
|
|
705
|
+
});
|
|
706
|
+
if (result.exitCode !== 0) continue;
|
|
707
|
+
const injected = parseSessionStartAdditionalContext(result.stdout);
|
|
708
|
+
if (injected) additionalContexts.push(injected);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} finally {
|
|
714
|
+
applyEnvFileToProcessEnv(envFilePath);
|
|
715
|
+
try {
|
|
716
|
+
rmSync(envFileDir, { recursive: true, force: true });
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const additionalContext = additionalContexts.filter(Boolean).join("\n\n");
|
|
721
|
+
sessionStartCache.set(sessionId, { additionalContext });
|
|
722
|
+
return additionalContext;
|
|
723
|
+
}
|
|
724
|
+
async function runPreToolUseHooks(args) {
|
|
725
|
+
const projectDir = args.cwd ?? getCwd();
|
|
726
|
+
const matchers = [
|
|
727
|
+
...loadSettingsMatchers(projectDir, "PreToolUse"),
|
|
728
|
+
...loadPluginMatchers(projectDir, "PreToolUse")
|
|
729
|
+
];
|
|
730
|
+
if (matchers.length === 0) return { kind: "allow", warnings: [] };
|
|
731
|
+
const applicable = matchers.filter(
|
|
732
|
+
(m) => matcherMatchesTool(m.matcher, args.toolName)
|
|
733
|
+
);
|
|
734
|
+
if (applicable.length === 0) return { kind: "allow", warnings: [] };
|
|
735
|
+
const hookInput = {
|
|
736
|
+
session_id: getKodeAgentSessionId(),
|
|
737
|
+
transcript_path: args.transcriptPath,
|
|
738
|
+
cwd: projectDir,
|
|
739
|
+
hook_event_name: "PreToolUse",
|
|
740
|
+
permission_mode: coerceHookPermissionMode(args.permissionMode),
|
|
741
|
+
tool_name: args.toolName,
|
|
742
|
+
tool_input: args.toolInput,
|
|
743
|
+
tool_use_id: args.toolUseId
|
|
744
|
+
};
|
|
745
|
+
const warnings = [];
|
|
746
|
+
const systemMessages = [];
|
|
747
|
+
const additionalContexts = [];
|
|
748
|
+
let mergedUpdatedInput;
|
|
749
|
+
let permissionDecision = null;
|
|
750
|
+
const executions = [];
|
|
751
|
+
for (const entry of applicable) {
|
|
752
|
+
for (const hook of entry.hooks) {
|
|
753
|
+
if (hook.type === "prompt") {
|
|
754
|
+
executions.push(
|
|
755
|
+
runPromptHook({
|
|
756
|
+
hook,
|
|
757
|
+
hookEvent: "PreToolUse",
|
|
758
|
+
hookInput,
|
|
759
|
+
safeMode: args.safeMode ?? false,
|
|
760
|
+
parentSignal: args.signal,
|
|
761
|
+
fallbackTimeoutMs: 3e4
|
|
762
|
+
}).then((result) => ({ hook, result }))
|
|
763
|
+
);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const { signal, cleanup } = withHookTimeout({
|
|
767
|
+
timeoutSeconds: hook.timeout,
|
|
768
|
+
parentSignal: args.signal,
|
|
769
|
+
fallbackTimeoutMs: 6e4
|
|
770
|
+
});
|
|
771
|
+
executions.push(
|
|
772
|
+
runCommandHook({
|
|
773
|
+
command: hook.command,
|
|
774
|
+
stdinJson: hookInput,
|
|
775
|
+
cwd: projectDir,
|
|
776
|
+
env: {
|
|
777
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
778
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {}
|
|
779
|
+
},
|
|
780
|
+
signal
|
|
781
|
+
}).then((result) => ({ hook, result })).finally(cleanup)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const settled = await Promise.allSettled(executions);
|
|
786
|
+
for (const item of settled) {
|
|
787
|
+
if (item.status === "rejected") {
|
|
788
|
+
logError(item.reason);
|
|
789
|
+
warnings.push(`Hook failed to run: ${String(item.reason ?? "")}`);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const { hook, result } = item.value;
|
|
793
|
+
if (result.exitCode === 2) {
|
|
794
|
+
return {
|
|
795
|
+
kind: "block",
|
|
796
|
+
message: coerceHookMessage(result.stdout, result.stderr)
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (result.exitCode !== 0) {
|
|
800
|
+
warnings.push(coerceHookMessage(result.stdout, result.stderr));
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
const json = tryParseHookJson(result.stdout);
|
|
804
|
+
if (!json) continue;
|
|
805
|
+
if (typeof json.systemMessage === "string" && json.systemMessage.trim()) {
|
|
806
|
+
systemMessages.push(json.systemMessage.trim());
|
|
807
|
+
}
|
|
808
|
+
const additional = json.hookSpecificOutput && typeof json.hookSpecificOutput === "object" && typeof json.hookSpecificOutput.additionalContext === "string" ? String(json.hookSpecificOutput.additionalContext) : null;
|
|
809
|
+
if (additional && additional.trim()) {
|
|
810
|
+
additionalContexts.push(additional.trim());
|
|
811
|
+
}
|
|
812
|
+
const decision = normalizePermissionDecision(
|
|
813
|
+
json.hookSpecificOutput?.permissionDecision
|
|
814
|
+
);
|
|
815
|
+
if (decision === "deny") {
|
|
816
|
+
const msg = systemMessages.length > 0 ? systemMessages.join("\n\n") : coerceHookMessage(result.stdout, result.stderr);
|
|
817
|
+
return {
|
|
818
|
+
kind: "block",
|
|
819
|
+
message: msg,
|
|
820
|
+
systemMessages,
|
|
821
|
+
additionalContexts
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (decision === "ask") {
|
|
825
|
+
permissionDecision = "ask";
|
|
826
|
+
} else if (decision === "allow") {
|
|
827
|
+
if (!permissionDecision) permissionDecision = "allow";
|
|
828
|
+
}
|
|
829
|
+
const updated = json.hookSpecificOutput && typeof json.hookSpecificOutput === "object" && json.hookSpecificOutput.updatedInput && typeof json.hookSpecificOutput.updatedInput === "object" ? json.hookSpecificOutput.updatedInput : null;
|
|
830
|
+
if (updated) {
|
|
831
|
+
mergedUpdatedInput = { ...mergedUpdatedInput ?? {}, ...updated };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
kind: "allow",
|
|
836
|
+
warnings,
|
|
837
|
+
permissionDecision: permissionDecision === "allow" ? "allow" : permissionDecision === "ask" ? "ask" : void 0,
|
|
838
|
+
updatedInput: permissionDecision === "allow" ? mergedUpdatedInput : void 0,
|
|
839
|
+
systemMessages,
|
|
840
|
+
additionalContexts
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function runPostToolUseHooks(args) {
|
|
844
|
+
const projectDir = args.cwd ?? getCwd();
|
|
845
|
+
const matchers = [
|
|
846
|
+
...loadSettingsMatchers(projectDir, "PostToolUse"),
|
|
847
|
+
...loadPluginMatchers(projectDir, "PostToolUse")
|
|
848
|
+
];
|
|
849
|
+
if (matchers.length === 0) {
|
|
850
|
+
return { warnings: [], systemMessages: [], additionalContexts: [] };
|
|
851
|
+
}
|
|
852
|
+
const applicable = matchers.filter(
|
|
853
|
+
(m) => matcherMatchesTool(m.matcher, args.toolName)
|
|
854
|
+
);
|
|
855
|
+
if (applicable.length === 0) {
|
|
856
|
+
return { warnings: [], systemMessages: [], additionalContexts: [] };
|
|
857
|
+
}
|
|
858
|
+
const hookInput = {
|
|
859
|
+
session_id: getKodeAgentSessionId(),
|
|
860
|
+
transcript_path: args.transcriptPath,
|
|
861
|
+
cwd: projectDir,
|
|
862
|
+
hook_event_name: "PostToolUse",
|
|
863
|
+
permission_mode: coerceHookPermissionMode(args.permissionMode),
|
|
864
|
+
tool_name: args.toolName,
|
|
865
|
+
tool_input: args.toolInput,
|
|
866
|
+
tool_result: args.toolResult,
|
|
867
|
+
tool_response: args.toolResult,
|
|
868
|
+
tool_use_id: args.toolUseId
|
|
869
|
+
};
|
|
870
|
+
const warnings = [];
|
|
871
|
+
const systemMessages = [];
|
|
872
|
+
const additionalContexts = [];
|
|
873
|
+
const executions = [];
|
|
874
|
+
for (const entry of applicable) {
|
|
875
|
+
for (const hook of entry.hooks) {
|
|
876
|
+
if (hook.type === "prompt") {
|
|
877
|
+
executions.push(
|
|
878
|
+
runPromptHook({
|
|
879
|
+
hook,
|
|
880
|
+
hookEvent: "PostToolUse",
|
|
881
|
+
hookInput,
|
|
882
|
+
safeMode: args.safeMode ?? false,
|
|
883
|
+
parentSignal: args.signal,
|
|
884
|
+
fallbackTimeoutMs: 3e4
|
|
885
|
+
}).then((result) => ({ hook, result }))
|
|
886
|
+
);
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
const { signal, cleanup } = withHookTimeout({
|
|
890
|
+
timeoutSeconds: hook.timeout,
|
|
891
|
+
parentSignal: args.signal,
|
|
892
|
+
fallbackTimeoutMs: 6e4
|
|
893
|
+
});
|
|
894
|
+
executions.push(
|
|
895
|
+
runCommandHook({
|
|
896
|
+
command: hook.command,
|
|
897
|
+
stdinJson: hookInput,
|
|
898
|
+
cwd: projectDir,
|
|
899
|
+
env: {
|
|
900
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
901
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {}
|
|
902
|
+
},
|
|
903
|
+
signal
|
|
904
|
+
}).then((result) => ({ hook, result })).finally(cleanup)
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const settled = await Promise.allSettled(executions);
|
|
909
|
+
for (const item of settled) {
|
|
910
|
+
if (item.status === "rejected") {
|
|
911
|
+
logError(item.reason);
|
|
912
|
+
warnings.push(`Hook failed to run: ${String(item.reason ?? "")}`);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const { result } = item.value;
|
|
916
|
+
if (result.exitCode !== 0) {
|
|
917
|
+
warnings.push(coerceHookMessage(result.stdout, result.stderr));
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const json = tryParseHookJson(result.stdout);
|
|
921
|
+
if (!json) continue;
|
|
922
|
+
if (typeof json.systemMessage === "string" && json.systemMessage.trim()) {
|
|
923
|
+
systemMessages.push(json.systemMessage.trim());
|
|
924
|
+
}
|
|
925
|
+
const additional = json.hookSpecificOutput && typeof json.hookSpecificOutput === "object" && typeof json.hookSpecificOutput.additionalContext === "string" ? String(json.hookSpecificOutput.additionalContext) : null;
|
|
926
|
+
if (additional && additional.trim()) {
|
|
927
|
+
additionalContexts.push(additional.trim());
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return { warnings, systemMessages, additionalContexts };
|
|
931
|
+
}
|
|
932
|
+
async function runStopHooks(args) {
|
|
933
|
+
const projectDir = args.cwd ?? getCwd();
|
|
934
|
+
const matchers = [
|
|
935
|
+
...loadSettingsMatchers(projectDir, args.hookEvent),
|
|
936
|
+
...loadPluginMatchers(projectDir, args.hookEvent)
|
|
937
|
+
];
|
|
938
|
+
if (matchers.length === 0) {
|
|
939
|
+
return {
|
|
940
|
+
decision: "approve",
|
|
941
|
+
warnings: [],
|
|
942
|
+
systemMessages: [],
|
|
943
|
+
additionalContexts: []
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const applicable = matchers.filter((m) => matcherMatchesTool(m.matcher, "*"));
|
|
947
|
+
if (applicable.length === 0) {
|
|
948
|
+
return {
|
|
949
|
+
decision: "approve",
|
|
950
|
+
warnings: [],
|
|
951
|
+
systemMessages: [],
|
|
952
|
+
additionalContexts: []
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const hookInput = {
|
|
956
|
+
session_id: getKodeAgentSessionId(),
|
|
957
|
+
transcript_path: args.transcriptPath,
|
|
958
|
+
cwd: projectDir,
|
|
959
|
+
hook_event_name: args.hookEvent,
|
|
960
|
+
permission_mode: coerceHookPermissionMode(args.permissionMode),
|
|
961
|
+
reason: args.reason,
|
|
962
|
+
stop_hook_active: args.stopHookActive === true,
|
|
963
|
+
...args.hookEvent === "SubagentStop" ? { agent_id: args.agentId, agent_transcript_path: args.transcriptPath } : {}
|
|
964
|
+
};
|
|
965
|
+
const warnings = [];
|
|
966
|
+
const systemMessages = [];
|
|
967
|
+
const additionalContexts = [];
|
|
968
|
+
const executions = [];
|
|
969
|
+
for (const entry of applicable) {
|
|
970
|
+
for (const hook of entry.hooks) {
|
|
971
|
+
if (hook.type === "prompt") {
|
|
972
|
+
executions.push(
|
|
973
|
+
runPromptHook({
|
|
974
|
+
hook,
|
|
975
|
+
hookEvent: args.hookEvent,
|
|
976
|
+
hookInput,
|
|
977
|
+
safeMode: args.safeMode ?? false,
|
|
978
|
+
parentSignal: args.signal,
|
|
979
|
+
fallbackTimeoutMs: 3e4
|
|
980
|
+
}).then((result) => ({ hook, result }))
|
|
981
|
+
);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const { signal, cleanup } = withHookTimeout({
|
|
985
|
+
timeoutSeconds: hook.timeout,
|
|
986
|
+
parentSignal: args.signal,
|
|
987
|
+
fallbackTimeoutMs: 6e4
|
|
988
|
+
});
|
|
989
|
+
executions.push(
|
|
990
|
+
runCommandHook({
|
|
991
|
+
command: hook.command,
|
|
992
|
+
stdinJson: hookInput,
|
|
993
|
+
cwd: projectDir,
|
|
994
|
+
env: {
|
|
995
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
996
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {}
|
|
997
|
+
},
|
|
998
|
+
signal
|
|
999
|
+
}).then((result) => ({ hook, result })).finally(cleanup)
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const settled = await Promise.allSettled(executions);
|
|
1004
|
+
for (const item of settled) {
|
|
1005
|
+
if (item.status === "rejected") {
|
|
1006
|
+
logError(item.reason);
|
|
1007
|
+
warnings.push(`Hook failed to run: ${String(item.reason ?? "")}`);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const { result } = item.value;
|
|
1011
|
+
if (result.exitCode === 2) {
|
|
1012
|
+
return {
|
|
1013
|
+
decision: "block",
|
|
1014
|
+
message: coerceHookMessage(result.stdout, result.stderr),
|
|
1015
|
+
warnings,
|
|
1016
|
+
systemMessages,
|
|
1017
|
+
additionalContexts
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
if (result.exitCode !== 0) {
|
|
1021
|
+
warnings.push(coerceHookMessage(result.stdout, result.stderr));
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const json = tryParseHookJson(result.stdout);
|
|
1025
|
+
if (!json) continue;
|
|
1026
|
+
if (typeof json.systemMessage === "string" && json.systemMessage.trim()) {
|
|
1027
|
+
systemMessages.push(json.systemMessage.trim());
|
|
1028
|
+
}
|
|
1029
|
+
const additional = json.hookSpecificOutput && typeof json.hookSpecificOutput === "object" && typeof json.hookSpecificOutput.additionalContext === "string" ? String(json.hookSpecificOutput.additionalContext) : null;
|
|
1030
|
+
if (additional && additional.trim()) {
|
|
1031
|
+
additionalContexts.push(additional.trim());
|
|
1032
|
+
}
|
|
1033
|
+
const stopDecision = normalizeStopDecision(json.decision);
|
|
1034
|
+
if (stopDecision === "block") {
|
|
1035
|
+
const reason = typeof json.reason === "string" && json.reason.trim() ? json.reason.trim() : null;
|
|
1036
|
+
const msg = reason || (systemMessages.length > 0 ? systemMessages.join("\n\n") : coerceHookMessage(result.stdout, result.stderr));
|
|
1037
|
+
return {
|
|
1038
|
+
decision: "block",
|
|
1039
|
+
message: msg,
|
|
1040
|
+
warnings,
|
|
1041
|
+
systemMessages,
|
|
1042
|
+
additionalContexts
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return { decision: "approve", warnings, systemMessages, additionalContexts };
|
|
1047
|
+
}
|
|
1048
|
+
async function runUserPromptSubmitHooks(args) {
|
|
1049
|
+
const projectDir = args.cwd ?? getCwd();
|
|
1050
|
+
const matchers = [
|
|
1051
|
+
...loadSettingsMatchers(projectDir, "UserPromptSubmit"),
|
|
1052
|
+
...loadPluginMatchers(projectDir, "UserPromptSubmit")
|
|
1053
|
+
];
|
|
1054
|
+
if (matchers.length === 0) {
|
|
1055
|
+
return {
|
|
1056
|
+
decision: "allow",
|
|
1057
|
+
warnings: [],
|
|
1058
|
+
systemMessages: [],
|
|
1059
|
+
additionalContexts: []
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
const applicable = matchers.filter((m) => matcherMatchesTool(m.matcher, "*"));
|
|
1063
|
+
if (applicable.length === 0) {
|
|
1064
|
+
return {
|
|
1065
|
+
decision: "allow",
|
|
1066
|
+
warnings: [],
|
|
1067
|
+
systemMessages: [],
|
|
1068
|
+
additionalContexts: []
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
const hookInput = {
|
|
1072
|
+
session_id: getKodeAgentSessionId(),
|
|
1073
|
+
transcript_path: args.transcriptPath,
|
|
1074
|
+
cwd: projectDir,
|
|
1075
|
+
hook_event_name: "UserPromptSubmit",
|
|
1076
|
+
permission_mode: coerceHookPermissionMode(args.permissionMode),
|
|
1077
|
+
user_prompt: args.prompt,
|
|
1078
|
+
prompt: args.prompt
|
|
1079
|
+
};
|
|
1080
|
+
const warnings = [];
|
|
1081
|
+
const systemMessages = [];
|
|
1082
|
+
const additionalContexts = [];
|
|
1083
|
+
const executions = [];
|
|
1084
|
+
for (const entry of applicable) {
|
|
1085
|
+
for (const hook of entry.hooks) {
|
|
1086
|
+
if (hook.type === "prompt") {
|
|
1087
|
+
executions.push(
|
|
1088
|
+
runPromptHook({
|
|
1089
|
+
hook,
|
|
1090
|
+
hookEvent: "UserPromptSubmit",
|
|
1091
|
+
hookInput,
|
|
1092
|
+
safeMode: args.safeMode ?? false,
|
|
1093
|
+
parentSignal: args.signal,
|
|
1094
|
+
fallbackTimeoutMs: 3e4
|
|
1095
|
+
}).then((result) => ({ hook, result }))
|
|
1096
|
+
);
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
const { signal, cleanup } = withHookTimeout({
|
|
1100
|
+
timeoutSeconds: hook.timeout,
|
|
1101
|
+
parentSignal: args.signal,
|
|
1102
|
+
fallbackTimeoutMs: 6e4
|
|
1103
|
+
});
|
|
1104
|
+
executions.push(
|
|
1105
|
+
runCommandHook({
|
|
1106
|
+
command: hook.command,
|
|
1107
|
+
stdinJson: hookInput,
|
|
1108
|
+
cwd: projectDir,
|
|
1109
|
+
env: {
|
|
1110
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
1111
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {}
|
|
1112
|
+
},
|
|
1113
|
+
signal
|
|
1114
|
+
}).then((result) => ({ hook, result })).finally(cleanup)
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const settled = await Promise.allSettled(executions);
|
|
1119
|
+
for (const item of settled) {
|
|
1120
|
+
if (item.status === "rejected") {
|
|
1121
|
+
logError(item.reason);
|
|
1122
|
+
warnings.push(`Hook failed to run: ${String(item.reason ?? "")}`);
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
const { result } = item.value;
|
|
1126
|
+
if (result.exitCode === 2) {
|
|
1127
|
+
return {
|
|
1128
|
+
decision: "block",
|
|
1129
|
+
message: coerceHookMessage(result.stdout, result.stderr),
|
|
1130
|
+
warnings,
|
|
1131
|
+
systemMessages,
|
|
1132
|
+
additionalContexts
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
if (result.exitCode !== 0) {
|
|
1136
|
+
warnings.push(coerceHookMessage(result.stdout, result.stderr));
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const json = tryParseHookJson(result.stdout);
|
|
1140
|
+
if (!json) continue;
|
|
1141
|
+
if (typeof json.systemMessage === "string" && json.systemMessage.trim()) {
|
|
1142
|
+
systemMessages.push(json.systemMessage.trim());
|
|
1143
|
+
}
|
|
1144
|
+
const additional = json.hookSpecificOutput && typeof json.hookSpecificOutput === "object" && typeof json.hookSpecificOutput.additionalContext === "string" ? String(json.hookSpecificOutput.additionalContext) : null;
|
|
1145
|
+
if (additional && additional.trim()) {
|
|
1146
|
+
additionalContexts.push(additional.trim());
|
|
1147
|
+
}
|
|
1148
|
+
const stopDecision = normalizeStopDecision(json.decision);
|
|
1149
|
+
if (stopDecision === "block") {
|
|
1150
|
+
const reason = typeof json.reason === "string" && json.reason.trim() ? json.reason.trim() : null;
|
|
1151
|
+
const msg = reason || (systemMessages.length > 0 ? systemMessages.join("\n\n") : coerceHookMessage(result.stdout, result.stderr));
|
|
1152
|
+
return {
|
|
1153
|
+
decision: "block",
|
|
1154
|
+
message: msg,
|
|
1155
|
+
warnings,
|
|
1156
|
+
systemMessages,
|
|
1157
|
+
additionalContexts
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return { decision: "allow", warnings, systemMessages, additionalContexts };
|
|
1162
|
+
}
|
|
1163
|
+
async function runSessionEndHooks(args) {
|
|
1164
|
+
const projectDir = args.cwd ?? getCwd();
|
|
1165
|
+
const matchers = [
|
|
1166
|
+
...loadSettingsMatchers(projectDir, "SessionEnd"),
|
|
1167
|
+
...loadPluginMatchers(projectDir, "SessionEnd")
|
|
1168
|
+
];
|
|
1169
|
+
if (matchers.length === 0) return { warnings: [], systemMessages: [] };
|
|
1170
|
+
const applicable = matchers.filter((m) => matcherMatchesTool(m.matcher, "*"));
|
|
1171
|
+
if (applicable.length === 0) return { warnings: [], systemMessages: [] };
|
|
1172
|
+
const hookInput = {
|
|
1173
|
+
session_id: getKodeAgentSessionId(),
|
|
1174
|
+
transcript_path: args.transcriptPath,
|
|
1175
|
+
cwd: projectDir,
|
|
1176
|
+
hook_event_name: "SessionEnd",
|
|
1177
|
+
permission_mode: coerceHookPermissionMode(args.permissionMode),
|
|
1178
|
+
reason: args.reason
|
|
1179
|
+
};
|
|
1180
|
+
const warnings = [];
|
|
1181
|
+
const systemMessages = [];
|
|
1182
|
+
const executions = [];
|
|
1183
|
+
for (const entry of applicable) {
|
|
1184
|
+
for (const hook of entry.hooks) {
|
|
1185
|
+
if (hook.type === "prompt") {
|
|
1186
|
+
executions.push(
|
|
1187
|
+
runPromptHook({
|
|
1188
|
+
hook,
|
|
1189
|
+
hookEvent: "SessionEnd",
|
|
1190
|
+
hookInput,
|
|
1191
|
+
safeMode: args.safeMode ?? false,
|
|
1192
|
+
parentSignal: args.signal,
|
|
1193
|
+
fallbackTimeoutMs: 3e4
|
|
1194
|
+
}).then((result) => ({ hook, result }))
|
|
1195
|
+
);
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
const { signal, cleanup } = withHookTimeout({
|
|
1199
|
+
timeoutSeconds: hook.timeout,
|
|
1200
|
+
parentSignal: args.signal,
|
|
1201
|
+
fallbackTimeoutMs: 6e4
|
|
1202
|
+
});
|
|
1203
|
+
executions.push(
|
|
1204
|
+
runCommandHook({
|
|
1205
|
+
command: hook.command,
|
|
1206
|
+
stdinJson: hookInput,
|
|
1207
|
+
cwd: projectDir,
|
|
1208
|
+
env: {
|
|
1209
|
+
CLAUDE_PROJECT_DIR: projectDir,
|
|
1210
|
+
...hook.pluginRoot ? { CLAUDE_PLUGIN_ROOT: hook.pluginRoot } : {}
|
|
1211
|
+
},
|
|
1212
|
+
signal
|
|
1213
|
+
}).then((result) => ({ hook, result })).finally(cleanup)
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const settled = await Promise.allSettled(executions);
|
|
1218
|
+
for (const item of settled) {
|
|
1219
|
+
if (item.status === "rejected") {
|
|
1220
|
+
logError(item.reason);
|
|
1221
|
+
warnings.push(`Hook failed to run: ${String(item.reason ?? "")}`);
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
const { result } = item.value;
|
|
1225
|
+
if (result.exitCode !== 0) {
|
|
1226
|
+
warnings.push(coerceHookMessage(result.stdout, result.stderr));
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const json = tryParseHookJson(result.stdout);
|
|
1230
|
+
if (!json) continue;
|
|
1231
|
+
if (typeof json.systemMessage === "string" && json.systemMessage.trim()) {
|
|
1232
|
+
systemMessages.push(json.systemMessage.trim());
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return { warnings, systemMessages };
|
|
1236
|
+
}
|
|
1237
|
+
function __resetKodeHooksCacheForTests() {
|
|
1238
|
+
cache.clear();
|
|
1239
|
+
pluginHooksCache.clear();
|
|
1240
|
+
sessionStartCache.clear();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
export {
|
|
1244
|
+
updateHookTranscriptForMessages,
|
|
1245
|
+
drainHookSystemPromptAdditions,
|
|
1246
|
+
getHookTranscriptPath,
|
|
1247
|
+
queueHookSystemMessages,
|
|
1248
|
+
queueHookAdditionalContexts,
|
|
1249
|
+
getSessionStartAdditionalContext,
|
|
1250
|
+
runPreToolUseHooks,
|
|
1251
|
+
runPostToolUseHooks,
|
|
1252
|
+
runStopHooks,
|
|
1253
|
+
runUserPromptSubmitHooks,
|
|
1254
|
+
runSessionEndHooks,
|
|
1255
|
+
__resetKodeHooksCacheForTests
|
|
1256
|
+
};
|