@clinebot/core 0.0.28 → 0.0.29

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 (339) hide show
  1. package/README.md +7 -0
  2. package/dist/ClineCore.d.ts +28 -2
  3. package/dist/ClineCore.d.ts.map +1 -1
  4. package/dist/account/cline-account-service.d.ts +1 -1
  5. package/dist/account/cline-account-service.d.ts.map +1 -1
  6. package/dist/account/index.d.ts +1 -1
  7. package/dist/account/index.d.ts.map +1 -1
  8. package/dist/account/types.d.ts +5 -0
  9. package/dist/account/types.d.ts.map +1 -1
  10. package/dist/auth/bounded-ttl-cache.d.ts +14 -0
  11. package/dist/auth/bounded-ttl-cache.d.ts.map +1 -0
  12. package/dist/auth/cline.d.ts +27 -2
  13. package/dist/auth/cline.d.ts.map +1 -1
  14. package/dist/auth/oca.d.ts.map +1 -1
  15. package/dist/chat/chat-schema.d.ts +11 -11
  16. package/dist/extensions/config/agent-config-loader.d.ts.map +1 -0
  17. package/dist/{agents → extensions/config}/agent-config-parser.d.ts +2 -2
  18. package/dist/extensions/config/agent-config-parser.d.ts.map +1 -0
  19. package/dist/{agents → extensions/config}/hooks-config-loader.d.ts +1 -1
  20. package/dist/extensions/config/hooks-config-loader.d.ts.map +1 -0
  21. package/dist/{agents → extensions/config}/index.d.ts +2 -4
  22. package/dist/extensions/config/index.d.ts.map +1 -0
  23. package/dist/{runtime/commands.d.ts → extensions/config/runtime-commands.d.ts} +2 -3
  24. package/dist/extensions/config/runtime-commands.d.ts.map +1 -0
  25. package/dist/extensions/config/unified-config-file-watcher.d.ts.map +1 -0
  26. package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -0
  27. package/dist/extensions/context/agentic-compaction.d.ts +13 -0
  28. package/dist/extensions/context/agentic-compaction.d.ts.map +1 -0
  29. package/dist/extensions/context/basic-compaction.d.ts +9 -0
  30. package/dist/extensions/context/basic-compaction.d.ts.map +1 -0
  31. package/dist/extensions/context/compaction-shared.d.ts +60 -0
  32. package/dist/extensions/context/compaction-shared.d.ts.map +1 -0
  33. package/dist/extensions/context/compaction.d.ts +20 -0
  34. package/dist/extensions/context/compaction.d.ts.map +1 -0
  35. package/dist/extensions/index.d.ts +5 -0
  36. package/dist/extensions/index.d.ts.map +1 -0
  37. package/dist/extensions/mcp/client.d.ts +3 -0
  38. package/dist/extensions/mcp/client.d.ts.map +1 -0
  39. package/dist/extensions/mcp/config-loader.d.ts.map +1 -0
  40. package/dist/extensions/mcp/index.d.ts +9 -0
  41. package/dist/extensions/mcp/index.d.ts.map +1 -0
  42. package/dist/{mcp → extensions/mcp}/manager.d.ts +1 -2
  43. package/dist/extensions/mcp/manager.d.ts.map +1 -0
  44. package/dist/extensions/mcp/name-transform.d.ts +3 -0
  45. package/dist/extensions/mcp/name-transform.d.ts.map +1 -0
  46. package/dist/extensions/mcp/policies.d.ts +15 -0
  47. package/dist/extensions/mcp/policies.d.ts.map +1 -0
  48. package/dist/extensions/mcp/tools.d.ts +4 -0
  49. package/dist/extensions/mcp/tools.d.ts.map +1 -0
  50. package/dist/{mcp → extensions/mcp}/types.d.ts +29 -1
  51. package/dist/extensions/mcp/types.d.ts.map +1 -0
  52. package/dist/{agents → extensions/plugin}/plugin-config-loader.d.ts +1 -1
  53. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -0
  54. package/dist/{agents → extensions/plugin}/plugin-loader.d.ts +1 -1
  55. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -0
  56. package/dist/extensions/plugin/plugin-module-import.d.ts +5 -0
  57. package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -0
  58. package/dist/{agents → extensions/plugin}/plugin-sandbox.d.ts +1 -1
  59. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -0
  60. package/dist/extensions/plugin-sandbox-bootstrap.js +485 -0
  61. package/dist/hooks/index.d.ts +4 -0
  62. package/dist/hooks/index.d.ts.map +1 -0
  63. package/dist/hooks/persistent.d.ts +64 -0
  64. package/dist/hooks/persistent.d.ts.map +1 -0
  65. package/dist/hooks/subprocess-runner.d.ts +22 -0
  66. package/dist/hooks/subprocess-runner.d.ts.map +1 -0
  67. package/dist/hooks/subprocess.d.ts +189 -0
  68. package/dist/hooks/subprocess.d.ts.map +1 -0
  69. package/dist/index.d.ts +22 -25
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +560 -447
  72. package/dist/prompt/default-system.d.ts +2 -0
  73. package/dist/prompt/default-system.d.ts.map +1 -0
  74. package/dist/providers/local-provider-service.d.ts +1 -1
  75. package/dist/providers/local-provider-service.d.ts.map +1 -1
  76. package/dist/runtime/checkpoint-hooks.d.ts +21 -0
  77. package/dist/runtime/checkpoint-hooks.d.ts.map +1 -0
  78. package/dist/runtime/hook-file-hooks.d.ts +1 -1
  79. package/dist/runtime/hook-file-hooks.d.ts.map +1 -1
  80. package/dist/runtime/rules.d.ts +1 -1
  81. package/dist/runtime/rules.d.ts.map +1 -1
  82. package/dist/runtime/runtime-builder.d.ts +1 -1
  83. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  84. package/dist/runtime/session-runtime.d.ts +25 -5
  85. package/dist/runtime/session-runtime.d.ts.map +1 -1
  86. package/dist/runtime/subprocess-sandbox.d.ts.map +1 -0
  87. package/dist/runtime/team-runtime-registry.d.ts +1 -1
  88. package/dist/runtime/team-runtime-registry.d.ts.map +1 -1
  89. package/dist/runtime/tool-approval.d.ts +1 -1
  90. package/dist/session/default-session-manager.d.ts +4 -3
  91. package/dist/session/default-session-manager.d.ts.map +1 -1
  92. package/dist/session/file-session-service.d.ts +1 -1
  93. package/dist/session/file-session-service.d.ts.map +1 -1
  94. package/dist/session/{unified-session-persistence-service.d.ts → persistence-service.d.ts} +11 -42
  95. package/dist/session/persistence-service.d.ts.map +1 -0
  96. package/dist/session/rpc-session-service.d.ts +1 -1
  97. package/dist/session/rpc-session-service.d.ts.map +1 -1
  98. package/dist/session/session-agent-events.d.ts +1 -1
  99. package/dist/session/session-artifacts.d.ts.map +1 -1
  100. package/dist/session/session-config-builder.d.ts.map +1 -1
  101. package/dist/session/session-graph.d.ts +1 -1
  102. package/dist/session/session-graph.d.ts.map +1 -1
  103. package/dist/session/session-host.d.ts.map +1 -1
  104. package/dist/session/session-manager.d.ts +6 -5
  105. package/dist/session/session-manager.d.ts.map +1 -1
  106. package/dist/session/session-manifest.d.ts +1 -1
  107. package/dist/session/session-service.d.ts +3 -2
  108. package/dist/session/session-service.d.ts.map +1 -1
  109. package/dist/session/session-team-coordination.d.ts +2 -1
  110. package/dist/session/session-team-coordination.d.ts.map +1 -1
  111. package/dist/session/utils/helpers.d.ts +51 -3
  112. package/dist/session/utils/helpers.d.ts.map +1 -1
  113. package/dist/session/utils/types.d.ts +41 -7
  114. package/dist/session/utils/types.d.ts.map +1 -1
  115. package/dist/session/workspace-manager.d.ts +1 -2
  116. package/dist/session/workspace-manager.d.ts.map +1 -1
  117. package/dist/session/workspace-manifest.d.ts +1 -22
  118. package/dist/session/workspace-manifest.d.ts.map +1 -1
  119. package/dist/storage/file-team-store.d.ts +2 -1
  120. package/dist/storage/file-team-store.d.ts.map +1 -1
  121. package/dist/storage/sqlite-team-store.d.ts +4 -1
  122. package/dist/storage/sqlite-team-store.d.ts.map +1 -1
  123. package/dist/storage/team-store.d.ts.map +1 -1
  124. package/dist/team/delegated-agent.d.ts +44 -0
  125. package/dist/team/delegated-agent.d.ts.map +1 -0
  126. package/dist/team/index.d.ts +1 -0
  127. package/dist/team/index.d.ts.map +1 -1
  128. package/dist/team/multi-agent.d.ts +229 -0
  129. package/dist/team/multi-agent.d.ts.map +1 -0
  130. package/dist/team/projections.d.ts +2 -2
  131. package/dist/team/projections.d.ts.map +1 -1
  132. package/dist/team/runtime.d.ts +5 -0
  133. package/dist/team/runtime.d.ts.map +1 -0
  134. package/dist/team/spawn-agent-tool.d.ts +85 -0
  135. package/dist/team/spawn-agent-tool.d.ts.map +1 -0
  136. package/dist/team/subagent-prompts.d.ts +4 -0
  137. package/dist/team/subagent-prompts.d.ts.map +1 -0
  138. package/dist/team/team-tools.d.ts +35 -0
  139. package/dist/team/team-tools.d.ts.map +1 -0
  140. package/dist/telemetry/OpenTelemetryProvider.d.ts +11 -1
  141. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
  142. package/dist/telemetry/{LoggerTelemetryAdapter.d.ts → TelemetryLoggerSink.d.ts} +10 -4
  143. package/dist/telemetry/TelemetryLoggerSink.d.ts.map +1 -0
  144. package/dist/telemetry/TelemetryService.d.ts.map +1 -1
  145. package/dist/telemetry/index.js +15 -28
  146. package/dist/tools/definitions.d.ts +4 -3
  147. package/dist/tools/definitions.d.ts.map +1 -1
  148. package/dist/tools/index.d.ts +5 -5
  149. package/dist/tools/index.d.ts.map +1 -1
  150. package/dist/tools/model-tool-routing.d.ts.map +1 -1
  151. package/dist/tools/presets.d.ts +26 -0
  152. package/dist/tools/presets.d.ts.map +1 -1
  153. package/dist/tools/schemas.d.ts +8 -0
  154. package/dist/tools/schemas.d.ts.map +1 -1
  155. package/dist/tools/types.d.ts +23 -2
  156. package/dist/tools/types.d.ts.map +1 -1
  157. package/dist/types/config.d.ts +47 -3
  158. package/dist/types/config.d.ts.map +1 -1
  159. package/dist/types/events.d.ts +1 -1
  160. package/dist/types/provider-settings.d.ts +1 -1
  161. package/dist/types/provider-settings.d.ts.map +1 -1
  162. package/dist/types/storage.d.ts +2 -1
  163. package/dist/types/storage.d.ts.map +1 -1
  164. package/dist/types.d.ts +7 -16
  165. package/dist/types.d.ts.map +1 -1
  166. package/package.json +15 -12
  167. package/src/ClineCore.test.ts +150 -0
  168. package/src/ClineCore.ts +114 -8
  169. package/src/account/cline-account-service.test.ts +84 -0
  170. package/src/account/cline-account-service.ts +2 -2
  171. package/src/account/index.ts +1 -0
  172. package/src/account/types.ts +6 -0
  173. package/src/auth/bounded-ttl-cache.test.ts +38 -0
  174. package/src/auth/bounded-ttl-cache.ts +53 -0
  175. package/src/auth/cline.test.ts +173 -36
  176. package/src/auth/cline.ts +395 -93
  177. package/src/auth/oca.test.ts +125 -0
  178. package/src/auth/oca.ts +17 -4
  179. package/src/{agents → extensions/config}/agent-config-loader.test.ts +1 -1
  180. package/src/{agents → extensions/config}/agent-config-parser.ts +2 -2
  181. package/src/{agents → extensions/config}/hooks-config-loader.ts +1 -1
  182. package/src/{agents → extensions/config}/index.ts +7 -11
  183. package/src/{runtime/commands.test.ts → extensions/config/runtime-commands.test.ts} +20 -3
  184. package/src/{runtime/commands.ts → extensions/config/runtime-commands.ts} +1 -8
  185. package/src/{agents → extensions/config}/unified-config-file-watcher.ts +15 -2
  186. package/src/{agents → extensions/config}/user-instruction-config-loader.test.ts +90 -2
  187. package/src/{agents → extensions/config}/user-instruction-config-loader.ts +126 -12
  188. package/src/extensions/context/agentic-compaction.ts +119 -0
  189. package/src/extensions/context/basic-compaction.ts +275 -0
  190. package/src/extensions/context/compaction-shared.ts +458 -0
  191. package/src/extensions/context/compaction.test.ts +477 -0
  192. package/src/extensions/context/compaction.ts +203 -0
  193. package/src/extensions/index.ts +12 -0
  194. package/src/extensions/mcp/client.ts +420 -0
  195. package/src/{mcp → extensions/mcp}/index.ts +16 -0
  196. package/src/{mcp → extensions/mcp}/manager.test.ts +1 -2
  197. package/src/{mcp → extensions/mcp}/manager.ts +3 -5
  198. package/src/extensions/mcp/name-transform.ts +33 -0
  199. package/src/extensions/mcp/policies.ts +47 -0
  200. package/src/extensions/mcp/tools.ts +47 -0
  201. package/src/{mcp → extensions/mcp}/types.ts +35 -7
  202. package/src/{agents → extensions/plugin}/plugin-config-loader.test.ts +18 -13
  203. package/src/{agents → extensions/plugin}/plugin-config-loader.ts +1 -1
  204. package/src/{agents → extensions/plugin}/plugin-loader.test.ts +41 -4
  205. package/src/extensions/plugin/plugin-loader.ts +106 -0
  206. package/src/extensions/plugin/plugin-module-import.ts +278 -0
  207. package/src/{agents → extensions/plugin}/plugin-sandbox-bootstrap.ts +30 -92
  208. package/src/{agents → extensions/plugin}/plugin-sandbox.test.ts +60 -3
  209. package/src/{agents → extensions/plugin}/plugin-sandbox.ts +146 -56
  210. package/src/hooks/index.ts +25 -0
  211. package/src/hooks/persistent.ts +661 -0
  212. package/src/hooks/subprocess-runner.ts +196 -0
  213. package/src/hooks/subprocess.ts +669 -0
  214. package/src/index.ts +200 -118
  215. package/src/prompt/default-system.ts +21 -0
  216. package/src/providers/local-provider-registry.ts +1 -1
  217. package/src/providers/local-provider-service.test.ts +23 -2
  218. package/src/providers/local-provider-service.ts +2 -2
  219. package/src/runtime/checkpoint-hooks.test.ts +167 -0
  220. package/src/runtime/checkpoint-hooks.ts +186 -0
  221. package/src/runtime/hook-file-hooks.test.ts +40 -1
  222. package/src/runtime/hook-file-hooks.ts +35 -16
  223. package/src/runtime/index.ts +4 -19
  224. package/src/runtime/rules.ts +4 -1
  225. package/src/runtime/runtime-builder.team-persistence.test.ts +3 -6
  226. package/src/runtime/runtime-builder.test.ts +266 -160
  227. package/src/runtime/runtime-builder.ts +120 -47
  228. package/src/runtime/runtime-parity.test.ts +22 -22
  229. package/src/runtime/session-runtime.ts +36 -6
  230. package/src/runtime/{sandbox/subprocess-sandbox.ts → subprocess-sandbox.ts} +24 -3
  231. package/src/runtime/team-runtime-registry.ts +1 -4
  232. package/src/runtime/tool-approval.ts +1 -1
  233. package/src/session/default-session-manager.e2e.test.ts +2 -2
  234. package/src/session/default-session-manager.test.ts +553 -9
  235. package/src/session/default-session-manager.ts +140 -46
  236. package/src/session/file-session-service.ts +3 -3
  237. package/src/session/index.ts +6 -6
  238. package/src/session/persistence-service.test.ts +212 -0
  239. package/src/session/{unified-session-persistence-service.ts → persistence-service.ts} +106 -172
  240. package/src/session/rpc-session-service.ts +3 -3
  241. package/src/session/runtime-oauth-token-manager.ts +1 -1
  242. package/src/session/session-agent-events.ts +1 -1
  243. package/src/session/session-artifacts.ts +32 -4
  244. package/src/session/session-config-builder.ts +22 -9
  245. package/src/session/session-graph.ts +1 -1
  246. package/src/session/session-host.ts +19 -11
  247. package/src/session/session-manager.ts +11 -6
  248. package/src/session/session-service.team-persistence.test.ts +1 -1
  249. package/src/session/session-service.ts +6 -9
  250. package/src/session/session-team-coordination.ts +7 -3
  251. package/src/session/session-telemetry.ts +1 -1
  252. package/src/session/utils/helpers.test.ts +160 -0
  253. package/src/session/utils/helpers.ts +289 -42
  254. package/src/session/utils/types.ts +47 -7
  255. package/src/session/workspace-manager.ts +5 -3
  256. package/src/session/workspace-manifest.ts +3 -49
  257. package/src/storage/file-team-store.ts +2 -5
  258. package/src/storage/provider-settings-legacy-migration.ts +2 -2
  259. package/src/storage/provider-settings-manager.test.ts +1 -1
  260. package/src/storage/sqlite-team-store.ts +212 -125
  261. package/src/storage/team-store.ts +1 -5
  262. package/src/team/delegated-agent.ts +131 -0
  263. package/src/team/index.ts +1 -0
  264. package/src/team/multi-agent.lifecycle.test.ts +201 -0
  265. package/src/team/multi-agent.ts +1666 -0
  266. package/src/team/projections.ts +2 -4
  267. package/src/team/runtime.ts +54 -0
  268. package/src/team/spawn-agent-tool.test.ts +387 -0
  269. package/src/team/spawn-agent-tool.ts +207 -0
  270. package/src/team/subagent-prompts.ts +41 -0
  271. package/src/team/team-tools.test.ts +802 -0
  272. package/src/team/team-tools.ts +792 -0
  273. package/src/telemetry/OpenTelemetryProvider.test.ts +25 -3
  274. package/src/telemetry/OpenTelemetryProvider.ts +108 -18
  275. package/src/telemetry/TelemetryLoggerSink.test.ts +42 -0
  276. package/src/telemetry/{LoggerTelemetryAdapter.ts → TelemetryLoggerSink.ts} +21 -14
  277. package/src/telemetry/TelemetryService.test.ts +7 -7
  278. package/src/telemetry/TelemetryService.ts +2 -4
  279. package/src/tools/definitions.test.ts +76 -0
  280. package/src/tools/definitions.ts +41 -2
  281. package/src/tools/executors/apply-patch.ts +1 -1
  282. package/src/tools/executors/editor.ts +1 -1
  283. package/src/tools/executors/file-read.ts +1 -1
  284. package/src/tools/executors/search.ts +1 -1
  285. package/src/tools/executors/web-fetch.ts +1 -1
  286. package/src/tools/index.ts +6 -1
  287. package/src/tools/model-tool-routing.ts +2 -0
  288. package/src/tools/presets.test.ts +8 -0
  289. package/src/tools/presets.ts +40 -2
  290. package/src/tools/schemas.ts +19 -0
  291. package/src/tools/types.ts +31 -2
  292. package/src/types/config.ts +61 -7
  293. package/src/types/events.ts +1 -1
  294. package/src/types/index.ts +0 -1
  295. package/src/types/provider-settings.ts +1 -1
  296. package/src/types/storage.ts +2 -5
  297. package/src/types.ts +32 -44
  298. package/dist/agents/agent-config-loader.d.ts.map +0 -1
  299. package/dist/agents/agent-config-parser.d.ts.map +0 -1
  300. package/dist/agents/hooks-config-loader.d.ts.map +0 -1
  301. package/dist/agents/index.d.ts.map +0 -1
  302. package/dist/agents/plugin-config-loader.d.ts.map +0 -1
  303. package/dist/agents/plugin-loader.d.ts.map +0 -1
  304. package/dist/agents/plugin-sandbox-bootstrap.js +0 -446
  305. package/dist/agents/plugin-sandbox.d.ts.map +0 -1
  306. package/dist/agents/unified-config-file-watcher.d.ts.map +0 -1
  307. package/dist/agents/user-instruction-config-loader.d.ts.map +0 -1
  308. package/dist/mcp/config-loader.d.ts.map +0 -1
  309. package/dist/mcp/index.d.ts +0 -5
  310. package/dist/mcp/index.d.ts.map +0 -1
  311. package/dist/mcp/manager.d.ts.map +0 -1
  312. package/dist/mcp/types.d.ts.map +0 -1
  313. package/dist/runtime/commands.d.ts.map +0 -1
  314. package/dist/runtime/sandbox/subprocess-sandbox.d.ts.map +0 -1
  315. package/dist/runtime/skills.d.ts +0 -14
  316. package/dist/runtime/skills.d.ts.map +0 -1
  317. package/dist/runtime/workflows.d.ts +0 -14
  318. package/dist/runtime/workflows.d.ts.map +0 -1
  319. package/dist/session/unified-session-persistence-service.d.ts.map +0 -1
  320. package/dist/telemetry/LoggerTelemetryAdapter.d.ts.map +0 -1
  321. package/dist/types/workspace.d.ts +0 -8
  322. package/dist/types/workspace.d.ts.map +0 -1
  323. package/src/agents/plugin-loader.ts +0 -175
  324. package/src/runtime/skills.ts +0 -44
  325. package/src/runtime/workflows.test.ts +0 -119
  326. package/src/runtime/workflows.ts +0 -45
  327. package/src/session/unified-session-persistence-service.test.ts +0 -85
  328. package/src/telemetry/LoggerTelemetryAdapter.test.ts +0 -42
  329. package/src/types/workspace.ts +0 -7
  330. /package/dist/{agents → extensions/config}/agent-config-loader.d.ts +0 -0
  331. /package/dist/{agents → extensions/config}/unified-config-file-watcher.d.ts +0 -0
  332. /package/dist/{agents → extensions/config}/user-instruction-config-loader.d.ts +0 -0
  333. /package/dist/{mcp → extensions/mcp}/config-loader.d.ts +0 -0
  334. /package/dist/runtime/{sandbox/subprocess-sandbox.d.ts → subprocess-sandbox.d.ts} +0 -0
  335. /package/src/{agents → extensions/config}/agent-config-loader.ts +0 -0
  336. /package/src/{agents → extensions/config}/hooks-config-loader.test.ts +0 -0
  337. /package/src/{agents → extensions/config}/unified-config-file-watcher.test.ts +0 -0
  338. /package/src/{mcp → extensions/mcp}/config-loader.test.ts +0 -0
  339. /package/src/{mcp → extensions/mcp}/config-loader.ts +0 -0
@@ -0,0 +1,661 @@
1
+ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
+ import {
3
+ type AgentHooks,
4
+ type HookSessionContextProvider,
5
+ resolveHookSessionContext,
6
+ } from "@clinebot/shared";
7
+ import {
8
+ type AgentAbortHookPayload,
9
+ type AgentEndHookPayload,
10
+ type AgentErrorHookPayload,
11
+ type AgentResumeHookPayload,
12
+ type AgentStartHookPayload,
13
+ createSubprocessHooks,
14
+ type HookEventName,
15
+ type HookEventPayload,
16
+ type HookEventPayloadBase,
17
+ HookOutputSchema,
18
+ type PromptSubmitHookPayload,
19
+ type RunHookResult,
20
+ type SessionShutdownHookPayload,
21
+ type ToolCallHookPayload,
22
+ type ToolResultHookPayload,
23
+ } from "./subprocess";
24
+
25
+ type AgentHookControl = NonNullable<
26
+ Awaited<ReturnType<NonNullable<AgentHooks["onToolCallStart"]>>>
27
+ >;
28
+ type AgentHookRunStartContext = Parameters<
29
+ NonNullable<AgentHooks["onRunStart"]>
30
+ >[0];
31
+ type AgentHookSessionShutdownContext = Parameters<
32
+ NonNullable<AgentHooks["onSessionShutdown"]>
33
+ >[0];
34
+ type AgentHookStopErrorContext = Parameters<
35
+ NonNullable<AgentHooks["onStopError"]>
36
+ >[0];
37
+ type AgentHookToolCallEndContext = Parameters<
38
+ NonNullable<AgentHooks["onToolCallEnd"]>
39
+ >[0];
40
+ type AgentHookToolCallStartContext = Parameters<
41
+ NonNullable<AgentHooks["onToolCallStart"]>
42
+ >[0];
43
+ type AgentHookTurnEndContext = Parameters<
44
+ NonNullable<AgentHooks["onTurnEnd"]>
45
+ >[0];
46
+
47
+ interface PersistentHookRequest {
48
+ id: string;
49
+ payload: HookEventPayload;
50
+ }
51
+
52
+ interface PersistentHookResponse {
53
+ id: string;
54
+ ok: boolean;
55
+ result?: RunHookResult;
56
+ error?: string;
57
+ }
58
+
59
+ interface PendingRequest {
60
+ resolve: (result: RunHookResult | undefined) => void;
61
+ reject: (error: Error) => void;
62
+ timeoutId?: NodeJS.Timeout;
63
+ }
64
+
65
+ export interface PersistentHookClientOptions {
66
+ command: string[];
67
+ cwd?: string;
68
+ env?: NodeJS.ProcessEnv;
69
+ onSpawn?: (event: {
70
+ command: string[];
71
+ pid?: number;
72
+ detached: boolean;
73
+ }) => void;
74
+ }
75
+
76
+ export class PersistentHookClient {
77
+ private readonly options: PersistentHookClientOptions;
78
+ private child?: ChildProcessWithoutNullStreams;
79
+ private startPromise?: Promise<void>;
80
+ private readonly pending = new Map<string, PendingRequest>();
81
+ private nextId = 0;
82
+ private stdoutBuffer = "";
83
+ private closing = false;
84
+
85
+ constructor(options: PersistentHookClientOptions) {
86
+ this.options = options;
87
+ }
88
+
89
+ async send(
90
+ payload: HookEventPayload,
91
+ options?: { timeoutMs?: number },
92
+ ): Promise<RunHookResult | undefined> {
93
+ await this.ensureStarted();
94
+ const child = this.child;
95
+ if (!child?.stdin || child.stdin.destroyed) {
96
+ throw new Error("persistent hook worker stdin is unavailable");
97
+ }
98
+
99
+ const id = `hook_req_${String(++this.nextId).padStart(8, "0")}`;
100
+ const frame = `${JSON.stringify({ id, payload } satisfies PersistentHookRequest)}\n`;
101
+
102
+ return await new Promise<RunHookResult | undefined>((resolve, reject) => {
103
+ const pending: PendingRequest = { resolve, reject };
104
+ const timeoutMs = options?.timeoutMs ?? 0;
105
+ if (timeoutMs > 0) {
106
+ pending.timeoutId = setTimeout(() => {
107
+ this.pending.delete(id);
108
+ reject(
109
+ new Error(`Persistent hook request timed out after ${timeoutMs}ms`),
110
+ );
111
+ }, timeoutMs);
112
+ }
113
+ this.pending.set(id, pending);
114
+ child.stdin.write(frame, (error) => {
115
+ if (!error) {
116
+ return;
117
+ }
118
+ this.clearPendingRequest(id);
119
+ reject(error instanceof Error ? error : new Error(String(error)));
120
+ });
121
+ });
122
+ }
123
+
124
+ async close(): Promise<void> {
125
+ this.closing = true;
126
+ for (const id of [...this.pending.keys()]) {
127
+ this.rejectPendingRequest(id, new Error("persistent hook worker closed"));
128
+ }
129
+ const child = this.child;
130
+ this.child = undefined;
131
+ this.startPromise = undefined;
132
+ this.stdoutBuffer = "";
133
+ if (!child) {
134
+ return;
135
+ }
136
+ await new Promise<void>((resolve) => {
137
+ let settled = false;
138
+ let forceKillId: NodeJS.Timeout | undefined;
139
+ const finish = () => {
140
+ if (settled) {
141
+ return;
142
+ }
143
+ settled = true;
144
+ if (forceKillId) {
145
+ clearTimeout(forceKillId);
146
+ }
147
+ resolve();
148
+ };
149
+
150
+ child.once("close", () => finish());
151
+
152
+ try {
153
+ child.stdin.end();
154
+ } catch {
155
+ // Ignore stdin shutdown failures and continue to terminate the child.
156
+ }
157
+
158
+ if (!child.killed) {
159
+ try {
160
+ child.kill("SIGTERM");
161
+ } catch {
162
+ finish();
163
+ return;
164
+ }
165
+ }
166
+
167
+ forceKillId = setTimeout(() => {
168
+ if (!child.killed) {
169
+ try {
170
+ child.kill("SIGKILL");
171
+ } catch {
172
+ // Ignore final kill errors.
173
+ }
174
+ }
175
+ }, 250);
176
+
177
+ setTimeout(() => finish(), 1000);
178
+ });
179
+ }
180
+
181
+ private async ensureStarted(): Promise<void> {
182
+ if (this.child && !this.child.killed) {
183
+ return;
184
+ }
185
+ if (this.startPromise) {
186
+ return await this.startPromise;
187
+ }
188
+ this.closing = false;
189
+ this.startPromise = this.start();
190
+ try {
191
+ await this.startPromise;
192
+ } finally {
193
+ this.startPromise = undefined;
194
+ }
195
+ }
196
+
197
+ private async start(): Promise<void> {
198
+ const command = this.options.command;
199
+ if (!Array.isArray(command) || command.length === 0) {
200
+ throw new Error("PersistentHookClient requires a non-empty command");
201
+ }
202
+ const child = spawn(command[0], command.slice(1), {
203
+ cwd: this.options.cwd,
204
+ env: this.options.env,
205
+ stdio: ["pipe", "pipe", "pipe"],
206
+ });
207
+ this.child = child;
208
+ this.stdoutBuffer = "";
209
+
210
+ child.stdout.setEncoding("utf8");
211
+ child.stdout.on("data", (chunk: string) => {
212
+ this.handleStdout(chunk);
213
+ });
214
+
215
+ child.stderr.setEncoding("utf8");
216
+ let stderr = "";
217
+ child.stderr.on("data", (chunk: string) => {
218
+ stderr += chunk;
219
+ });
220
+
221
+ child.once("error", (error) => {
222
+ this.handleChildExit(
223
+ error instanceof Error ? error : new Error(String(error)),
224
+ );
225
+ });
226
+ child.once("close", (code, signal) => {
227
+ const detail =
228
+ stderr.trim() ||
229
+ `persistent hook worker exited with code ${code ?? "null"}${signal ? ` signal ${signal}` : ""}`;
230
+ this.handleChildExit(new Error(detail));
231
+ });
232
+
233
+ await new Promise<void>((resolve, reject) => {
234
+ child.once("spawn", () => {
235
+ try {
236
+ this.options.onSpawn?.({
237
+ command,
238
+ pid: child.pid ?? undefined,
239
+ detached: false,
240
+ });
241
+ } catch {
242
+ // Logging callbacks must not break subprocess execution.
243
+ }
244
+ resolve();
245
+ });
246
+ child.once("error", (error) =>
247
+ reject(error instanceof Error ? error : new Error(String(error))),
248
+ );
249
+ });
250
+ }
251
+
252
+ private handleStdout(chunk: string): void {
253
+ this.stdoutBuffer += chunk;
254
+ let newlineIndex = this.stdoutBuffer.indexOf("\n");
255
+ while (newlineIndex >= 0) {
256
+ const line = this.stdoutBuffer.slice(0, newlineIndex).trim();
257
+ this.stdoutBuffer = this.stdoutBuffer.slice(newlineIndex + 1);
258
+ if (line) {
259
+ this.handleResponseLine(line);
260
+ }
261
+ newlineIndex = this.stdoutBuffer.indexOf("\n");
262
+ }
263
+ }
264
+
265
+ private handleResponseLine(line: string): void {
266
+ let parsed: PersistentHookResponse;
267
+ try {
268
+ parsed = JSON.parse(line) as PersistentHookResponse;
269
+ } catch {
270
+ return;
271
+ }
272
+ if (!parsed || typeof parsed.id !== "string") {
273
+ return;
274
+ }
275
+ const pending = this.clearPendingRequest(parsed.id);
276
+ if (!pending) {
277
+ return;
278
+ }
279
+ if (!parsed.ok) {
280
+ pending.reject(
281
+ new Error(parsed.error || "persistent hook worker failed"),
282
+ );
283
+ return;
284
+ }
285
+ pending.resolve(parsed.result);
286
+ }
287
+
288
+ private handleChildExit(error: Error): void {
289
+ const child = this.child;
290
+ if (!child) {
291
+ return;
292
+ }
293
+ this.child = undefined;
294
+ this.stdoutBuffer = "";
295
+ if (this.closing) {
296
+ return;
297
+ }
298
+ for (const id of [...this.pending.keys()]) {
299
+ this.rejectPendingRequest(id, error);
300
+ }
301
+ }
302
+
303
+ private clearPendingRequest(id: string): PendingRequest | undefined {
304
+ const pending = this.pending.get(id);
305
+ if (!pending) {
306
+ return undefined;
307
+ }
308
+ this.pending.delete(id);
309
+ if (pending.timeoutId) {
310
+ clearTimeout(pending.timeoutId);
311
+ }
312
+ return pending;
313
+ }
314
+
315
+ private rejectPendingRequest(id: string, error: Error): void {
316
+ const pending = this.clearPendingRequest(id);
317
+ pending?.reject(error);
318
+ }
319
+ }
320
+
321
+ export interface PersistentSubprocessHooksOptions {
322
+ command?: string[];
323
+ cwd?: string;
324
+ env?: NodeJS.ProcessEnv;
325
+ timeoutMs?: number;
326
+ onSpawn?: (event: {
327
+ command: string[];
328
+ pid?: number;
329
+ detached: boolean;
330
+ }) => void;
331
+ onDispatchError?: (error: Error, payload: HookEventPayload) => void;
332
+ onDispatch?: (event: {
333
+ payload: HookEventPayload;
334
+ result?: RunHookResult;
335
+ detached: boolean;
336
+ }) => void;
337
+ sessionContext?: HookSessionContextProvider;
338
+ fallbackToSubprocess?: boolean;
339
+ }
340
+
341
+ export interface PersistentSubprocessHookControl {
342
+ hooks: AgentHooks;
343
+ shutdown: (ctx: {
344
+ agentId: string;
345
+ conversationId: string;
346
+ parentAgentId: string | null;
347
+ reason?: string;
348
+ }) => Promise<void>;
349
+ client: PersistentHookClient;
350
+ }
351
+
352
+ const DEFAULT_HOOK_WORKER_COMMAND = ["agent", "hook-worker"];
353
+
354
+ function toError(error: unknown): Error {
355
+ return error instanceof Error ? error : new Error(String(error));
356
+ }
357
+
358
+ function toHookControl(value: unknown): AgentHookControl | undefined {
359
+ if (!value || typeof value !== "object") {
360
+ return undefined;
361
+ }
362
+ const parsed = HookOutputSchema.safeParse(value);
363
+ if (!parsed.success) {
364
+ return undefined;
365
+ }
366
+ const maybe = parsed.data;
367
+ const hasControlKey =
368
+ "cancel" in maybe ||
369
+ "review" in maybe ||
370
+ "context" in maybe ||
371
+ "contextModification" in maybe ||
372
+ "overrideInput" in maybe ||
373
+ "errorMessage" in maybe;
374
+ if (!hasControlKey) {
375
+ return undefined;
376
+ }
377
+ const contextFromHook =
378
+ typeof maybe.context === "string"
379
+ ? maybe.context
380
+ : typeof maybe.contextModification === "string"
381
+ ? maybe.contextModification
382
+ : typeof maybe.errorMessage === "string" &&
383
+ maybe.errorMessage.length > 0
384
+ ? maybe.errorMessage
385
+ : undefined;
386
+ return {
387
+ cancel: typeof maybe.cancel === "boolean" ? maybe.cancel : undefined,
388
+ review: typeof maybe.review === "boolean" ? maybe.review : undefined,
389
+ context: contextFromHook,
390
+ overrideInput: Object.hasOwn(maybe, "overrideInput")
391
+ ? maybe.overrideInput
392
+ : undefined,
393
+ };
394
+ }
395
+
396
+ function mapParams(input: unknown): Record<string, string> {
397
+ if (!input || typeof input !== "object") {
398
+ return {};
399
+ }
400
+ const output: Record<string, string> = {};
401
+ for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
402
+ if (typeof value === "string") {
403
+ output[key] = value;
404
+ } else {
405
+ output[key] = JSON.stringify(value);
406
+ }
407
+ }
408
+ return output;
409
+ }
410
+
411
+ function basePayload(
412
+ hookName: HookEventName,
413
+ ctx: {
414
+ agentId: string;
415
+ conversationId: string;
416
+ parentAgentId: string | null;
417
+ },
418
+ options: PersistentSubprocessHooksOptions,
419
+ ): HookEventPayloadBase {
420
+ const env = options.env ?? process.env;
421
+ const userId = env.CLINE_USER_ID?.trim() || env.USER?.trim() || "unknown";
422
+ const workspaceRoot = options.cwd || process.cwd();
423
+ return {
424
+ clineVersion: env.CLINE_VERSION?.trim() || "",
425
+ hookName,
426
+ timestamp: new Date().toISOString(),
427
+ taskId: ctx.conversationId,
428
+ sessionContext: resolveHookSessionContext(options.sessionContext, {
429
+ hookName,
430
+ conversationId: ctx.conversationId,
431
+ agentId: ctx.agentId,
432
+ parentAgentId: ctx.parentAgentId,
433
+ }),
434
+ workspaceRoots: workspaceRoot ? [workspaceRoot] : [],
435
+ userId,
436
+ agent_id: ctx.agentId,
437
+ parent_agent_id: ctx.parentAgentId,
438
+ };
439
+ }
440
+
441
+ function serializeHookError(error: Error): AgentErrorHookPayload["error"] {
442
+ return {
443
+ name: error.name,
444
+ message: error.message,
445
+ stack: error.stack,
446
+ };
447
+ }
448
+
449
+ function isAbortReason(reason?: string): boolean {
450
+ const value = String(reason ?? "").toLowerCase();
451
+ return (
452
+ value.includes("cancel") ||
453
+ value.includes("abort") ||
454
+ value.includes("interrupt")
455
+ );
456
+ }
457
+
458
+ async function dispatchDetached(
459
+ client: PersistentHookClient,
460
+ payload: HookEventPayload,
461
+ options: PersistentSubprocessHooksOptions,
462
+ ): Promise<void> {
463
+ try {
464
+ const result = await client.send(payload, {
465
+ timeoutMs: options.timeoutMs,
466
+ });
467
+ options.onDispatch?.({ payload, result, detached: true });
468
+ } catch (error) {
469
+ options.onDispatchError?.(toError(error), payload);
470
+ }
471
+ }
472
+
473
+ export function createPersistentSubprocessHooks(
474
+ options: PersistentSubprocessHooksOptions = {},
475
+ ): PersistentSubprocessHookControl {
476
+ const command = options.command ?? DEFAULT_HOOK_WORKER_COMMAND;
477
+ const client = new PersistentHookClient({
478
+ command,
479
+ cwd: options.cwd,
480
+ env: options.env,
481
+ onSpawn: options.onSpawn,
482
+ });
483
+
484
+ const fallbackHooks = options.fallbackToSubprocess
485
+ ? createSubprocessHooks(options)
486
+ : undefined;
487
+
488
+ const onRunStart = async (
489
+ ctx: AgentHookRunStartContext,
490
+ ): Promise<AgentHookControl | undefined> => {
491
+ const isResume =
492
+ (options.env ?? process.env).CLINE_HOOK_AGENT_RESUME === "1";
493
+ if (isResume) {
494
+ const resumePayload: AgentResumeHookPayload = {
495
+ ...basePayload("agent_resume", ctx, options),
496
+ hookName: "agent_resume",
497
+ taskResume: {
498
+ taskMetadata: {},
499
+ previousState: {},
500
+ },
501
+ };
502
+ await dispatchDetached(client, resumePayload, options);
503
+ } else {
504
+ const startPayload: AgentStartHookPayload = {
505
+ ...basePayload("agent_start", ctx, options),
506
+ hookName: "agent_start",
507
+ taskStart: { taskMetadata: {} },
508
+ };
509
+ await dispatchDetached(client, startPayload, options);
510
+ }
511
+
512
+ const promptPayload: PromptSubmitHookPayload = {
513
+ ...basePayload("prompt_submit", ctx, options),
514
+ hookName: "prompt_submit",
515
+ userPromptSubmit: {
516
+ prompt: ctx.userMessage,
517
+ attachments: [],
518
+ },
519
+ };
520
+ await dispatchDetached(client, promptPayload, options);
521
+ return undefined;
522
+ };
523
+
524
+ const onToolCallStart = async (
525
+ ctx: AgentHookToolCallStartContext,
526
+ ): Promise<AgentHookControl | undefined> => {
527
+ const payload: ToolCallHookPayload = {
528
+ ...basePayload("tool_call", ctx, options),
529
+ hookName: "tool_call",
530
+ iteration: ctx.iteration,
531
+ tool_call: {
532
+ id: ctx.call.id,
533
+ name: ctx.call.name,
534
+ input: ctx.call.input,
535
+ },
536
+ preToolUse: {
537
+ toolName: ctx.call.name,
538
+ parameters: mapParams(ctx.call.input),
539
+ },
540
+ };
541
+
542
+ try {
543
+ const result = await client.send(payload, {
544
+ timeoutMs: options.timeoutMs,
545
+ });
546
+ options.onDispatch?.({ payload, result, detached: false });
547
+ if (result?.timedOut) {
548
+ throw new Error("tool_call hook command timed out");
549
+ }
550
+ if (result?.parseError) {
551
+ throw new Error(
552
+ `tool_call hook produced invalid control JSON: ${result.parseError}`,
553
+ );
554
+ }
555
+ return toHookControl(result?.parsedJson);
556
+ } catch (error) {
557
+ options.onDispatchError?.(toError(error), payload);
558
+ if (fallbackHooks) {
559
+ return await fallbackHooks.hooks.onToolCallStart?.(ctx);
560
+ }
561
+ return undefined;
562
+ }
563
+ };
564
+
565
+ const onToolCallEnd = async (
566
+ ctx: AgentHookToolCallEndContext,
567
+ ): Promise<AgentHookControl | undefined> => {
568
+ const payload: ToolResultHookPayload = {
569
+ ...basePayload("tool_result", ctx, options),
570
+ hookName: "tool_result",
571
+ iteration: ctx.iteration,
572
+ tool_result: ctx.record,
573
+ postToolUse: {
574
+ toolName: ctx.record.name,
575
+ parameters: mapParams(ctx.record.input),
576
+ result:
577
+ typeof ctx.record.output === "string"
578
+ ? ctx.record.output
579
+ : JSON.stringify(ctx.record.output),
580
+ success: !ctx.record.error,
581
+ executionTimeMs: ctx.record.durationMs,
582
+ },
583
+ };
584
+ await dispatchDetached(client, payload, options);
585
+ return undefined;
586
+ };
587
+
588
+ const onTurnEnd = async (
589
+ ctx: AgentHookTurnEndContext,
590
+ ): Promise<AgentHookControl | undefined> => {
591
+ const payload: AgentEndHookPayload = {
592
+ ...basePayload("agent_end", ctx, options),
593
+ hookName: "agent_end",
594
+ iteration: ctx.iteration,
595
+ turn: ctx.turn,
596
+ taskComplete: { taskMetadata: {} },
597
+ };
598
+ await dispatchDetached(client, payload, options);
599
+ return undefined;
600
+ };
601
+
602
+ const onStopError = async (
603
+ ctx: AgentHookStopErrorContext,
604
+ ): Promise<AgentHookControl | undefined> => {
605
+ const hookName: HookEventName = isAbortReason(ctx.error.message)
606
+ ? "agent_abort"
607
+ : "agent_error";
608
+ const payload: AgentErrorHookPayload | AgentAbortHookPayload =
609
+ hookName === "agent_error"
610
+ ? {
611
+ ...basePayload(hookName, ctx, options),
612
+ hookName,
613
+ iteration: ctx.iteration,
614
+ error: serializeHookError(ctx.error),
615
+ taskCancel: { taskMetadata: {} },
616
+ }
617
+ : {
618
+ ...basePayload(hookName, ctx, options),
619
+ hookName,
620
+ reason: ctx.error.message,
621
+ taskCancel: { taskMetadata: {} },
622
+ };
623
+ await dispatchDetached(client, payload, options);
624
+ return undefined;
625
+ };
626
+
627
+ const shutdown = async ({
628
+ agentId,
629
+ conversationId,
630
+ parentAgentId,
631
+ reason,
632
+ }: AgentHookSessionShutdownContext): Promise<void> => {
633
+ const payload: SessionShutdownHookPayload = {
634
+ ...basePayload(
635
+ "session_shutdown",
636
+ {
637
+ agentId,
638
+ conversationId,
639
+ parentAgentId,
640
+ },
641
+ options,
642
+ ),
643
+ hookName: "session_shutdown",
644
+ reason,
645
+ };
646
+ await dispatchDetached(client, payload, options);
647
+ await client.close();
648
+ };
649
+
650
+ return {
651
+ hooks: {
652
+ onRunStart,
653
+ onToolCallStart,
654
+ onToolCallEnd,
655
+ onTurnEnd,
656
+ onStopError,
657
+ },
658
+ shutdown,
659
+ client,
660
+ };
661
+ }