@mariozechner/pi-coding-agent 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/CHANGELOG.md +224 -18
  2. package/README.md +233 -105
  3. package/dist/cli/args.d.ts +3 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +13 -18
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +3 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +39 -50
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +166 -197
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  16. package/dist/core/compaction/branch-summarization.js +3 -3
  17. package/dist/core/compaction/branch-summarization.js.map +1 -1
  18. package/dist/core/compaction/compaction.d.ts +1 -1
  19. package/dist/core/compaction/compaction.d.ts.map +1 -1
  20. package/dist/core/compaction/compaction.js +6 -5
  21. package/dist/core/compaction/compaction.js.map +1 -1
  22. package/dist/core/event-bus.d.ts +9 -0
  23. package/dist/core/event-bus.d.ts.map +1 -0
  24. package/dist/core/event-bus.js +25 -0
  25. package/dist/core/event-bus.js.map +1 -0
  26. package/dist/core/exec.d.ts +1 -1
  27. package/dist/core/exec.d.ts.map +1 -1
  28. package/dist/core/exec.js +1 -1
  29. package/dist/core/exec.js.map +1 -1
  30. package/dist/core/extensions/index.d.ts +10 -0
  31. package/dist/core/extensions/index.d.ts.map +1 -0
  32. package/dist/core/extensions/index.js +9 -0
  33. package/dist/core/extensions/index.js.map +1 -0
  34. package/dist/core/extensions/loader.d.ts +21 -0
  35. package/dist/core/extensions/loader.d.ts.map +1 -0
  36. package/dist/core/extensions/loader.js +400 -0
  37. package/dist/core/extensions/loader.js.map +1 -0
  38. package/dist/core/extensions/runner.d.ts +88 -0
  39. package/dist/core/extensions/runner.d.ts.map +1 -0
  40. package/dist/core/{hooks → extensions}/runner.js +52 -141
  41. package/dist/core/extensions/runner.js.map +1 -0
  42. package/dist/core/extensions/types.d.ts +461 -0
  43. package/dist/core/extensions/types.d.ts.map +1 -0
  44. package/dist/core/{hooks → extensions}/types.js +7 -4
  45. package/dist/core/extensions/types.js.map +1 -0
  46. package/dist/core/extensions/wrapper.d.ts +25 -0
  47. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  48. package/dist/core/{hooks/tool-wrapper.js → extensions/wrapper.js} +39 -24
  49. package/dist/core/extensions/wrapper.js.map +1 -0
  50. package/dist/core/index.d.ts +2 -2
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/core/index.js +3 -2
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/messages.d.ts +7 -7
  55. package/dist/core/messages.d.ts.map +1 -1
  56. package/dist/core/messages.js +4 -4
  57. package/dist/core/messages.js.map +1 -1
  58. package/dist/core/prompt-templates.d.ts +40 -0
  59. package/dist/core/prompt-templates.d.ts.map +1 -0
  60. package/dist/core/{slash-commands.js → prompt-templates.js} +31 -31
  61. package/dist/core/prompt-templates.js.map +1 -0
  62. package/dist/core/sdk.d.ts +29 -52
  63. package/dist/core/sdk.d.ts.map +1 -1
  64. package/dist/core/sdk.js +111 -211
  65. package/dist/core/sdk.js.map +1 -1
  66. package/dist/core/session-manager.d.ts +17 -17
  67. package/dist/core/session-manager.d.ts.map +1 -1
  68. package/dist/core/session-manager.js +25 -10
  69. package/dist/core/session-manager.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +3 -6
  71. package/dist/core/settings-manager.d.ts.map +1 -1
  72. package/dist/core/settings-manager.js +4 -11
  73. package/dist/core/settings-manager.js.map +1 -1
  74. package/dist/core/system-prompt.d.ts.map +1 -1
  75. package/dist/core/system-prompt.js +4 -2
  76. package/dist/core/system-prompt.js.map +1 -1
  77. package/dist/index.d.ts +4 -5
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +5 -6
  80. package/dist/index.js.map +1 -1
  81. package/dist/main.d.ts.map +1 -1
  82. package/dist/main.js +36 -33
  83. package/dist/main.js.map +1 -1
  84. package/dist/migrations.d.ts +7 -2
  85. package/dist/migrations.d.ts.map +1 -1
  86. package/dist/migrations.js +93 -4
  87. package/dist/migrations.js.map +1 -1
  88. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  89. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  91. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  92. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  93. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  95. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  96. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  97. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  99. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  100. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  101. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/custom-editor.js +4 -4
  103. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  104. package/dist/modes/interactive/components/custom-message.d.ts +18 -0
  105. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/{hook-message.js → custom-message.js} +3 -3
  107. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  108. package/dist/modes/interactive/components/dynamic-border.d.ts +2 -2
  109. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/dynamic-border.js +2 -2
  111. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  112. package/dist/modes/interactive/components/{hook-editor.d.ts → extension-editor.d.ts} +3 -3
  113. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/{hook-editor.js → extension-editor.js} +4 -4
  115. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  116. package/dist/modes/interactive/components/{hook-input.d.ts → extension-input.d.ts} +3 -3
  117. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  118. package/dist/modes/interactive/components/{hook-input.js → extension-input.js} +3 -3
  119. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  120. package/dist/modes/interactive/components/{hook-selector.d.ts → extension-selector.d.ts} +3 -3
  121. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/{hook-selector.js → extension-selector.js} +3 -3
  123. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  124. package/dist/modes/interactive/components/footer.d.ts +3 -3
  125. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/footer.js +8 -8
  127. package/dist/modes/interactive/components/footer.js.map +1 -1
  128. package/dist/modes/interactive/components/tool-execution.d.ts +3 -3
  129. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/tool-execution.js +9 -9
  131. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.d.ts +37 -44
  133. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.js +143 -189
  135. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  136. package/dist/modes/print-mode.d.ts.map +1 -1
  137. package/dist/modes/print-mode.js +10 -33
  138. package/dist/modes/print-mode.js.map +1 -1
  139. package/dist/modes/rpc/rpc-client.d.ts +3 -3
  140. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-client.js +3 -3
  142. package/dist/modes/rpc/rpc-client.js.map +1 -1
  143. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  144. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +33 -57
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +16 -16
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/docs/extensions.md +1053 -0
  151. package/docs/rpc.md +4 -4
  152. package/docs/sdk.md +62 -93
  153. package/docs/session.md +22 -19
  154. package/docs/skills.md +1 -1
  155. package/docs/tui.md +1 -1
  156. package/examples/README.md +9 -15
  157. package/examples/extensions/README.md +141 -0
  158. package/examples/{hooks → extensions}/auto-commit-on-exit.ts +3 -3
  159. package/examples/extensions/chalk-logger.ts +26 -0
  160. package/examples/{hooks → extensions}/confirm-destructive.ts +3 -3
  161. package/examples/{hooks → extensions}/custom-compaction.ts +6 -6
  162. package/examples/{hooks → extensions}/dirty-repo-guard.ts +8 -4
  163. package/examples/{hooks → extensions}/file-trigger.ts +3 -3
  164. package/examples/{hooks → extensions}/git-checkpoint.ts +3 -3
  165. package/examples/{hooks → extensions}/handoff.ts +3 -3
  166. package/examples/extensions/hello.ts +25 -0
  167. package/examples/{hooks → extensions}/permission-gate.ts +3 -3
  168. package/examples/{hooks → extensions}/pirate.ts +5 -5
  169. package/examples/{hooks → extensions}/plan-mode.ts +6 -6
  170. package/examples/{hooks → extensions}/protected-paths.ts +3 -3
  171. package/examples/{hooks → extensions}/qna.ts +3 -3
  172. package/examples/{custom-tools/question/index.ts → extensions/question.ts} +13 -17
  173. package/examples/{hooks → extensions}/snake.ts +3 -3
  174. package/examples/{hooks → extensions}/status-line.ts +3 -3
  175. package/examples/{custom-tools → extensions}/subagent/README.md +15 -15
  176. package/examples/{custom-tools → extensions}/subagent/index.ts +22 -43
  177. package/examples/{custom-tools/todo/index.ts → extensions/todo.ts} +122 -39
  178. package/examples/{hooks → extensions}/tools.ts +5 -5
  179. package/examples/extensions/with-deps/index.ts +40 -0
  180. package/examples/extensions/with-deps/package-lock.json +31 -0
  181. package/examples/extensions/with-deps/package.json +16 -0
  182. package/examples/sdk/01-minimal.ts +1 -1
  183. package/examples/sdk/05-tools.ts +7 -41
  184. package/examples/sdk/06-extensions.ts +81 -0
  185. package/examples/sdk/08-prompt-templates.ts +42 -0
  186. package/examples/sdk/12-full-control.ts +10 -29
  187. package/examples/sdk/README.md +5 -5
  188. package/package.json +4 -4
  189. package/dist/core/custom-tools/index.d.ts +0 -7
  190. package/dist/core/custom-tools/index.d.ts.map +0 -1
  191. package/dist/core/custom-tools/index.js +0 -6
  192. package/dist/core/custom-tools/index.js.map +0 -1
  193. package/dist/core/custom-tools/loader.d.ts +0 -30
  194. package/dist/core/custom-tools/loader.d.ts.map +0 -1
  195. package/dist/core/custom-tools/loader.js +0 -276
  196. package/dist/core/custom-tools/loader.js.map +0 -1
  197. package/dist/core/custom-tools/types.d.ts +0 -144
  198. package/dist/core/custom-tools/types.d.ts.map +0 -1
  199. package/dist/core/custom-tools/types.js +0 -8
  200. package/dist/core/custom-tools/types.js.map +0 -1
  201. package/dist/core/custom-tools/wrapper.d.ts +0 -15
  202. package/dist/core/custom-tools/wrapper.d.ts.map +0 -1
  203. package/dist/core/custom-tools/wrapper.js +0 -23
  204. package/dist/core/custom-tools/wrapper.js.map +0 -1
  205. package/dist/core/hooks/index.d.ts +0 -6
  206. package/dist/core/hooks/index.d.ts.map +0 -1
  207. package/dist/core/hooks/index.js +0 -6
  208. package/dist/core/hooks/index.js.map +0 -1
  209. package/dist/core/hooks/loader.d.ts +0 -146
  210. package/dist/core/hooks/loader.d.ts.map +0 -1
  211. package/dist/core/hooks/loader.js +0 -275
  212. package/dist/core/hooks/loader.js.map +0 -1
  213. package/dist/core/hooks/runner.d.ts +0 -173
  214. package/dist/core/hooks/runner.d.ts.map +0 -1
  215. package/dist/core/hooks/runner.js.map +0 -1
  216. package/dist/core/hooks/tool-wrapper.d.ts +0 -17
  217. package/dist/core/hooks/tool-wrapper.d.ts.map +0 -1
  218. package/dist/core/hooks/tool-wrapper.js.map +0 -1
  219. package/dist/core/hooks/types.d.ts +0 -767
  220. package/dist/core/hooks/types.d.ts.map +0 -1
  221. package/dist/core/hooks/types.js.map +0 -1
  222. package/dist/core/slash-commands.d.ts +0 -40
  223. package/dist/core/slash-commands.d.ts.map +0 -1
  224. package/dist/core/slash-commands.js.map +0 -1
  225. package/dist/modes/interactive/components/hook-editor.d.ts.map +0 -1
  226. package/dist/modes/interactive/components/hook-editor.js.map +0 -1
  227. package/dist/modes/interactive/components/hook-input.d.ts.map +0 -1
  228. package/dist/modes/interactive/components/hook-input.js.map +0 -1
  229. package/dist/modes/interactive/components/hook-message.d.ts +0 -18
  230. package/dist/modes/interactive/components/hook-message.d.ts.map +0 -1
  231. package/dist/modes/interactive/components/hook-message.js.map +0 -1
  232. package/dist/modes/interactive/components/hook-selector.d.ts.map +0 -1
  233. package/dist/modes/interactive/components/hook-selector.js.map +0 -1
  234. package/docs/custom-tools.md +0 -514
  235. package/docs/extension-loading.md +0 -1004
  236. package/docs/hooks.md +0 -979
  237. package/docs/session-tree-plan.md +0 -441
  238. package/examples/custom-tools/README.md +0 -114
  239. package/examples/custom-tools/hello/index.ts +0 -21
  240. package/examples/hooks/README.md +0 -60
  241. package/examples/hooks/todo/index.ts +0 -134
  242. package/examples/sdk/06-hooks.ts +0 -61
  243. package/examples/sdk/08-slash-commands.ts +0 -42
  244. /package/examples/{custom-tools → extensions}/subagent/agents/planner.md +0 -0
  245. /package/examples/{custom-tools → extensions}/subagent/agents/reviewer.md +0 -0
  246. /package/examples/{custom-tools → extensions}/subagent/agents/scout.md +0 -0
  247. /package/examples/{custom-tools → extensions}/subagent/agents/worker.md +0 -0
  248. /package/examples/{custom-tools → extensions}/subagent/agents.ts +0 -0
  249. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement-and-review.md +0 -0
  250. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement.md +0 -0
  251. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/scout-and-plan.md +0 -0
@@ -1,275 +0,0 @@
1
- /**
2
- * Hook loader - loads TypeScript hook modules using jiti.
3
- */
4
- import * as fs from "node:fs";
5
- import { createRequire } from "node:module";
6
- import * as os from "node:os";
7
- import * as path from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import { createJiti } from "jiti";
10
- import { getAgentDir } from "../../config.js";
11
- import { execCommand } from "./runner.js";
12
- // Create require function to resolve module paths at runtime
13
- const require = createRequire(import.meta.url);
14
- // Lazily computed aliases - resolved at runtime to handle global installs
15
- let _aliases = null;
16
- function getAliases() {
17
- if (_aliases)
18
- return _aliases;
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const packageIndex = path.resolve(__dirname, "../..", "index.js");
21
- // For typebox, we need the package root directory (not the entry file)
22
- // because jiti's alias is prefix-based: imports like "@sinclair/typebox/compiler"
23
- // get the alias prepended. If we alias to the entry file (.../build/cjs/index.js),
24
- // then "@sinclair/typebox/compiler" becomes ".../build/cjs/index.js/compiler" (invalid).
25
- // By aliasing to the package root, it becomes ".../typebox/compiler" which resolves correctly.
26
- const typeboxEntry = require.resolve("@sinclair/typebox");
27
- const typeboxRoot = typeboxEntry.replace(/\/build\/cjs\/index\.js$/, "");
28
- _aliases = {
29
- "@mariozechner/pi-coding-agent": packageIndex,
30
- "@mariozechner/pi-coding-agent/hooks": path.resolve(__dirname, "index.js"),
31
- "@mariozechner/pi-tui": require.resolve("@mariozechner/pi-tui"),
32
- "@mariozechner/pi-ai": require.resolve("@mariozechner/pi-ai"),
33
- "@sinclair/typebox": typeboxRoot,
34
- };
35
- return _aliases;
36
- }
37
- const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
38
- function normalizeUnicodeSpaces(str) {
39
- return str.replace(UNICODE_SPACES, " ");
40
- }
41
- function expandPath(p) {
42
- const normalized = normalizeUnicodeSpaces(p);
43
- if (normalized.startsWith("~/")) {
44
- return path.join(os.homedir(), normalized.slice(2));
45
- }
46
- if (normalized.startsWith("~")) {
47
- return path.join(os.homedir(), normalized.slice(1));
48
- }
49
- return normalized;
50
- }
51
- /**
52
- * Resolve hook path.
53
- * - Absolute paths used as-is
54
- * - Paths starting with ~ expanded to home directory
55
- * - Relative paths resolved from cwd
56
- */
57
- function resolveHookPath(hookPath, cwd) {
58
- const expanded = expandPath(hookPath);
59
- if (path.isAbsolute(expanded)) {
60
- return expanded;
61
- }
62
- // Relative paths resolved from cwd
63
- return path.resolve(cwd, expanded);
64
- }
65
- /**
66
- * Create a HookAPI instance that collects handlers, renderers, and commands.
67
- * Returns the API, maps, and functions to set handlers later.
68
- */
69
- function createHookAPI(handlers, cwd, hookPath) {
70
- let sendMessageHandler = () => {
71
- // Default no-op until mode sets the handler
72
- };
73
- let appendEntryHandler = () => {
74
- // Default no-op until mode sets the handler
75
- };
76
- let getActiveToolsHandler = () => [];
77
- let getAllToolsHandler = () => [];
78
- let setActiveToolsHandler = () => {
79
- // Default no-op until mode sets the handler
80
- };
81
- const messageRenderers = new Map();
82
- const commands = new Map();
83
- const flags = new Map();
84
- const flagValues = new Map();
85
- const shortcuts = new Map();
86
- // Cast to HookAPI - the implementation is more general (string event names)
87
- // but the interface has specific overloads for type safety in hooks
88
- const api = {
89
- on(event, handler) {
90
- const list = handlers.get(event) ?? [];
91
- list.push(handler);
92
- handlers.set(event, list);
93
- },
94
- sendMessage(message, options) {
95
- sendMessageHandler(message, options);
96
- },
97
- appendEntry(customType, data) {
98
- appendEntryHandler(customType, data);
99
- },
100
- registerMessageRenderer(customType, renderer) {
101
- messageRenderers.set(customType, renderer);
102
- },
103
- registerCommand(name, options) {
104
- commands.set(name, { name, ...options });
105
- },
106
- exec(command, args, options) {
107
- return execCommand(command, args, options?.cwd ?? cwd, options);
108
- },
109
- getActiveTools() {
110
- return getActiveToolsHandler();
111
- },
112
- getAllTools() {
113
- return getAllToolsHandler();
114
- },
115
- setActiveTools(toolNames) {
116
- setActiveToolsHandler(toolNames);
117
- },
118
- registerFlag(name, options) {
119
- flags.set(name, { name, hookPath, ...options });
120
- // Set default value if provided
121
- if (options.default !== undefined) {
122
- flagValues.set(name, options.default);
123
- }
124
- },
125
- getFlag(name) {
126
- return flagValues.get(name);
127
- },
128
- registerShortcut(shortcut, options) {
129
- shortcuts.set(shortcut, { shortcut, hookPath, ...options });
130
- },
131
- };
132
- return {
133
- api,
134
- messageRenderers,
135
- commands,
136
- flags,
137
- flagValues,
138
- shortcuts,
139
- setSendMessageHandler: (handler) => {
140
- sendMessageHandler = handler;
141
- },
142
- setAppendEntryHandler: (handler) => {
143
- appendEntryHandler = handler;
144
- },
145
- setGetActiveToolsHandler: (handler) => {
146
- getActiveToolsHandler = handler;
147
- },
148
- setGetAllToolsHandler: (handler) => {
149
- getAllToolsHandler = handler;
150
- },
151
- setSetActiveToolsHandler: (handler) => {
152
- setActiveToolsHandler = handler;
153
- },
154
- setFlagValue: (name, value) => {
155
- flagValues.set(name, value);
156
- },
157
- };
158
- }
159
- /**
160
- * Load a single hook module using jiti.
161
- */
162
- async function loadHook(hookPath, cwd) {
163
- const resolvedPath = resolveHookPath(hookPath, cwd);
164
- try {
165
- // Create jiti instance for TypeScript/ESM loading
166
- // Use aliases to resolve package imports since hooks are loaded from user directories
167
- // (e.g. ~/.pi/agent/hooks) but import from packages installed with pi-coding-agent
168
- const jiti = createJiti(import.meta.url, {
169
- alias: getAliases(),
170
- });
171
- // Import the module
172
- const module = await jiti.import(resolvedPath, { default: true });
173
- const factory = module;
174
- if (typeof factory !== "function") {
175
- return { hook: null, error: "Hook must export a default function" };
176
- }
177
- // Create handlers map and API
178
- const handlers = new Map();
179
- const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setFlagValue, } = createHookAPI(handlers, cwd, hookPath);
180
- // Call factory to register handlers
181
- factory(api);
182
- return {
183
- hook: {
184
- path: hookPath,
185
- resolvedPath,
186
- handlers,
187
- messageRenderers,
188
- commands,
189
- flags,
190
- flagValues,
191
- shortcuts,
192
- setSendMessageHandler,
193
- setAppendEntryHandler,
194
- setGetActiveToolsHandler,
195
- setGetAllToolsHandler,
196
- setSetActiveToolsHandler,
197
- setFlagValue,
198
- },
199
- error: null,
200
- };
201
- }
202
- catch (err) {
203
- const message = err instanceof Error ? err.message : String(err);
204
- return { hook: null, error: `Failed to load hook: ${message}` };
205
- }
206
- }
207
- /**
208
- * Load all hooks from configuration.
209
- * @param paths - Array of hook file paths
210
- * @param cwd - Current working directory for resolving relative paths
211
- */
212
- export async function loadHooks(paths, cwd) {
213
- const hooks = [];
214
- const errors = [];
215
- for (const hookPath of paths) {
216
- const { hook, error } = await loadHook(hookPath, cwd);
217
- if (error) {
218
- errors.push({ path: hookPath, error });
219
- continue;
220
- }
221
- if (hook) {
222
- hooks.push(hook);
223
- }
224
- }
225
- return { hooks, errors };
226
- }
227
- /**
228
- * Discover hook files from a directory.
229
- * Returns all .ts files (and symlinks to .ts files) in the directory (non-recursive).
230
- */
231
- function discoverHooksInDir(dir) {
232
- if (!fs.existsSync(dir)) {
233
- return [];
234
- }
235
- try {
236
- const entries = fs.readdirSync(dir, { withFileTypes: true });
237
- return entries
238
- .filter((e) => (e.isFile() || e.isSymbolicLink()) && e.name.endsWith(".ts"))
239
- .map((e) => path.join(dir, e.name));
240
- }
241
- catch {
242
- return [];
243
- }
244
- }
245
- /**
246
- * Discover and load hooks from standard locations:
247
- * 1. agentDir/hooks/*.ts (global)
248
- * 2. cwd/.pi/hooks/*.ts (project-local)
249
- *
250
- * Plus any explicitly configured paths from settings.
251
- */
252
- export async function discoverAndLoadHooks(configuredPaths, cwd, agentDir = getAgentDir()) {
253
- const allPaths = [];
254
- const seen = new Set();
255
- // Helper to add paths without duplicates
256
- const addPaths = (paths) => {
257
- for (const p of paths) {
258
- const resolved = path.resolve(p);
259
- if (!seen.has(resolved)) {
260
- seen.add(resolved);
261
- allPaths.push(p);
262
- }
263
- }
264
- };
265
- // 1. Global hooks: agentDir/hooks/
266
- const globalHooksDir = path.join(agentDir, "hooks");
267
- addPaths(discoverHooksInDir(globalHooksDir));
268
- // 2. Project-local hooks: cwd/.pi/hooks/
269
- const localHooksDir = path.join(cwd, ".pi", "hooks");
270
- addPaths(discoverHooksInDir(localHooksDir));
271
- // 3. Explicitly configured paths (can override/add)
272
- addPaths(configuredPaths.map((p) => resolveHookPath(p, cwd)));
273
- return loadHooks(allPaths, cwd);
274
- }
275
- //# sourceMappingURL=loader.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/core/hooks/loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAU1C,6DAA6D;AAC7D,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,0EAA0E;AAC1E,IAAI,QAAQ,GAAkC,IAAI,CAAC;AACnD,SAAS,UAAU,GAA2B;IAC7C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAElE,uEAAuE;IACvE,kFAAkF;IAClF,mFAAmF;IACnF,yFAAyF;IACzF,+FAA+F;IAC/F,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC;IAEzE,QAAQ,GAAG;QACV,+BAA+B,EAAE,YAAY;QAC7C,qCAAqC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC;QAC1E,sBAAsB,EAAE,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC;QAC/D,qBAAqB,EAAE,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC;QAC7D,mBAAmB,EAAE,WAAW;KAChC,CAAC;IACF,OAAO,QAAQ,CAAC;AAAA,CAChB;AAkID,MAAM,cAAc,GAAG,0CAA0C,CAAC;AAElE,SAAS,sBAAsB,CAAC,GAAW,EAAU;IACpD,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;AAAA,CACxC;AAED,SAAS,UAAU,CAAC,CAAS,EAAU;IACtC,MAAM,UAAU,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,UAAU,CAAC;AAAA,CAClB;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,GAAW,EAAU;IAC/D,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEtC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,mCAAmC;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAAA,CACnC;AAED;;;GAGG;AACH,SAAS,aAAa,CACrB,QAAkC,EAClC,GAAW,EACX,QAAgB,EAcf;IACD,IAAI,kBAAkB,GAAuB,GAAG,EAAE,CAAC;QAClD,4CAA4C;IADO,CAEnD,CAAC;IACF,IAAI,kBAAkB,GAAuB,GAAG,EAAE,CAAC;QAClD,4CAA4C;IADO,CAEnD,CAAC;IACF,IAAI,qBAAqB,GAA0B,GAAG,EAAE,CAAC,EAAE,CAAC;IAC5D,IAAI,kBAAkB,GAAuB,GAAG,EAAE,CAAC,EAAE,CAAC;IACtD,IAAI,qBAAqB,GAA0B,GAAG,EAAE,CAAC;QACxD,4CAA4C;IADa,CAEzD,CAAC;IACF,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA+B,CAAC;IAChE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA6B,CAAC;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC1C,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEjD,4EAA4E;IAC5E,oEAAoE;IACpE,MAAM,GAAG,GAAG;QACX,EAAE,CAAC,KAAa,EAAE,OAAkB,EAAQ;YAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAAA,CAC1B;QACD,WAAW,CACV,OAAuB,EACvB,OAAqE,EAC9D;YACP,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAAA,CACrC;QACD,WAAW,CAAc,UAAkB,EAAE,IAAQ,EAAQ;YAC5D,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAAA,CACrC;QACD,uBAAuB,CAAc,UAAkB,EAAE,QAAgC,EAAQ;YAChG,gBAAgB,CAAC,GAAG,CAAC,UAAU,EAAE,QAA+B,CAAC,CAAC;QAAA,CAClE;QACD,eAAe,CAAC,IAAY,EAAE,OAAwE,EAAQ;YAC7G,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QAAA,CACzC;QACD,IAAI,CAAC,OAAe,EAAE,IAAc,EAAE,OAAqB,EAAE;YAC5D,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,EAAE,OAAO,CAAC,CAAC;QAAA,CAChE;QACD,cAAc,GAAa;YAC1B,OAAO,qBAAqB,EAAE,CAAC;QAAA,CAC/B;QACD,WAAW,GAAa;YACvB,OAAO,kBAAkB,EAAE,CAAC;QAAA,CAC5B;QACD,cAAc,CAAC,SAAmB,EAAQ;YACzC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAAA,CACjC;QACD,YAAY,CACX,IAAY,EACZ,OAAyF,EAClF;YACP,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;YAChD,gCAAgC;YAChC,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACnC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACvC,CAAC;QAAA,CACD;QACD,OAAO,CAAC,IAAY,EAAgC;YACnD,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAAA,CAC5B;QACD,gBAAgB,CACf,QAAe,EACf,OAGC,EACM;YACP,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QAAA,CAC5D;KACU,CAAC;IAEb,OAAO;QACN,GAAG;QACH,gBAAgB;QAChB,QAAQ;QACR,KAAK;QACL,UAAU;QACV,SAAS;QACT,qBAAqB,EAAE,CAAC,OAA2B,EAAE,EAAE,CAAC;YACvD,kBAAkB,GAAG,OAAO,CAAC;QAAA,CAC7B;QACD,qBAAqB,EAAE,CAAC,OAA2B,EAAE,EAAE,CAAC;YACvD,kBAAkB,GAAG,OAAO,CAAC;QAAA,CAC7B;QACD,wBAAwB,EAAE,CAAC,OAA8B,EAAE,EAAE,CAAC;YAC7D,qBAAqB,GAAG,OAAO,CAAC;QAAA,CAChC;QACD,qBAAqB,EAAE,CAAC,OAA2B,EAAE,EAAE,CAAC;YACvD,kBAAkB,GAAG,OAAO,CAAC;QAAA,CAC7B;QACD,wBAAwB,EAAE,CAAC,OAA8B,EAAE,EAAE,CAAC;YAC7D,qBAAqB,GAAG,OAAO,CAAC;QAAA,CAChC;QACD,YAAY,EAAE,CAAC,IAAY,EAAE,KAAuB,EAAE,EAAE,CAAC;YACxD,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAAA,CAC5B;KACD,CAAC;AAAA,CACF;AAED;;GAEG;AACH,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,GAAW,EAA8D;IAClH,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAEpD,IAAI,CAAC;QACJ,kDAAkD;QAClD,sFAAsF;QACtF,mFAAmF;QACnF,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE;YACxC,KAAK,EAAE,UAAU,EAAE;SACnB,CAAC,CAAC;QAEH,oBAAoB;QACpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,MAAqB,CAAC;QAEtC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YACnC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC;QACrE,CAAC;QAED,8BAA8B;QAC9B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;QAChD,MAAM,EACL,GAAG,EACH,gBAAgB,EAChB,QAAQ,EACR,KAAK,EACL,UAAU,EACV,SAAS,EACT,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,YAAY,GACZ,GAAG,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QAE3C,oCAAoC;QACpC,OAAO,CAAC,GAAG,CAAC,CAAC;QAEb,OAAO;YACN,IAAI,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,YAAY;gBACZ,QAAQ;gBACR,gBAAgB;gBAChB,QAAQ;gBACR,KAAK;gBACL,UAAU;gBACV,SAAS;gBACT,qBAAqB;gBACrB,qBAAqB;gBACrB,wBAAwB;gBACxB,qBAAqB;gBACrB,wBAAwB;gBACxB,YAAY;aACZ;YACD,KAAK,EAAE,IAAI;SACX,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,wBAAwB,OAAO,EAAE,EAAE,CAAC;IACjE,CAAC;AAAA,CACD;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAe,EAAE,GAAW,EAA4B;IACvF,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,MAAM,GAA2C,EAAE,CAAC;IAE1D,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAEtD,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;YACvC,SAAS;QACV,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACF,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAAA,CACzB;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,GAAW,EAAY;IAClD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO,OAAO;aACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;aAC3E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,eAAyB,EACzB,GAAW,EACX,QAAQ,GAAW,WAAW,EAAE,EACL;IAC3B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,yCAAyC;IACzC,MAAM,QAAQ,GAAG,CAAC,KAAe,EAAE,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACF,CAAC;IAAA,CACD,CAAC;IAEF,mCAAmC;IACnC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC;IAE7C,yCAAyC;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACrD,QAAQ,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC;IAE5C,oDAAoD;IACpD,QAAQ,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAE9D,OAAO,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAAA,CAChC","sourcesContent":["/**\n * Hook loader - loads TypeScript hook modules using jiti.\n */\n\nimport * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { KeyId } from \"@mariozechner/pi-tui\";\nimport { createJiti } from \"jiti\";\nimport { getAgentDir } from \"../../config.js\";\nimport type { HookMessage } from \"../messages.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { execCommand } from \"./runner.js\";\nimport type {\n\tExecOptions,\n\tHookAPI,\n\tHookContext,\n\tHookFactory,\n\tHookMessageRenderer,\n\tRegisteredCommand,\n} from \"./types.js\";\n\n// Create require function to resolve module paths at runtime\nconst require = createRequire(import.meta.url);\n\n// Lazily computed aliases - resolved at runtime to handle global installs\nlet _aliases: Record<string, string> | null = null;\nfunction getAliases(): Record<string, string> {\n\tif (_aliases) return _aliases;\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\tconst packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n\t// For typebox, we need the package root directory (not the entry file)\n\t// because jiti's alias is prefix-based: imports like \"@sinclair/typebox/compiler\"\n\t// get the alias prepended. If we alias to the entry file (.../build/cjs/index.js),\n\t// then \"@sinclair/typebox/compiler\" becomes \".../build/cjs/index.js/compiler\" (invalid).\n\t// By aliasing to the package root, it becomes \".../typebox/compiler\" which resolves correctly.\n\tconst typeboxEntry = require.resolve(\"@sinclair/typebox\");\n\tconst typeboxRoot = typeboxEntry.replace(/\\/build\\/cjs\\/index\\.js$/, \"\");\n\n\t_aliases = {\n\t\t\"@mariozechner/pi-coding-agent\": packageIndex,\n\t\t\"@mariozechner/pi-coding-agent/hooks\": path.resolve(__dirname, \"index.js\"),\n\t\t\"@mariozechner/pi-tui\": require.resolve(\"@mariozechner/pi-tui\"),\n\t\t\"@mariozechner/pi-ai\": require.resolve(\"@mariozechner/pi-ai\"),\n\t\t\"@sinclair/typebox\": typeboxRoot,\n\t};\n\treturn _aliases;\n}\n\n/**\n * Generic handler function type.\n */\ntype HandlerFn = (...args: unknown[]) => Promise<unknown>;\n\n/**\n * Send message handler type for pi.sendMessage().\n */\nexport type SendMessageHandler = <T = unknown>(\n\tmessage: Pick<HookMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" },\n) => void;\n\n/**\n * Append entry handler type for pi.appendEntry().\n */\nexport type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;\n\n/**\n * Get active tools handler type for pi.getActiveTools().\n */\nexport type GetActiveToolsHandler = () => string[];\n\n/**\n * Get all tools handler type for pi.getAllTools().\n */\nexport type GetAllToolsHandler = () => string[];\n\n/**\n * Set active tools handler type for pi.setActiveTools().\n */\nexport type SetActiveToolsHandler = (toolNames: string[]) => void;\n\n/**\n * CLI flag definition registered by a hook.\n */\nexport interface HookFlag {\n\t/** Flag name (without --) */\n\tname: string;\n\t/** Description for --help */\n\tdescription?: string;\n\t/** Type: boolean or string */\n\ttype: \"boolean\" | \"string\";\n\t/** Default value */\n\tdefault?: boolean | string;\n\t/** Hook path that registered this flag */\n\thookPath: string;\n}\n\n/**\n * Keyboard shortcut registered by a hook.\n */\nexport interface HookShortcut {\n\t/** Key identifier (e.g., Key.shift(\"p\"), \"ctrl+x\") */\n\tshortcut: KeyId;\n\t/** Description for help */\n\tdescription?: string;\n\t/** Handler function */\n\thandler: (ctx: HookContext) => Promise<void> | void;\n\t/** Hook path that registered this shortcut */\n\thookPath: string;\n}\n\n/**\n * New session handler type for ctx.newSession() in HookCommandContext.\n */\nexport type NewSessionHandler = (options?: {\n\tparentSession?: string;\n\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n}) => Promise<{ cancelled: boolean }>;\n\n/**\n * Branch handler type for ctx.branch() in HookCommandContext.\n */\nexport type BranchHandler = (entryId: string) => Promise<{ cancelled: boolean }>;\n\n/**\n * Navigate tree handler type for ctx.navigateTree() in HookCommandContext.\n */\nexport type NavigateTreeHandler = (\n\ttargetId: string,\n\toptions?: { summarize?: boolean },\n) => Promise<{ cancelled: boolean }>;\n\n/**\n * Registered handlers for a loaded hook.\n */\nexport interface LoadedHook {\n\t/** Original path from config */\n\tpath: string;\n\t/** Resolved absolute path */\n\tresolvedPath: string;\n\t/** Map of event type to handler functions */\n\thandlers: Map<string, HandlerFn[]>;\n\t/** Map of customType to hook message renderer */\n\tmessageRenderers: Map<string, HookMessageRenderer>;\n\t/** Map of command name to registered command */\n\tcommands: Map<string, RegisteredCommand>;\n\t/** CLI flags registered by this hook */\n\tflags: Map<string, HookFlag>;\n\t/** Flag values (set after CLI parsing) */\n\tflagValues: Map<string, boolean | string>;\n\t/** Keyboard shortcuts registered by this hook */\n\tshortcuts: Map<KeyId, HookShortcut>;\n\t/** Set the send message handler for this hook's pi.sendMessage() */\n\tsetSendMessageHandler: (handler: SendMessageHandler) => void;\n\t/** Set the append entry handler for this hook's pi.appendEntry() */\n\tsetAppendEntryHandler: (handler: AppendEntryHandler) => void;\n\t/** Set the get active tools handler for this hook's pi.getActiveTools() */\n\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;\n\t/** Set the get all tools handler for this hook's pi.getAllTools() */\n\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => void;\n\t/** Set the set active tools handler for this hook's pi.setActiveTools() */\n\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;\n\t/** Set a flag value (called after CLI parsing) */\n\tsetFlagValue: (name: string, value: boolean | string) => void;\n}\n\n/**\n * Result of loading hooks.\n */\nexport interface LoadHooksResult {\n\t/** Successfully loaded hooks */\n\thooks: LoadedHook[];\n\t/** Errors encountered during loading */\n\terrors: Array<{ path: string; error: string }>;\n}\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nfunction normalizeUnicodeSpaces(str: string): string {\n\treturn str.replace(UNICODE_SPACES, \" \");\n}\n\nfunction expandPath(p: string): string {\n\tconst normalized = normalizeUnicodeSpaces(p);\n\tif (normalized.startsWith(\"~/\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(2));\n\t}\n\tif (normalized.startsWith(\"~\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(1));\n\t}\n\treturn normalized;\n}\n\n/**\n * Resolve hook path.\n * - Absolute paths used as-is\n * - Paths starting with ~ expanded to home directory\n * - Relative paths resolved from cwd\n */\nfunction resolveHookPath(hookPath: string, cwd: string): string {\n\tconst expanded = expandPath(hookPath);\n\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\n\t// Relative paths resolved from cwd\n\treturn path.resolve(cwd, expanded);\n}\n\n/**\n * Create a HookAPI instance that collects handlers, renderers, and commands.\n * Returns the API, maps, and functions to set handlers later.\n */\nfunction createHookAPI(\n\thandlers: Map<string, HandlerFn[]>,\n\tcwd: string,\n\thookPath: string,\n): {\n\tapi: HookAPI;\n\tmessageRenderers: Map<string, HookMessageRenderer>;\n\tcommands: Map<string, RegisteredCommand>;\n\tflags: Map<string, HookFlag>;\n\tflagValues: Map<string, boolean | string>;\n\tshortcuts: Map<KeyId, HookShortcut>;\n\tsetSendMessageHandler: (handler: SendMessageHandler) => void;\n\tsetAppendEntryHandler: (handler: AppendEntryHandler) => void;\n\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;\n\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => void;\n\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;\n\tsetFlagValue: (name: string, value: boolean | string) => void;\n} {\n\tlet sendMessageHandler: SendMessageHandler = () => {\n\t\t// Default no-op until mode sets the handler\n\t};\n\tlet appendEntryHandler: AppendEntryHandler = () => {\n\t\t// Default no-op until mode sets the handler\n\t};\n\tlet getActiveToolsHandler: GetActiveToolsHandler = () => [];\n\tlet getAllToolsHandler: GetAllToolsHandler = () => [];\n\tlet setActiveToolsHandler: SetActiveToolsHandler = () => {\n\t\t// Default no-op until mode sets the handler\n\t};\n\tconst messageRenderers = new Map<string, HookMessageRenderer>();\n\tconst commands = new Map<string, RegisteredCommand>();\n\tconst flags = new Map<string, HookFlag>();\n\tconst flagValues = new Map<string, boolean | string>();\n\tconst shortcuts = new Map<KeyId, HookShortcut>();\n\n\t// Cast to HookAPI - the implementation is more general (string event names)\n\t// but the interface has specific overloads for type safety in hooks\n\tconst api = {\n\t\ton(event: string, handler: HandlerFn): void {\n\t\t\tconst list = handlers.get(event) ?? [];\n\t\t\tlist.push(handler);\n\t\t\thandlers.set(event, list);\n\t\t},\n\t\tsendMessage<T = unknown>(\n\t\t\tmessage: HookMessage<T>,\n\t\t\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" },\n\t\t): void {\n\t\t\tsendMessageHandler(message, options);\n\t\t},\n\t\tappendEntry<T = unknown>(customType: string, data?: T): void {\n\t\t\tappendEntryHandler(customType, data);\n\t\t},\n\t\tregisterMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void {\n\t\t\tmessageRenderers.set(customType, renderer as HookMessageRenderer);\n\t\t},\n\t\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void {\n\t\t\tcommands.set(name, { name, ...options });\n\t\t},\n\t\texec(command: string, args: string[], options?: ExecOptions) {\n\t\t\treturn execCommand(command, args, options?.cwd ?? cwd, options);\n\t\t},\n\t\tgetActiveTools(): string[] {\n\t\t\treturn getActiveToolsHandler();\n\t\t},\n\t\tgetAllTools(): string[] {\n\t\t\treturn getAllToolsHandler();\n\t\t},\n\t\tsetActiveTools(toolNames: string[]): void {\n\t\t\tsetActiveToolsHandler(toolNames);\n\t\t},\n\t\tregisterFlag(\n\t\t\tname: string,\n\t\t\toptions: { description?: string; type: \"boolean\" | \"string\"; default?: boolean | string },\n\t\t): void {\n\t\t\tflags.set(name, { name, hookPath, ...options });\n\t\t\t// Set default value if provided\n\t\t\tif (options.default !== undefined) {\n\t\t\t\tflagValues.set(name, options.default);\n\t\t\t}\n\t\t},\n\t\tgetFlag(name: string): boolean | string | undefined {\n\t\t\treturn flagValues.get(name);\n\t\t},\n\t\tregisterShortcut(\n\t\t\tshortcut: KeyId,\n\t\t\toptions: {\n\t\t\t\tdescription?: string;\n\t\t\t\thandler: (ctx: HookContext) => Promise<void> | void;\n\t\t\t},\n\t\t): void {\n\t\t\tshortcuts.set(shortcut, { shortcut, hookPath, ...options });\n\t\t},\n\t} as HookAPI;\n\n\treturn {\n\t\tapi,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler: (handler: SendMessageHandler) => {\n\t\t\tsendMessageHandler = handler;\n\t\t},\n\t\tsetAppendEntryHandler: (handler: AppendEntryHandler) => {\n\t\t\tappendEntryHandler = handler;\n\t\t},\n\t\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => {\n\t\t\tgetActiveToolsHandler = handler;\n\t\t},\n\t\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => {\n\t\t\tgetAllToolsHandler = handler;\n\t\t},\n\t\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => {\n\t\t\tsetActiveToolsHandler = handler;\n\t\t},\n\t\tsetFlagValue: (name: string, value: boolean | string) => {\n\t\t\tflagValues.set(name, value);\n\t\t},\n\t};\n}\n\n/**\n * Load a single hook module using jiti.\n */\nasync function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHook | null; error: string | null }> {\n\tconst resolvedPath = resolveHookPath(hookPath, cwd);\n\n\ttry {\n\t\t// Create jiti instance for TypeScript/ESM loading\n\t\t// Use aliases to resolve package imports since hooks are loaded from user directories\n\t\t// (e.g. ~/.pi/agent/hooks) but import from packages installed with pi-coding-agent\n\t\tconst jiti = createJiti(import.meta.url, {\n\t\t\talias: getAliases(),\n\t\t});\n\n\t\t// Import the module\n\t\tconst module = await jiti.import(resolvedPath, { default: true });\n\t\tconst factory = module as HookFactory;\n\n\t\tif (typeof factory !== \"function\") {\n\t\t\treturn { hook: null, error: \"Hook must export a default function\" };\n\t\t}\n\n\t\t// Create handlers map and API\n\t\tconst handlers = new Map<string, HandlerFn[]>();\n\t\tconst {\n\t\t\tapi,\n\t\t\tmessageRenderers,\n\t\t\tcommands,\n\t\t\tflags,\n\t\t\tflagValues,\n\t\t\tshortcuts,\n\t\t\tsetSendMessageHandler,\n\t\t\tsetAppendEntryHandler,\n\t\t\tsetGetActiveToolsHandler,\n\t\t\tsetGetAllToolsHandler,\n\t\t\tsetSetActiveToolsHandler,\n\t\t\tsetFlagValue,\n\t\t} = createHookAPI(handlers, cwd, hookPath);\n\n\t\t// Call factory to register handlers\n\t\tfactory(api);\n\n\t\treturn {\n\t\t\thook: {\n\t\t\t\tpath: hookPath,\n\t\t\t\tresolvedPath,\n\t\t\t\thandlers,\n\t\t\t\tmessageRenderers,\n\t\t\t\tcommands,\n\t\t\t\tflags,\n\t\t\t\tflagValues,\n\t\t\t\tshortcuts,\n\t\t\t\tsetSendMessageHandler,\n\t\t\t\tsetAppendEntryHandler,\n\t\t\t\tsetGetActiveToolsHandler,\n\t\t\t\tsetGetAllToolsHandler,\n\t\t\t\tsetSetActiveToolsHandler,\n\t\t\t\tsetFlagValue,\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { hook: null, error: `Failed to load hook: ${message}` };\n\t}\n}\n\n/**\n * Load all hooks from configuration.\n * @param paths - Array of hook file paths\n * @param cwd - Current working directory for resolving relative paths\n */\nexport async function loadHooks(paths: string[], cwd: string): Promise<LoadHooksResult> {\n\tconst hooks: LoadedHook[] = [];\n\tconst errors: Array<{ path: string; error: string }> = [];\n\n\tfor (const hookPath of paths) {\n\t\tconst { hook, error } = await loadHook(hookPath, cwd);\n\n\t\tif (error) {\n\t\t\terrors.push({ path: hookPath, error });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hook) {\n\t\t\thooks.push(hook);\n\t\t}\n\t}\n\n\treturn { hooks, errors };\n}\n\n/**\n * Discover hook files from a directory.\n * Returns all .ts files (and symlinks to .ts files) in the directory (non-recursive).\n */\nfunction discoverHooksInDir(dir: string): string[] {\n\tif (!fs.existsSync(dir)) {\n\t\treturn [];\n\t}\n\n\ttry {\n\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\t\treturn entries\n\t\t\t.filter((e) => (e.isFile() || e.isSymbolicLink()) && e.name.endsWith(\".ts\"))\n\t\t\t.map((e) => path.join(dir, e.name));\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Discover and load hooks from standard locations:\n * 1. agentDir/hooks/*.ts (global)\n * 2. cwd/.pi/hooks/*.ts (project-local)\n *\n * Plus any explicitly configured paths from settings.\n */\nexport async function discoverAndLoadHooks(\n\tconfiguredPaths: string[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): Promise<LoadHooksResult> {\n\tconst allPaths: string[] = [];\n\tconst seen = new Set<string>();\n\n\t// Helper to add paths without duplicates\n\tconst addPaths = (paths: string[]) => {\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = path.resolve(p);\n\t\t\tif (!seen.has(resolved)) {\n\t\t\t\tseen.add(resolved);\n\t\t\t\tallPaths.push(p);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Global hooks: agentDir/hooks/\n\tconst globalHooksDir = path.join(agentDir, \"hooks\");\n\taddPaths(discoverHooksInDir(globalHooksDir));\n\n\t// 2. Project-local hooks: cwd/.pi/hooks/\n\tconst localHooksDir = path.join(cwd, \".pi\", \"hooks\");\n\taddPaths(discoverHooksInDir(localHooksDir));\n\n\t// 3. Explicitly configured paths (can override/add)\n\taddPaths(configuredPaths.map((p) => resolveHookPath(p, cwd)));\n\n\treturn loadHooks(allPaths, cwd);\n}\n"]}
@@ -1,173 +0,0 @@
1
- /**
2
- * Hook runner - executes hooks and manages their lifecycle.
3
- */
4
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
5
- import type { ImageContent, Model } from "@mariozechner/pi-ai";
6
- import type { KeyId } from "@mariozechner/pi-tui";
7
- import type { ModelRegistry } from "../model-registry.js";
8
- import type { SessionManager } from "../session-manager.js";
9
- import type { AppendEntryHandler, BranchHandler, HookFlag, HookShortcut, LoadedHook, NavigateTreeHandler, NewSessionHandler, SendMessageHandler } from "./loader.js";
10
- import type { BeforeAgentStartEventResult, HookCommandContext, HookError, HookEvent, HookMessageRenderer, HookUIContext, RegisteredCommand, SessionBeforeCompactResult, SessionBeforeTreeResult, ToolCallEvent, ToolCallEventResult, ToolResultEventResult } from "./types.js";
11
- /** Combined result from all before_agent_start handlers (internal) */
12
- interface BeforeAgentStartCombinedResult {
13
- messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
14
- systemPromptAppend?: string;
15
- }
16
- /**
17
- * Listener for hook errors.
18
- */
19
- export type HookErrorListener = (error: HookError) => void;
20
- export { execCommand } from "../exec.js";
21
- /**
22
- * HookRunner executes hooks and manages event emission.
23
- */
24
- export declare class HookRunner {
25
- private hooks;
26
- private uiContext;
27
- private hasUI;
28
- private cwd;
29
- private sessionManager;
30
- private modelRegistry;
31
- private errorListeners;
32
- private getModel;
33
- private isIdleFn;
34
- private waitForIdleFn;
35
- private abortFn;
36
- private hasPendingMessagesFn;
37
- private newSessionHandler;
38
- private branchHandler;
39
- private navigateTreeHandler;
40
- constructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry);
41
- /**
42
- * Initialize HookRunner with all required context.
43
- * Modes call this once the agent session is fully set up.
44
- */
45
- initialize(options: {
46
- /** Function to get the current model */
47
- getModel: () => Model<any> | undefined;
48
- /** Handler for hooks to send messages */
49
- sendMessageHandler: SendMessageHandler;
50
- /** Handler for hooks to append entries */
51
- appendEntryHandler: AppendEntryHandler;
52
- /** Handler for getting current active tools */
53
- getActiveToolsHandler: () => string[];
54
- /** Handler for getting all configured tools */
55
- getAllToolsHandler: () => string[];
56
- /** Handler for setting active tools */
57
- setActiveToolsHandler: (toolNames: string[]) => void;
58
- /** Handler for creating new sessions (for HookCommandContext) */
59
- newSessionHandler?: NewSessionHandler;
60
- /** Handler for branching sessions (for HookCommandContext) */
61
- branchHandler?: BranchHandler;
62
- /** Handler for navigating session tree (for HookCommandContext) */
63
- navigateTreeHandler?: NavigateTreeHandler;
64
- /** Function to check if agent is idle */
65
- isIdle?: () => boolean;
66
- /** Function to wait for agent to be idle */
67
- waitForIdle?: () => Promise<void>;
68
- /** Function to abort current operation (fire-and-forget) */
69
- abort?: () => void;
70
- /** Function to check if there are queued messages */
71
- hasPendingMessages?: () => boolean;
72
- /** UI context for interactive prompts */
73
- uiContext?: HookUIContext;
74
- /** Whether UI is available */
75
- hasUI?: boolean;
76
- }): void;
77
- /**
78
- * Get the UI context (set by mode).
79
- */
80
- getUIContext(): HookUIContext | null;
81
- /**
82
- * Get whether UI is available.
83
- */
84
- getHasUI(): boolean;
85
- /**
86
- * Get the paths of all loaded hooks.
87
- */
88
- getHookPaths(): string[];
89
- /**
90
- * Get all CLI flags registered by hooks.
91
- */
92
- getFlags(): Map<string, HookFlag>;
93
- /**
94
- * Set a flag value (after CLI parsing).
95
- */
96
- setFlagValue(name: string, value: boolean | string): void;
97
- private static readonly RESERVED_SHORTCUTS;
98
- /**
99
- * Get all keyboard shortcuts registered by hooks.
100
- * When multiple hooks register the same shortcut, the last one wins.
101
- * Conflicts with built-in shortcuts are skipped with a warning.
102
- * Conflicts between hooks are logged as warnings.
103
- */
104
- getShortcuts(): Map<KeyId, HookShortcut>;
105
- /**
106
- * Subscribe to hook errors.
107
- * @returns Unsubscribe function
108
- */
109
- onError(listener: HookErrorListener): () => void;
110
- /**
111
- * Emit an error to all listeners.
112
- */
113
- /**
114
- * Emit an error to all error listeners.
115
- */
116
- emitError(error: HookError): void;
117
- /**
118
- * Check if any hooks have handlers for the given event type.
119
- */
120
- hasHandlers(eventType: string): boolean;
121
- /**
122
- * Get a message renderer for the given customType.
123
- * Returns the first renderer found across all hooks, or undefined if none.
124
- */
125
- getMessageRenderer(customType: string): HookMessageRenderer | undefined;
126
- /**
127
- * Get all registered commands from all hooks.
128
- */
129
- getRegisteredCommands(): RegisteredCommand[];
130
- /**
131
- * Get a registered command by name.
132
- * Returns the first command found across all hooks, or undefined if none.
133
- */
134
- getCommand(name: string): RegisteredCommand | undefined;
135
- /**
136
- * Create the event context for handlers.
137
- */
138
- private createContext;
139
- /**
140
- * Create the command context for slash command handlers.
141
- * Extends HookContext with session control methods that are only safe in commands.
142
- */
143
- createCommandContext(): HookCommandContext;
144
- /**
145
- * Check if event type is a session "before_*" event that can be cancelled.
146
- */
147
- private isSessionBeforeEvent;
148
- /**
149
- * Emit an event to all hooks.
150
- * Returns the result from session before_* / tool_result events (if any handler returns one).
151
- */
152
- emit(event: HookEvent): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined>;
153
- /**
154
- * Emit a tool_call event to all hooks.
155
- * No timeout - user prompts can take as long as needed.
156
- * Errors are thrown (not swallowed) so caller can block on failure.
157
- */
158
- emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined>;
159
- /**
160
- * Emit a context event to all hooks.
161
- * Handlers are chained - each gets the previous handler's output (if any).
162
- * Returns the final modified messages, or the original if no modifications.
163
- *
164
- * Messages are deep-copied before passing to hooks, so mutations are safe.
165
- */
166
- emitContext(messages: AgentMessage[]): Promise<AgentMessage[]>;
167
- /**
168
- * Emit before_agent_start event to all hooks.
169
- * Returns combined result: all messages and all systemPromptAppend strings concatenated.
170
- */
171
- emitBeforeAgentStart(prompt: string, images?: ImageContent[]): Promise<BeforeAgentStartCombinedResult | undefined>;
172
- }
173
- //# sourceMappingURL=runner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Model } from \"@mariozechner/pi-ai\";\nimport type { KeyId } from \"@mariozechner/pi-tui\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type {\n\tAppendEntryHandler,\n\tBranchHandler,\n\tHookFlag,\n\tHookShortcut,\n\tLoadedHook,\n\tNavigateTreeHandler,\n\tNewSessionHandler,\n\tSendMessageHandler,\n} from \"./loader.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tContextEvent,\n\tContextEventResult,\n\tHookCommandContext,\n\tHookContext,\n\tHookError,\n\tHookEvent,\n\tHookMessageRenderer,\n\tHookUIContext,\n\tRegisteredCommand,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/** Combined result from all before_agent_start handlers (internal) */\ninterface BeforeAgentStartCombinedResult {\n\tmessages?: NonNullable<BeforeAgentStartEventResult[\"message\"]>[];\n\tsystemPromptAppend?: string;\n}\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n// Re-export execCommand for backward compatibility\nexport { execCommand } from \"../exec.js\";\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tsetStatus: () => {},\n\tsetWidget: () => {},\n\tsetTitle: () => {},\n\tcustom: async () => undefined as never,\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\tget theme() {\n\t\treturn theme;\n\t},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasPendingMessagesFn: () => boolean = () => false;\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate branchHandler: BranchHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\n\tconstructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\t/**\n\t * Initialize HookRunner with all required context.\n\t * Modes call this once the agent session is fully set up.\n\t */\n\tinitialize(options: {\n\t\t/** Function to get the current model */\n\t\tgetModel: () => Model<any> | undefined;\n\t\t/** Handler for hooks to send messages */\n\t\tsendMessageHandler: SendMessageHandler;\n\t\t/** Handler for hooks to append entries */\n\t\tappendEntryHandler: AppendEntryHandler;\n\t\t/** Handler for getting current active tools */\n\t\tgetActiveToolsHandler: () => string[];\n\t\t/** Handler for getting all configured tools */\n\t\tgetAllToolsHandler: () => string[];\n\t\t/** Handler for setting active tools */\n\t\tsetActiveToolsHandler: (toolNames: string[]) => void;\n\t\t/** Handler for creating new sessions (for HookCommandContext) */\n\t\tnewSessionHandler?: NewSessionHandler;\n\t\t/** Handler for branching sessions (for HookCommandContext) */\n\t\tbranchHandler?: BranchHandler;\n\t\t/** Handler for navigating session tree (for HookCommandContext) */\n\t\tnavigateTreeHandler?: NavigateTreeHandler;\n\t\t/** Function to check if agent is idle */\n\t\tisIdle?: () => boolean;\n\t\t/** Function to wait for agent to be idle */\n\t\twaitForIdle?: () => Promise<void>;\n\t\t/** Function to abort current operation (fire-and-forget) */\n\t\tabort?: () => void;\n\t\t/** Function to check if there are queued messages */\n\t\thasPendingMessages?: () => boolean;\n\t\t/** UI context for interactive prompts */\n\t\tuiContext?: HookUIContext;\n\t\t/** Whether UI is available */\n\t\thasUI?: boolean;\n\t}): void {\n\t\tthis.getModel = options.getModel;\n\t\tthis.isIdleFn = options.isIdle ?? (() => true);\n\t\tthis.waitForIdleFn = options.waitForIdle ?? (async () => {});\n\t\tthis.abortFn = options.abort ?? (() => {});\n\t\tthis.hasPendingMessagesFn = options.hasPendingMessages ?? (() => false);\n\t\t// Store session handlers for HookCommandContext\n\t\tif (options.newSessionHandler) {\n\t\t\tthis.newSessionHandler = options.newSessionHandler;\n\t\t}\n\t\tif (options.branchHandler) {\n\t\t\tthis.branchHandler = options.branchHandler;\n\t\t}\n\t\tif (options.navigateTreeHandler) {\n\t\t\tthis.navigateTreeHandler = options.navigateTreeHandler;\n\t\t}\n\t\t// Set per-hook handlers for pi.sendMessage(), pi.appendEntry(), pi.getActiveTools(), pi.getAllTools(), pi.setActiveTools()\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendMessageHandler(options.sendMessageHandler);\n\t\t\thook.setAppendEntryHandler(options.appendEntryHandler);\n\t\t\thook.setGetActiveToolsHandler(options.getActiveToolsHandler);\n\t\t\thook.setGetAllToolsHandler(options.getAllToolsHandler);\n\t\t\thook.setSetActiveToolsHandler(options.setActiveToolsHandler);\n\t\t}\n\t\tthis.uiContext = options.uiContext ?? noOpUIContext;\n\t\tthis.hasUI = options.hasUI ?? false;\n\t}\n\n\t/**\n\t * Get the UI context (set by mode).\n\t */\n\tgetUIContext(): HookUIContext | null {\n\t\treturn this.uiContext;\n\t}\n\n\t/**\n\t * Get whether UI is available.\n\t */\n\tgetHasUI(): boolean {\n\t\treturn this.hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Get all CLI flags registered by hooks.\n\t */\n\tgetFlags(): Map<string, HookFlag> {\n\t\tconst allFlags = new Map<string, HookFlag>();\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const [name, flag] of hook.flags) {\n\t\t\t\tallFlags.set(name, flag);\n\t\t\t}\n\t\t}\n\t\treturn allFlags;\n\t}\n\n\t/**\n\t * Set a flag value (after CLI parsing).\n\t */\n\tsetFlagValue(name: string, value: boolean | string): void {\n\t\tfor (const hook of this.hooks) {\n\t\t\tif (hook.flags.has(name)) {\n\t\t\t\thook.setFlagValue(name, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Built-in shortcuts that hooks should not override\n\tprivate static readonly RESERVED_SHORTCUTS = new Set([\n\t\t\"ctrl+c\",\n\t\t\"ctrl+d\",\n\t\t\"ctrl+z\",\n\t\t\"ctrl+k\",\n\t\t\"ctrl+p\",\n\t\t\"ctrl+l\",\n\t\t\"ctrl+o\",\n\t\t\"ctrl+t\",\n\t\t\"ctrl+g\",\n\t\t\"shift+tab\",\n\t\t\"shift+ctrl+p\",\n\t\t\"alt+enter\",\n\t\t\"escape\",\n\t\t\"enter\",\n\t]);\n\n\t/**\n\t * Get all keyboard shortcuts registered by hooks.\n\t * When multiple hooks register the same shortcut, the last one wins.\n\t * Conflicts with built-in shortcuts are skipped with a warning.\n\t * Conflicts between hooks are logged as warnings.\n\t */\n\tgetShortcuts(): Map<KeyId, HookShortcut> {\n\t\tconst allShortcuts = new Map<KeyId, HookShortcut>();\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const [key, shortcut] of hook.shortcuts) {\n\t\t\t\t// Normalize to lowercase for comparison (KeyId is string at runtime)\n\t\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\n\t\t\t\t// Check for built-in shortcut conflicts\n\t\t\t\tif (HookRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`Hook shortcut '${key}' from ${shortcut.hookPath} conflicts with built-in shortcut. Skipping.`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst existing = allShortcuts.get(normalizedKey);\n\t\t\t\tif (existing) {\n\t\t\t\t\t// Log conflict between hooks - last one wins\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`Hook shortcut conflict: '${key}' registered by both ${existing.hookPath} and ${shortcut.hookPath}. Using ${shortcut.hookPath}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tallShortcuts.set(normalizedKey, shortcut);\n\t\t\t}\n\t\t}\n\t\treturn allShortcuts;\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\t/**\n\t * Emit an error to all error listeners.\n\t */\n\temitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get a message renderer for the given customType.\n\t * Returns the first renderer found across all hooks, or undefined if none.\n\t */\n\tgetMessageRenderer(customType: string): HookMessageRenderer | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst renderer = hook.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered commands from all hooks.\n\t */\n\tgetRegisteredCommands(): RegisteredCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const command of hook.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * Get a registered command by name.\n\t * Returns the first command found across all hooks, or undefined if none.\n\t */\n\tgetCommand(name: string): RegisteredCommand | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst command = hook.commands.get(name);\n\t\t\tif (command) {\n\t\t\t\treturn command;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookContext {\n\t\treturn {\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tmodelRegistry: this.modelRegistry,\n\t\t\tmodel: this.getModel(),\n\t\t\tisIdle: () => this.isIdleFn(),\n\t\t\tabort: () => this.abortFn(),\n\t\t\thasPendingMessages: () => this.hasPendingMessagesFn(),\n\t\t};\n\t}\n\n\t/**\n\t * Create the command context for slash command handlers.\n\t * Extends HookContext with session control methods that are only safe in commands.\n\t */\n\tcreateCommandContext(): HookCommandContext {\n\t\treturn {\n\t\t\t...this.createContext(),\n\t\t\twaitForIdle: () => this.waitForIdleFn(),\n\t\t\tnewSession: (options) => this.newSessionHandler(options),\n\t\t\tbranch: (entryId) => this.branchHandler(entryId),\n\t\t\tnavigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),\n\t\t};\n\t}\n\n\t/**\n\t * Check if event type is a session \"before_*\" event that can be cancelled.\n\t */\n\tprivate isSessionBeforeEvent(\n\t\ttype: string,\n\t): type is \"session_before_switch\" | \"session_before_branch\" | \"session_before_compact\" | \"session_before_tree\" {\n\t\treturn (\n\t\t\ttype === \"session_before_switch\" ||\n\t\t\ttype === \"session_before_branch\" ||\n\t\t\ttype === \"session_before_compact\" ||\n\t\t\ttype === \"session_before_tree\"\n\t\t);\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from session before_* / tool_result events (if any handler returns one).\n\t */\n\tasync emit(\n\t\tevent: HookEvent,\n\t): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// For session before_* events, capture the result (for cancellation)\n\t\t\t\t\tif (this.isSessionBeforeEvent(event.type) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeCompactResult | SessionBeforeTreeResult;\n\t\t\t\t\t\t// If cancelled, stop processing further hooks\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a context event to all hooks.\n\t * Handlers are chained - each gets the previous handler's output (if any).\n\t * Returns the final modified messages, or the original if no modifications.\n\t *\n\t * Messages are deep-copied before passing to hooks, so mutations are safe.\n\t */\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentMessages = structuredClone(messages);\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\t/**\n\t * Emit before_agent_start event to all hooks.\n\t * Returns combined result: all messages and all systemPromptAppend strings concatenated.\n\t */\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages?: ImageContent[],\n\t): Promise<BeforeAgentStartCombinedResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tconst messages: NonNullable<BeforeAgentStartEventResult[\"message\"]>[] = [];\n\t\tconst systemPromptAppends: string[] = [];\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = { type: \"before_agent_start\", prompt, images };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\tconst result = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t\t// Collect all messages\n\t\t\t\t\t\tif (result.message) {\n\t\t\t\t\t\t\tmessages.push(result.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Collect all systemPromptAppend strings\n\t\t\t\t\t\tif (result.systemPromptAppend) {\n\t\t\t\t\t\t\tsystemPromptAppends.push(result.systemPromptAppend);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Return combined result\n\t\tif (messages.length > 0 || systemPromptAppends.length > 0) {\n\t\t\treturn {\n\t\t\t\tmessages: messages.length > 0 ? messages : undefined,\n\t\t\t\tsystemPromptAppend: systemPromptAppends.length > 0 ? systemPromptAppends.join(\"\\n\\n\") : undefined,\n\t\t\t};\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, Model } from \"@mariozechner/pi-ai\";\nimport type { KeyId } from \"@mariozechner/pi-tui\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type {\n\tAppendEntryHandler,\n\tBranchHandler,\n\tHookFlag,\n\tHookShortcut,\n\tLoadedHook,\n\tNavigateTreeHandler,\n\tNewSessionHandler,\n\tSendMessageHandler,\n} from \"./loader.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tContextEvent,\n\tContextEventResult,\n\tHookCommandContext,\n\tHookContext,\n\tHookError,\n\tHookEvent,\n\tHookMessageRenderer,\n\tHookUIContext,\n\tRegisteredCommand,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/** Combined result from all before_agent_start handlers (internal) */\ninterface BeforeAgentStartCombinedResult {\n\tmessages?: NonNullable<BeforeAgentStartEventResult[\"message\"]>[];\n\tsystemPromptAppend?: string;\n}\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n// Re-export execCommand for backward compatibility\nexport { execCommand } from \"../exec.js\";\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tsetStatus: () => {},\n\tsetWidget: () => {},\n\tsetTitle: () => {},\n\tcustom: async () => undefined as never,\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\tget theme() {\n\t\treturn theme;\n\t},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasPendingMessagesFn: () => boolean = () => false;\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate branchHandler: BranchHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\n\tconstructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\t/**\n\t * Initialize HookRunner with all required context.\n\t * Modes call this once the agent session is fully set up.\n\t */\n\tinitialize(options: {\n\t\t/** Function to get the current model */\n\t\tgetModel: () => Model<any> | undefined;\n\t\t/** Handler for hooks to send messages */\n\t\tsendMessageHandler: SendMessageHandler;\n\t\t/** Handler for hooks to append entries */\n\t\tappendEntryHandler: AppendEntryHandler;\n\t\t/** Handler for getting current active tools */\n\t\tgetActiveToolsHandler: () => string[];\n\t\t/** Handler for getting all configured tools */\n\t\tgetAllToolsHandler: () => string[];\n\t\t/** Handler for setting active tools */\n\t\tsetActiveToolsHandler: (toolNames: string[]) => void;\n\t\t/** Handler for creating new sessions (for HookCommandContext) */\n\t\tnewSessionHandler?: NewSessionHandler;\n\t\t/** Handler for branching sessions (for HookCommandContext) */\n\t\tbranchHandler?: BranchHandler;\n\t\t/** Handler for navigating session tree (for HookCommandContext) */\n\t\tnavigateTreeHandler?: NavigateTreeHandler;\n\t\t/** Function to check if agent is idle */\n\t\tisIdle?: () => boolean;\n\t\t/** Function to wait for agent to be idle */\n\t\twaitForIdle?: () => Promise<void>;\n\t\t/** Function to abort current operation (fire-and-forget) */\n\t\tabort?: () => void;\n\t\t/** Function to check if there are queued messages */\n\t\thasPendingMessages?: () => boolean;\n\t\t/** UI context for interactive prompts */\n\t\tuiContext?: HookUIContext;\n\t\t/** Whether UI is available */\n\t\thasUI?: boolean;\n\t}): void {\n\t\tthis.getModel = options.getModel;\n\t\tthis.isIdleFn = options.isIdle ?? (() => true);\n\t\tthis.waitForIdleFn = options.waitForIdle ?? (async () => {});\n\t\tthis.abortFn = options.abort ?? (() => {});\n\t\tthis.hasPendingMessagesFn = options.hasPendingMessages ?? (() => false);\n\t\t// Store session handlers for HookCommandContext\n\t\tif (options.newSessionHandler) {\n\t\t\tthis.newSessionHandler = options.newSessionHandler;\n\t\t}\n\t\tif (options.branchHandler) {\n\t\t\tthis.branchHandler = options.branchHandler;\n\t\t}\n\t\tif (options.navigateTreeHandler) {\n\t\t\tthis.navigateTreeHandler = options.navigateTreeHandler;\n\t\t}\n\t\t// Set per-hook handlers for pi.sendMessage(), pi.appendEntry(), pi.getActiveTools(), pi.getAllTools(), pi.setActiveTools()\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendMessageHandler(options.sendMessageHandler);\n\t\t\thook.setAppendEntryHandler(options.appendEntryHandler);\n\t\t\thook.setGetActiveToolsHandler(options.getActiveToolsHandler);\n\t\t\thook.setGetAllToolsHandler(options.getAllToolsHandler);\n\t\t\thook.setSetActiveToolsHandler(options.setActiveToolsHandler);\n\t\t}\n\t\tthis.uiContext = options.uiContext ?? noOpUIContext;\n\t\tthis.hasUI = options.hasUI ?? false;\n\t}\n\n\t/**\n\t * Get the UI context (set by mode).\n\t */\n\tgetUIContext(): HookUIContext | null {\n\t\treturn this.uiContext;\n\t}\n\n\t/**\n\t * Get whether UI is available.\n\t */\n\tgetHasUI(): boolean {\n\t\treturn this.hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Get all CLI flags registered by hooks.\n\t */\n\tgetFlags(): Map<string, HookFlag> {\n\t\tconst allFlags = new Map<string, HookFlag>();\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const [name, flag] of hook.flags) {\n\t\t\t\tallFlags.set(name, flag);\n\t\t\t}\n\t\t}\n\t\treturn allFlags;\n\t}\n\n\t/**\n\t * Set a flag value (after CLI parsing).\n\t */\n\tsetFlagValue(name: string, value: boolean | string): void {\n\t\tfor (const hook of this.hooks) {\n\t\t\tif (hook.flags.has(name)) {\n\t\t\t\thook.setFlagValue(name, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Built-in shortcuts that hooks should not override\n\tprivate static readonly RESERVED_SHORTCUTS = new Set([\n\t\t\"ctrl+c\",\n\t\t\"ctrl+d\",\n\t\t\"ctrl+z\",\n\t\t\"ctrl+k\",\n\t\t\"ctrl+p\",\n\t\t\"ctrl+l\",\n\t\t\"ctrl+o\",\n\t\t\"ctrl+t\",\n\t\t\"ctrl+g\",\n\t\t\"shift+tab\",\n\t\t\"shift+ctrl+p\",\n\t\t\"alt+enter\",\n\t\t\"escape\",\n\t\t\"enter\",\n\t]);\n\n\t/**\n\t * Get all keyboard shortcuts registered by hooks.\n\t * When multiple hooks register the same shortcut, the last one wins.\n\t * Conflicts with built-in shortcuts are skipped with a warning.\n\t * Conflicts between hooks are logged as warnings.\n\t */\n\tgetShortcuts(): Map<KeyId, HookShortcut> {\n\t\tconst allShortcuts = new Map<KeyId, HookShortcut>();\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const [key, shortcut] of hook.shortcuts) {\n\t\t\t\t// Normalize to lowercase for comparison (KeyId is string at runtime)\n\t\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\n\t\t\t\t// Check for built-in shortcut conflicts\n\t\t\t\tif (HookRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`Hook shortcut '${key}' from ${shortcut.hookPath} conflicts with built-in shortcut. Skipping.`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst existing = allShortcuts.get(normalizedKey);\n\t\t\t\tif (existing) {\n\t\t\t\t\t// Log conflict between hooks - last one wins\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`Hook shortcut conflict: '${key}' registered by both ${existing.hookPath} and ${shortcut.hookPath}. Using ${shortcut.hookPath}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tallShortcuts.set(normalizedKey, shortcut);\n\t\t\t}\n\t\t}\n\t\treturn allShortcuts;\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\t/**\n\t * Emit an error to all error listeners.\n\t */\n\temitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get a message renderer for the given customType.\n\t * Returns the first renderer found across all hooks, or undefined if none.\n\t */\n\tgetMessageRenderer(customType: string): HookMessageRenderer | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst renderer = hook.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered commands from all hooks.\n\t */\n\tgetRegisteredCommands(): RegisteredCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tfor (const hook of this.hooks) {\n\t\t\tfor (const command of hook.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * Get a registered command by name.\n\t * Returns the first command found across all hooks, or undefined if none.\n\t */\n\tgetCommand(name: string): RegisteredCommand | undefined {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst command = hook.commands.get(name);\n\t\t\tif (command) {\n\t\t\t\treturn command;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookContext {\n\t\treturn {\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tmodelRegistry: this.modelRegistry,\n\t\t\tmodel: this.getModel(),\n\t\t\tisIdle: () => this.isIdleFn(),\n\t\t\tabort: () => this.abortFn(),\n\t\t\thasPendingMessages: () => this.hasPendingMessagesFn(),\n\t\t};\n\t}\n\n\t/**\n\t * Create the command context for slash command handlers.\n\t * Extends HookContext with session control methods that are only safe in commands.\n\t */\n\tcreateCommandContext(): HookCommandContext {\n\t\treturn {\n\t\t\t...this.createContext(),\n\t\t\twaitForIdle: () => this.waitForIdleFn(),\n\t\t\tnewSession: (options) => this.newSessionHandler(options),\n\t\t\tbranch: (entryId) => this.branchHandler(entryId),\n\t\t\tnavigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),\n\t\t};\n\t}\n\n\t/**\n\t * Check if event type is a session \"before_*\" event that can be cancelled.\n\t */\n\tprivate isSessionBeforeEvent(\n\t\ttype: string,\n\t): type is \"session_before_switch\" | \"session_before_branch\" | \"session_before_compact\" | \"session_before_tree\" {\n\t\treturn (\n\t\t\ttype === \"session_before_switch\" ||\n\t\t\ttype === \"session_before_branch\" ||\n\t\t\ttype === \"session_before_compact\" ||\n\t\t\ttype === \"session_before_tree\"\n\t\t);\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from session before_* / tool_result events (if any handler returns one).\n\t */\n\tasync emit(\n\t\tevent: HookEvent,\n\t): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\t// For session before_* events, capture the result (for cancellation)\n\t\t\t\t\tif (this.isSessionBeforeEvent(event.type) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeCompactResult | SessionBeforeTreeResult;\n\t\t\t\t\t\t// If cancelled, stop processing further hooks\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a context event to all hooks.\n\t * Handlers are chained - each gets the previous handler's output (if any).\n\t * Returns the final modified messages, or the original if no modifications.\n\t *\n\t * Messages are deep-copied before passing to hooks, so mutations are safe.\n\t */\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentMessages = structuredClone(messages);\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\t/**\n\t * Emit before_agent_start event to all hooks.\n\t * Returns combined result: all messages and all systemPromptAppend strings concatenated.\n\t */\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages?: ImageContent[],\n\t): Promise<BeforeAgentStartCombinedResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tconst messages: NonNullable<BeforeAgentStartEventResult[\"message\"]>[] = [];\n\t\tconst systemPromptAppends: string[] = [];\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = { type: \"before_agent_start\", prompt, images };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\tconst result = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t\t// Collect all messages\n\t\t\t\t\t\tif (result.message) {\n\t\t\t\t\t\t\tmessages.push(result.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Collect all systemPromptAppend strings\n\t\t\t\t\t\tif (result.systemPromptAppend) {\n\t\t\t\t\t\t\tsystemPromptAppends.push(result.systemPromptAppend);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Return combined result\n\t\tif (messages.length > 0 || systemPromptAppends.length > 0) {\n\t\t\treturn {\n\t\t\t\tmessages: messages.length > 0 ? messages : undefined,\n\t\t\t\tsystemPromptAppend: systemPromptAppends.length > 0 ? systemPromptAppends.join(\"\\n\\n\") : undefined,\n\t\t\t};\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n"]}
@@ -1,17 +0,0 @@
1
- /**
2
- * Tool wrapper - wraps tools with hook callbacks for interception.
3
- */
4
- import type { AgentTool } from "@mariozechner/pi-agent-core";
5
- import type { HookRunner } from "./runner.js";
6
- /**
7
- * Wrap a tool with hook callbacks.
8
- * - Emits tool_call event before execution (can block)
9
- * - Emits tool_result event after execution (can modify result)
10
- * - Forwards onUpdate callback to wrapped tool for progress streaming
11
- */
12
- export declare function wrapToolWithHooks<T>(tool: AgentTool<any, T>, hookRunner: HookRunner): AgentTool<any, T>;
13
- /**
14
- * Wrap all tools with hook callbacks.
15
- */
16
- export declare function wrapToolsWithHooks<T>(tools: AgentTool<any, T>[], hookRunner: HookRunner): AgentTool<any, T>[];
17
- //# sourceMappingURL=tool-wrapper.d.ts.map