@clinebot/core 0.0.36 → 0.0.37

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 (228) hide show
  1. package/dist/ClineCore.d.ts +312 -3
  2. package/dist/ClineCore.d.ts.map +1 -1
  3. package/dist/account/cline-account-service.d.ts.map +1 -1
  4. package/dist/cron/cron-event-ingress.d.ts +38 -0
  5. package/dist/cron/cron-event-ingress.d.ts.map +1 -0
  6. package/dist/cron/cron-materializer.d.ts +36 -0
  7. package/dist/cron/cron-materializer.d.ts.map +1 -0
  8. package/dist/cron/cron-reconciler.d.ts +62 -0
  9. package/dist/cron/cron-reconciler.d.ts.map +1 -0
  10. package/dist/cron/cron-report-writer.d.ts +41 -0
  11. package/dist/cron/cron-report-writer.d.ts.map +1 -0
  12. package/dist/cron/cron-runner.d.ts +43 -0
  13. package/dist/cron/cron-runner.d.ts.map +1 -0
  14. package/dist/cron/cron-schema.d.ts +3 -0
  15. package/dist/cron/cron-schema.d.ts.map +1 -0
  16. package/dist/cron/cron-service.d.ts +57 -0
  17. package/dist/cron/cron-service.d.ts.map +1 -0
  18. package/dist/cron/cron-spec-parser.d.ts +27 -0
  19. package/dist/cron/cron-spec-parser.d.ts.map +1 -0
  20. package/dist/cron/cron-watcher.d.ts +23 -0
  21. package/dist/cron/cron-watcher.d.ts.map +1 -0
  22. package/dist/cron/scheduler.d.ts +3 -1
  23. package/dist/cron/scheduler.d.ts.map +1 -1
  24. package/dist/cron/sqlite-cron-store.d.ts +230 -0
  25. package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
  26. package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
  27. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  28. package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
  29. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  30. package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
  31. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  32. package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
  33. package/dist/extensions/tools/constants.d.ts +1 -0
  34. package/dist/extensions/tools/constants.d.ts.map +1 -1
  35. package/dist/extensions/tools/definitions.d.ts +2 -3
  36. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  37. package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
  38. package/dist/extensions/tools/helpers.d.ts +1 -0
  39. package/dist/extensions/tools/helpers.d.ts.map +1 -1
  40. package/dist/extensions/tools/index.d.ts +1 -2
  41. package/dist/extensions/tools/index.d.ts.map +1 -1
  42. package/dist/extensions/tools/presets.d.ts +1 -1
  43. package/dist/extensions/tools/schemas.d.ts +25 -3
  44. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  45. package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
  46. package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
  47. package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
  48. package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
  49. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  50. package/dist/extensions/tools/types.d.ts +0 -5
  51. package/dist/extensions/tools/types.d.ts.map +1 -1
  52. package/dist/hooks/hook-bridge.d.ts +118 -0
  53. package/dist/hooks/hook-bridge.d.ts.map +1 -0
  54. package/dist/hooks/hook-file-hooks.d.ts +2 -1
  55. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  56. package/dist/hooks/hook-registry.d.ts +16 -0
  57. package/dist/hooks/hook-registry.d.ts.map +1 -0
  58. package/dist/hub/browser-websocket.d.ts.map +1 -1
  59. package/dist/hub/client.d.ts +7 -1
  60. package/dist/hub/client.d.ts.map +1 -1
  61. package/dist/hub/daemon-entry.js +721 -461
  62. package/dist/hub/daemon.d.ts.map +1 -1
  63. package/dist/hub/defaults.d.ts +8 -4
  64. package/dist/hub/defaults.d.ts.map +1 -1
  65. package/dist/hub/index.js +665 -415
  66. package/dist/hub/runtime-handlers.d.ts.map +1 -1
  67. package/dist/hub/server.d.ts +18 -0
  68. package/dist/hub/server.d.ts.map +1 -1
  69. package/dist/hub/session-client.d.ts +3 -0
  70. package/dist/hub/session-client.d.ts.map +1 -1
  71. package/dist/hub/start-shared-server.d.ts.map +1 -1
  72. package/dist/hub/ui-client.d.ts +1 -0
  73. package/dist/hub/ui-client.d.ts.map +1 -1
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +756 -467
  77. package/dist/llms/cline-recommended-models.d.ts +20 -0
  78. package/dist/llms/cline-recommended-models.d.ts.map +1 -0
  79. package/dist/llms/handler-factory.d.ts +16 -0
  80. package/dist/llms/handler-factory.d.ts.map +1 -0
  81. package/dist/llms/provider-defaults.d.ts.map +1 -1
  82. package/dist/llms/provider-settings.d.ts +45 -2
  83. package/dist/llms/provider-settings.d.ts.map +1 -1
  84. package/dist/llms/runtime-registry.d.ts.map +1 -1
  85. package/dist/runtime/agent-config-adapter.d.ts +148 -0
  86. package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
  87. package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
  88. package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
  89. package/dist/runtime/history.d.ts +6 -0
  90. package/dist/runtime/history.d.ts.map +1 -1
  91. package/dist/runtime/host.d.ts.map +1 -1
  92. package/dist/runtime/loop-detection.d.ts +59 -0
  93. package/dist/runtime/loop-detection.d.ts.map +1 -0
  94. package/dist/runtime/mistake-tracker.d.ts +69 -0
  95. package/dist/runtime/mistake-tracker.d.ts.map +1 -0
  96. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  97. package/dist/runtime/runtime-event-adapter.d.ts +102 -0
  98. package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
  99. package/dist/runtime/runtime-host.d.ts +28 -3
  100. package/dist/runtime/runtime-host.d.ts.map +1 -1
  101. package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
  102. package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
  103. package/dist/runtime/session-runtime.d.ts +16 -3
  104. package/dist/runtime/session-runtime.d.ts.map +1 -1
  105. package/dist/runtime/user-input-builder.d.ts +24 -0
  106. package/dist/runtime/user-input-builder.d.ts.map +1 -0
  107. package/dist/services/index.js +28 -0
  108. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  109. package/dist/services/plugin-tools.d.ts.map +1 -1
  110. package/dist/services/providers/local-provider-registry.d.ts +197 -21
  111. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  112. package/dist/services/providers/local-provider-service.d.ts +3 -1
  113. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  114. package/dist/services/session-data.d.ts.map +1 -1
  115. package/dist/services/session-telemetry.d.ts +7 -2
  116. package/dist/services/session-telemetry.d.ts.map +1 -1
  117. package/dist/services/storage/file-team-store.d.ts.map +1 -1
  118. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  119. package/dist/services/storage/provider-settings-manager.d.ts +1 -0
  120. package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
  121. package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
  122. package/dist/session/conversation-store.d.ts +30 -0
  123. package/dist/session/conversation-store.d.ts.map +1 -0
  124. package/dist/session/message-builder.d.ts +65 -0
  125. package/dist/session/message-builder.d.ts.map +1 -0
  126. package/dist/session/session-manifest.d.ts +1 -1
  127. package/dist/transports/hub.d.ts +14 -3
  128. package/dist/transports/hub.d.ts.map +1 -1
  129. package/dist/transports/local.d.ts +14 -4
  130. package/dist/transports/local.d.ts.map +1 -1
  131. package/dist/transports/remote.d.ts.map +1 -1
  132. package/dist/types/chat-schema.d.ts +5 -5
  133. package/dist/types/config.d.ts +9 -0
  134. package/dist/types/config.d.ts.map +1 -1
  135. package/dist/types/events.d.ts +7 -6
  136. package/dist/types/events.d.ts.map +1 -1
  137. package/dist/types/provider-settings.d.ts +2 -2
  138. package/dist/types/provider-settings.d.ts.map +1 -1
  139. package/dist/types/session.d.ts +5 -2
  140. package/dist/types/session.d.ts.map +1 -1
  141. package/dist/types.d.ts +4 -4
  142. package/dist/types.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/ClineCore.ts +691 -6
  145. package/src/account/cline-account-service.ts +44 -6
  146. package/src/cron/cron-event-ingress.ts +357 -0
  147. package/src/cron/cron-materializer.ts +97 -0
  148. package/src/cron/cron-reconciler.ts +241 -0
  149. package/src/cron/cron-report-writer.ts +153 -0
  150. package/src/cron/cron-runner.ts +495 -0
  151. package/src/cron/cron-schema.ts +127 -0
  152. package/src/cron/cron-service.ts +163 -0
  153. package/src/cron/cron-spec-parser.ts +489 -0
  154. package/src/cron/cron-watcher.ts +102 -0
  155. package/src/cron/index.ts +10 -0
  156. package/src/cron/scheduler.ts +141 -6
  157. package/src/cron/sqlite-cron-store.ts +1286 -0
  158. package/src/extensions/plugin/plugin-config-loader.ts +21 -1
  159. package/src/extensions/plugin/plugin-loader.ts +25 -9
  160. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
  161. package/src/extensions/plugin/plugin-sandbox.ts +131 -7
  162. package/src/extensions/tools/constants.ts +2 -0
  163. package/src/extensions/tools/definitions.ts +31 -22
  164. package/src/extensions/tools/executors/editor.ts +4 -3
  165. package/src/extensions/tools/helpers.ts +24 -0
  166. package/src/extensions/tools/index.ts +1 -2
  167. package/src/extensions/tools/presets.ts +1 -1
  168. package/src/extensions/tools/schemas.ts +13 -18
  169. package/src/extensions/tools/team/delegated-agent.ts +8 -3
  170. package/src/extensions/tools/team/multi-agent.ts +135 -19
  171. package/src/extensions/tools/team/team-tools.ts +151 -91
  172. package/src/extensions/tools/types.ts +0 -6
  173. package/src/hooks/hook-bridge.ts +489 -0
  174. package/src/hooks/hook-file-hooks.ts +58 -3
  175. package/src/hooks/hook-registry.ts +257 -0
  176. package/src/hub/browser-websocket.ts +26 -4
  177. package/src/hub/client.ts +72 -13
  178. package/src/hub/daemon-entry.ts +35 -0
  179. package/src/hub/daemon.ts +117 -14
  180. package/src/hub/defaults.ts +39 -12
  181. package/src/hub/runtime-handlers.ts +4 -3
  182. package/src/hub/server.ts +506 -77
  183. package/src/hub/session-client.ts +43 -1
  184. package/src/hub/start-shared-server.ts +3 -0
  185. package/src/hub/ui-client.ts +4 -0
  186. package/src/index.ts +46 -1
  187. package/src/llms/cline-recommended-models.ts +167 -0
  188. package/src/llms/handler-factory.ts +56 -0
  189. package/src/llms/provider-defaults.ts +17 -1
  190. package/src/llms/provider-settings.ts +48 -1
  191. package/src/llms/runtime-registry.ts +1 -0
  192. package/src/runtime/agent-config-adapter.ts +636 -0
  193. package/src/runtime/agent-runtime-config-builder.ts +205 -0
  194. package/src/runtime/error-feedback.ts +142 -0
  195. package/src/runtime/history.ts +137 -0
  196. package/src/runtime/host.ts +22 -0
  197. package/src/runtime/loop-detection.ts +162 -0
  198. package/src/runtime/mistake-tracker.ts +221 -0
  199. package/src/runtime/runtime-builder.ts +61 -5
  200. package/src/runtime/runtime-event-adapter.ts +412 -0
  201. package/src/runtime/runtime-host.ts +45 -1
  202. package/src/runtime/session-runtime-orchestrator.ts +1253 -0
  203. package/src/runtime/session-runtime.ts +16 -2
  204. package/src/runtime/user-input-builder.ts +167 -0
  205. package/src/services/local-runtime-bootstrap.ts +128 -22
  206. package/src/services/plugin-tools.ts +1 -0
  207. package/src/services/providers/local-provider-registry.ts +273 -57
  208. package/src/services/providers/local-provider-service.ts +67 -7
  209. package/src/services/session-data.ts +16 -14
  210. package/src/services/session-telemetry.ts +6 -15
  211. package/src/services/storage/file-team-store.ts +1 -5
  212. package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
  213. package/src/services/storage/provider-settings-manager.ts +16 -1
  214. package/src/services/storage/sqlite-team-store.ts +1 -5
  215. package/src/session/conversation-store.ts +77 -0
  216. package/src/session/message-builder.ts +941 -0
  217. package/src/transports/hub.ts +458 -33
  218. package/src/transports/local.ts +296 -65
  219. package/src/transports/remote.ts +1 -0
  220. package/src/types/config.ts +9 -0
  221. package/src/types/events.ts +8 -6
  222. package/src/types/index.ts +3 -0
  223. package/src/types/provider-settings.ts +8 -1
  224. package/src/types/session.ts +5 -2
  225. package/src/types.ts +15 -1
  226. package/dist/cron/index.d.ts +0 -6
  227. package/dist/cron/index.d.ts.map +0 -1
  228. package/dist/services/telemetry/index.js +0 -28
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Bridges the flat `AgentRuntimeHooks` bag consumed by the
3
+ * new `AgentRuntime` and the legacy 15-stage `HookEngine` + `AgentHooks`
4
+ * + `AgentExtension[]` surface that `@clinebot/core` already speaks.
5
+ *
6
+ * @see PLAN.md §3.1 — replaces `packages/agents/src/runtime/lifecycle-orchestrator.ts`.
7
+ * @see PLAN.md §3.2.3 — public surface of `HookBridge`.
8
+ * @see PLAN.md §3.3.1 — OLD `HookStage` → NEW runtime primitive mapping.
9
+ * @see PLAN.md §3.3.3 — OLD `config.hooks.*` → NEW placement.
10
+ *
11
+ * -------------------------------------------------------------------------
12
+ * Responsibilities
13
+ * -------------------------------------------------------------------------
14
+ *
15
+ * 1. **Register** the legacy `AgentHooks` + `AgentExtension[]` on the
16
+ * `HookEngine` once at construction (via `registerLifecycleHandlers`
17
+ * — moved verbatim from the old agents package).
18
+ * 2. **Synthesize** an `AgentRuntimeHooks` bag via `toRuntimeHooks()`.
19
+ * Each runtime hook dispatches to the `HookEngine`'s
20
+ * corresponding stage (or stages, e.g. `beforeModel` fires both
21
+ * `turn_start` **and** `before_agent_start` per §3.3.1).
22
+ * 3. **Forward** `AgentRuntimeEvent`s via `onEvent` (part of the
23
+ * runtime-hooks bag) into the engine's `runtime_event` stage — and
24
+ * synthesize the stages that have no direct new-runtime primitive
25
+ * (`iteration_start`, `iteration_end`, `error`, per §3.3.2).
26
+ * 4. **Expose** imperative `dispatch(source, input)` for
27
+ * `SessionRuntime` to fire the *non*-runtime stages that live on
28
+ * the session boundary: `session_start`, `session_shutdown`,
29
+ * `input`, `run_end`, and `stop_error` (the bridge synthesizes
30
+ * these from session-level events, not from the new runtime's hooks).
31
+ * 5. **Apply** hook-returned `control.context` by invoking
32
+ * `onHookContext(source, context)` so the caller (facade or
33
+ * session-runtime) can append `<hook_context source="…">…` user
34
+ * messages to the conversation (legacy behavior at
35
+ * `packages/agents/src/agent.ts:1402-1421`).
36
+ *
37
+ * This is the **non-Step-8a** half of the core runtime port: it wires
38
+ * the existing hook ecosystem to the new runtime without changing
39
+ * either side's semantics.
40
+ */
41
+
42
+ import type {
43
+ AgentExtension,
44
+ AgentHookControl,
45
+ AgentHooks,
46
+ AgentRuntimeEvent,
47
+ AgentRuntimeHooks,
48
+ AgentStopControl,
49
+ HookDispatchInput,
50
+ HookEngine,
51
+ } from "@clinebot/shared";
52
+ import { registerLifecycleHandlers } from "./hook-registry";
53
+
54
+ export interface HookBridgeOptions {
55
+ readonly agentId: string;
56
+ readonly agentRole?: string;
57
+ readonly conversationId: string;
58
+ readonly parentAgentId: string | null;
59
+ readonly hookEngine: HookEngine;
60
+ /** Legacy flat `AgentHooks` bag — registered on the engine. */
61
+ readonly hooks?: AgentHooks;
62
+ /** Legacy `AgentExtension[]` — their `on*` callbacks are registered. */
63
+ readonly extensions?: readonly AgentExtension[];
64
+ /** Supplies the currently-active run id (stable for the duration of one run). */
65
+ readonly getRunId: () => string;
66
+ /** Called when any dispatched hook returns `control.context`. */
67
+ readonly onHookContext?: (source: string, context: string) => void;
68
+ /** Called when a dispatch throws. */
69
+ readonly onDispatchError?: (error: unknown) => void;
70
+ }
71
+
72
+ type ImperativeDispatchInput = Pick<
73
+ HookDispatchInput,
74
+ "stage" | "payload" | "iteration" | "parentEventId"
75
+ >;
76
+
77
+ export class HookBridge {
78
+ private readonly options: HookBridgeOptions;
79
+
80
+ constructor(options: HookBridgeOptions) {
81
+ this.options = options;
82
+ // Register legacy hooks + extensions on the shared engine.
83
+ // Safe to call even when both are empty: the registry is a
84
+ // no-op for unset callbacks and disabled extensions.
85
+ registerLifecycleHandlers(options.hookEngine, {
86
+ hooks: options.hooks,
87
+ extensions: options.extensions ? [...options.extensions] : undefined,
88
+ });
89
+ }
90
+
91
+ // -------------------------------------------------------------------
92
+ // Imperative dispatch (used by SessionRuntime for session-scoped stages)
93
+ // -------------------------------------------------------------------
94
+
95
+ /**
96
+ * Dispatch a lifecycle stage imperatively. `SessionRuntime` calls
97
+ * this at `session_start`, `session_shutdown`, `input`, `run_end`, and
98
+ * `stop_error` — stages that do not map cleanly onto the
99
+ * `AgentRuntime` hooks (§3.3.1).
100
+ *
101
+ * Returns the merged `AgentHookControl` from the dispatched
102
+ * handlers, or `undefined` if nothing ran. Context propagation
103
+ * (`control.context` → `onHookContext(source, …)`) is applied
104
+ * inline.
105
+ */
106
+ async dispatch(
107
+ source: string,
108
+ input: ImperativeDispatchInput,
109
+ ): Promise<AgentHookControl | undefined> {
110
+ try {
111
+ const result = await this.options.hookEngine.dispatch({
112
+ ...input,
113
+ runId: this.options.getRunId(),
114
+ agentId: this.options.agentId,
115
+ conversationId: this.options.conversationId,
116
+ parentAgentId: this.options.parentAgentId,
117
+ });
118
+ if (result.control?.context) {
119
+ this.options.onHookContext?.(source, result.control.context);
120
+ }
121
+ return result.control as AgentHookControl | undefined;
122
+ } catch (error) {
123
+ this.options.onDispatchError?.(error);
124
+ return undefined;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Fire the `runtime_event` stage for a single `AgentRuntimeEvent`.
130
+ * Non-blocking: errors route through `onDispatchError`. Exposed
131
+ * for callers who already hold the event (e.g. the legacy facade's
132
+ * subscribeEvents adapter).
133
+ */
134
+ dispatchRuntimeEvent(event: AgentRuntimeEvent): void {
135
+ void this.dispatch("hook.runtime_event", {
136
+ stage: "runtime_event",
137
+ payload: {
138
+ agentId: this.options.agentId,
139
+ conversationId: this.options.conversationId,
140
+ parentAgentId: this.options.parentAgentId,
141
+ event,
142
+ },
143
+ });
144
+ }
145
+
146
+ async shutdown(timeoutMs?: number): Promise<void> {
147
+ await this.options.hookEngine.shutdown(timeoutMs);
148
+ }
149
+
150
+ // -------------------------------------------------------------------
151
+ // toRuntimeHooks() — the synthesized AgentRuntimeHooks bag
152
+ // -------------------------------------------------------------------
153
+
154
+ /**
155
+ * Build the `AgentRuntimeHooks` bag consumed by
156
+ * `AgentRuntime`. Each synthesized callback:
157
+ *
158
+ * - invokes `hookEngine.dispatch(stage, payload)` for the
159
+ * corresponding legacy stage(s) (§3.3.1);
160
+ * - merges the returned `AgentHookControl` into the runtime-hook's
161
+ * return shape (`AgentStopControl` / `AgentBeforeModelResult` /
162
+ * `AgentBeforeToolResult` / `AgentAfterToolResult`) per §3.3.3;
163
+ * - applies `control.context` via `onHookContext(source, …)` so
164
+ * the caller can append `<hook_context>` user messages.
165
+ *
166
+ * Legacy stages that have no direct new-runtime primitive
167
+ * (`iteration_start`, `iteration_end`, `error`) are synthesized
168
+ * inside `onEvent` from the corresponding `AgentRuntimeEvent`
169
+ * variants (§3.3.2).
170
+ */
171
+ toRuntimeHooks(): AgentRuntimeHooks {
172
+ return {
173
+ beforeRun: async (ctx) => {
174
+ const control = await this.dispatch("hook.run_start", {
175
+ stage: "run_start",
176
+ iteration: ctx.snapshot.iteration,
177
+ payload: {
178
+ agentId: this.options.agentId,
179
+ agentRole: this.options.agentRole,
180
+ conversationId: this.options.conversationId,
181
+ parentAgentId: this.options.parentAgentId,
182
+ runId: this.options.getRunId(),
183
+ snapshot: ctx.snapshot,
184
+ },
185
+ });
186
+ return controlToStop(control);
187
+ },
188
+
189
+ beforeModel: async (ctx) => {
190
+ // §3.3.1: `beforeModel` fires both `turn_start` AND
191
+ // `before_agent_start` in that exact order (legacy
192
+ // `agent.ts:599` → `:618` sequence, §3.3.3 note).
193
+ const turnStartControl = await this.dispatch("hook.turn_start", {
194
+ stage: "turn_start",
195
+ iteration: ctx.snapshot.iteration,
196
+ payload: {
197
+ agentId: this.options.agentId,
198
+ agentRole: this.options.agentRole,
199
+ conversationId: this.options.conversationId,
200
+ parentAgentId: this.options.parentAgentId,
201
+ runId: this.options.getRunId(),
202
+ snapshot: ctx.snapshot,
203
+ request: ctx.request,
204
+ },
205
+ });
206
+ const beforeAgentControl = await this.dispatch(
207
+ "hook.before_agent_start",
208
+ {
209
+ stage: "before_agent_start",
210
+ iteration: ctx.snapshot.iteration,
211
+ payload: {
212
+ agentId: this.options.agentId,
213
+ agentRole: this.options.agentRole,
214
+ conversationId: this.options.conversationId,
215
+ parentAgentId: this.options.parentAgentId,
216
+ runId: this.options.getRunId(),
217
+ snapshot: ctx.snapshot,
218
+ request: ctx.request,
219
+ },
220
+ },
221
+ );
222
+ return mergeBeforeModelControl(turnStartControl, beforeAgentControl);
223
+ },
224
+
225
+ afterModel: async (ctx) => {
226
+ const control = await this.dispatch("hook.turn_end", {
227
+ stage: "turn_end",
228
+ iteration: ctx.snapshot.iteration,
229
+ payload: {
230
+ agentId: this.options.agentId,
231
+ agentRole: this.options.agentRole,
232
+ conversationId: this.options.conversationId,
233
+ parentAgentId: this.options.parentAgentId,
234
+ runId: this.options.getRunId(),
235
+ snapshot: ctx.snapshot,
236
+ assistantMessage: ctx.assistantMessage,
237
+ finishReason: ctx.finishReason,
238
+ },
239
+ });
240
+ return controlToStop(control);
241
+ },
242
+
243
+ beforeTool: async (ctx) => {
244
+ const control = await this.dispatch("hook.tool_call_before", {
245
+ stage: "tool_call_before",
246
+ iteration: ctx.snapshot.iteration,
247
+ payload: {
248
+ agentId: this.options.agentId,
249
+ agentRole: this.options.agentRole,
250
+ conversationId: this.options.conversationId,
251
+ parentAgentId: this.options.parentAgentId,
252
+ runId: this.options.getRunId(),
253
+ snapshot: ctx.snapshot,
254
+ tool: ctx.tool,
255
+ toolCall: ctx.toolCall,
256
+ input: ctx.input,
257
+ },
258
+ });
259
+ return controlToBeforeTool(control);
260
+ },
261
+
262
+ afterTool: async (ctx) => {
263
+ const control = await this.dispatch("hook.tool_call_after", {
264
+ stage: "tool_call_after",
265
+ iteration: ctx.snapshot.iteration,
266
+ payload: {
267
+ agentId: this.options.agentId,
268
+ agentRole: this.options.agentRole,
269
+ conversationId: this.options.conversationId,
270
+ parentAgentId: this.options.parentAgentId,
271
+ runId: this.options.getRunId(),
272
+ snapshot: ctx.snapshot,
273
+ tool: ctx.tool,
274
+ toolCall: ctx.toolCall,
275
+ input: ctx.input,
276
+ result: ctx.result,
277
+ },
278
+ });
279
+ return controlToAfterTool(control);
280
+ },
281
+
282
+ onEvent: (event) => this.onRuntimeEvent(event),
283
+ };
284
+ }
285
+
286
+ // -------------------------------------------------------------------
287
+ // onRuntimeEvent — synthesizes legacy stages from runtime events
288
+ // -------------------------------------------------------------------
289
+
290
+ /**
291
+ * Handle a single `AgentRuntimeEvent`. Always forwards it to the
292
+ * engine's `runtime_event` stage (§3.3.1 last row). Additionally
293
+ * synthesizes the three legacy stages that have no direct
294
+ * new-runtime primitive (§3.3.1):
295
+ *
296
+ * turn-started → also fires `iteration_start`
297
+ * turn-finished → also fires `iteration_end`
298
+ * run-failed → also fires `error`
299
+ *
300
+ * This method is public so `SessionRuntime` can call it when it
301
+ * holds the event directly; the runtime-hooks bag's `onEvent` also
302
+ * routes through here.
303
+ */
304
+ async onRuntimeEvent(event: AgentRuntimeEvent): Promise<void> {
305
+ await this.dispatch("hook.runtime_event", {
306
+ stage: "runtime_event",
307
+ payload: {
308
+ agentId: this.options.agentId,
309
+ conversationId: this.options.conversationId,
310
+ parentAgentId: this.options.parentAgentId,
311
+ event,
312
+ },
313
+ });
314
+
315
+ switch (event.type) {
316
+ case "turn-started":
317
+ await this.dispatch("hook.iteration_start", {
318
+ stage: "iteration_start",
319
+ iteration: event.iteration,
320
+ payload: {
321
+ agentId: this.options.agentId,
322
+ agentRole: this.options.agentRole,
323
+ conversationId: this.options.conversationId,
324
+ parentAgentId: this.options.parentAgentId,
325
+ runId: this.options.getRunId(),
326
+ iteration: event.iteration,
327
+ snapshot: event.snapshot,
328
+ },
329
+ });
330
+ return;
331
+ case "turn-finished":
332
+ await this.dispatch("hook.iteration_end", {
333
+ stage: "iteration_end",
334
+ iteration: event.iteration,
335
+ payload: {
336
+ agentId: this.options.agentId,
337
+ agentRole: this.options.agentRole,
338
+ conversationId: this.options.conversationId,
339
+ parentAgentId: this.options.parentAgentId,
340
+ runId: this.options.getRunId(),
341
+ iteration: event.iteration,
342
+ hadToolCalls: event.toolCallCount > 0,
343
+ toolCallCount: event.toolCallCount,
344
+ snapshot: event.snapshot,
345
+ },
346
+ });
347
+ return;
348
+ case "run-failed":
349
+ await this.dispatch("hook.error", {
350
+ stage: "error",
351
+ iteration: event.snapshot.iteration,
352
+ payload: {
353
+ agentId: this.options.agentId,
354
+ agentRole: this.options.agentRole,
355
+ conversationId: this.options.conversationId,
356
+ parentAgentId: this.options.parentAgentId,
357
+ runId: this.options.getRunId(),
358
+ iteration: event.snapshot.iteration,
359
+ error: event.error,
360
+ recoverable: false,
361
+ },
362
+ });
363
+ return;
364
+ default:
365
+ return;
366
+ }
367
+ }
368
+ }
369
+
370
+ // =============================================================================
371
+ // Private control-mergers
372
+ // =============================================================================
373
+
374
+ /**
375
+ * Convert an `AgentHookControl` into the runtime's `AgentStopControl`
376
+ * shape. `control.cancel === true` is interpreted as
377
+ * `{ stop: true }` per §3.3.3 (legacy `cancel` flag translates to a
378
+ * stop-control on `beforeRun` / `afterModel`).
379
+ */
380
+ function controlToStop(
381
+ control: AgentHookControl | undefined,
382
+ ): AgentStopControl | undefined {
383
+ if (!control) {
384
+ return undefined;
385
+ }
386
+ if (control.cancel === true) {
387
+ return { stop: true };
388
+ }
389
+ return undefined;
390
+ }
391
+
392
+ /**
393
+ * Merge the two `AgentHookControl` returns from `turn_start` and
394
+ * `before_agent_start` into a single `AgentBeforeModelResult`
395
+ * (§3.3.3). The `before_agent_start` control wins on per-field
396
+ * conflicts — matches legacy `agent.ts:618-649` where the later hook
397
+ * can override earlier `systemPrompt` / `replaceMessages` /
398
+ * `appendMessages`.
399
+ */
400
+ function mergeBeforeModelControl(
401
+ turnStart: AgentHookControl | undefined,
402
+ beforeAgent: AgentHookControl | undefined,
403
+ ): { stop?: boolean; options?: Record<string, unknown> } | undefined {
404
+ if (!turnStart && !beforeAgent) {
405
+ return undefined;
406
+ }
407
+ const cancel = turnStart?.cancel === true || beforeAgent?.cancel === true;
408
+ const systemPrompt = beforeAgent?.systemPrompt ?? turnStart?.systemPrompt;
409
+ // Legacy `replaceMessages` / `appendMessages` carry `Message[]`
410
+ // (the old LlmsProviders shape), which is type-incompatible with
411
+ // the new runtime's `AgentMessage[]`. Rather than attempt a lossy
412
+ // cross-shape conversion here (the runtime has no direct message-
413
+ // override anyway — `AgentBeforeModelResult.messages` exists but
414
+ // carries `AgentMessage[]`), we forward both as an opaque
415
+ // `options.*` hint. The facade / SessionRuntime materializes them
416
+ // into `conversation.replaceMessages()` / `appendMessages()` on
417
+ // the next turn, preserving the legacy deferred-apply semantics.
418
+ const replaceMessages =
419
+ beforeAgent?.replaceMessages ?? turnStart?.replaceMessages;
420
+ const appendMessages =
421
+ beforeAgent?.appendMessages ?? turnStart?.appendMessages;
422
+ const out: { stop?: boolean; options?: Record<string, unknown> } = {};
423
+ if (cancel) {
424
+ out.stop = true;
425
+ }
426
+ const options: Record<string, unknown> = {};
427
+ if (replaceMessages) {
428
+ options.replaceMessages = [...replaceMessages];
429
+ }
430
+ if (appendMessages) {
431
+ options.appendMessages = [...appendMessages];
432
+ }
433
+ if (systemPrompt !== undefined) {
434
+ options.systemPrompt = systemPrompt;
435
+ }
436
+ if (Object.keys(options).length > 0) {
437
+ out.options = options;
438
+ }
439
+ return Object.keys(out).length === 0 ? undefined : out;
440
+ }
441
+
442
+ /**
443
+ * Convert an `AgentHookControl` into the runtime's
444
+ * `AgentBeforeToolResult`. Maps:
445
+ *
446
+ * control.cancel → { stop: true }
447
+ * control.overrideInput → { input: … }
448
+ * control.review (legacy) → no direct equivalent — handled by the
449
+ * core ToolApprovalController, not passed
450
+ * through the runtime hook.
451
+ */
452
+ function controlToBeforeTool(
453
+ control: AgentHookControl | undefined,
454
+ ):
455
+ | { skip?: boolean; stop?: boolean; reason?: string; input?: unknown }
456
+ | undefined {
457
+ if (!control) {
458
+ return undefined;
459
+ }
460
+ const out: {
461
+ skip?: boolean;
462
+ stop?: boolean;
463
+ reason?: string;
464
+ input?: unknown;
465
+ } = {};
466
+ if (control.cancel === true) {
467
+ out.stop = true;
468
+ }
469
+ if (control.overrideInput !== undefined) {
470
+ out.input = control.overrideInput;
471
+ }
472
+ return Object.keys(out).length === 0 ? undefined : out;
473
+ }
474
+
475
+ /**
476
+ * Convert an `AgentHookControl` into the runtime's
477
+ * `AgentAfterToolResult`.
478
+ */
479
+ function controlToAfterTool(
480
+ control: AgentHookControl | undefined,
481
+ ): { stop?: boolean; reason?: string } | undefined {
482
+ if (!control) {
483
+ return undefined;
484
+ }
485
+ if (control.cancel === true) {
486
+ return { stop: true };
487
+ }
488
+ return undefined;
489
+ }
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { appendFileSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import type { AgentHooks } from "@clinebot/agents";
4
+ import type { AgentHooks } from "@clinebot/shared";
5
5
  import {
6
6
  augmentNodeCommandForDebug,
7
7
  type BasicLogger,
@@ -272,6 +272,34 @@ async function runHookCommand(
272
272
  if (options.command.length === 0) {
273
273
  throw new Error("runHookCommand requires non-empty command");
274
274
  }
275
+ try {
276
+ return await runHookCommandOnce(payload, options);
277
+ } catch (error) {
278
+ const fallbackCommand = getWindowsPythonFallbackCommand(
279
+ options.command,
280
+ process.platform,
281
+ error,
282
+ );
283
+ if (!fallbackCommand) {
284
+ throw error;
285
+ }
286
+ return await runHookCommandOnce(payload, {
287
+ ...options,
288
+ command: fallbackCommand,
289
+ });
290
+ }
291
+ }
292
+
293
+ async function runHookCommandOnce(
294
+ payload: HookEventPayload,
295
+ options: {
296
+ command: string[];
297
+ cwd: string;
298
+ env?: NodeJS.ProcessEnv;
299
+ detached: boolean;
300
+ timeoutMs?: number;
301
+ },
302
+ ): Promise<HookCommandResult | undefined> {
275
303
  const command = augmentNodeCommandForDebug(options.command, {
276
304
  env: options.env,
277
305
  debugRole: "hook",
@@ -357,6 +385,29 @@ function parseShebangCommand(path: string): string[] | undefined {
357
385
  }
358
386
  }
359
387
 
388
+ function isMissingCommandError(error: unknown): boolean {
389
+ return !!(
390
+ error &&
391
+ typeof error === "object" &&
392
+ "code" in error &&
393
+ (error as { code?: unknown }).code === "ENOENT"
394
+ );
395
+ }
396
+
397
+ export function getWindowsPythonFallbackCommand(
398
+ command: string[],
399
+ platform = process.platform,
400
+ error?: unknown,
401
+ ): string[] | undefined {
402
+ if (platform !== "win32" || !isMissingCommandError(error)) {
403
+ return undefined;
404
+ }
405
+ if (command[0] !== "py" || command[1] !== "-3") {
406
+ return undefined;
407
+ }
408
+ return ["python", ...command.slice(2)];
409
+ }
410
+
360
411
  function normalizeHookInterpreter(tokens: string[]): string[] | undefined {
361
412
  if (tokens.length === 0) {
362
413
  return undefined;
@@ -374,7 +425,9 @@ function normalizeHookInterpreter(tokens: string[]): string[] | undefined {
374
425
  }
375
426
 
376
427
  if (commandName === "python3" || commandName === "python") {
377
- return [process.platform === "win32" ? "python" : commandName, ...rest];
428
+ return process.platform === "win32"
429
+ ? ["py", "-3", ...rest]
430
+ : [commandName, ...rest];
378
431
  }
379
432
 
380
433
  return tokens;
@@ -410,7 +463,9 @@ function inferHookCommand(path: string): string[] {
410
463
  return ["bun", "run", path];
411
464
  }
412
465
  if (lowered.endsWith(".py")) {
413
- return [process.platform === "win32" ? "python" : "python3", path];
466
+ return process.platform === "win32"
467
+ ? ["py", "-3", path]
468
+ : ["python3", path];
414
469
  }
415
470
  if (lowered.endsWith(".ps1")) {
416
471
  return [