@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,205 @@
1
+ /**
2
+ * Build an `AgentRuntimeConfig` from an `AgentConfig` plus session-owned
3
+ * supporting objects (model handler, tools, hooks, plugins, telemetry).
4
+ *
5
+ * @see PLAN.md §3.1 — new file introduced alongside the core runtime port.
6
+ * @see PLAN.md §3.2.1 — field-by-field mapping.
7
+ *
8
+ * Implemented in Step 8c. The function is intentionally **pure** — it
9
+ * does not create handlers or tools itself; it receives them already
10
+ * adapted (via `agent-config-adapter.ts`) from the caller
11
+ * (`SessionRuntime`) and wires them into an `AgentRuntimeConfig`.
12
+ *
13
+ * Fields that do **not** round-trip into `AgentRuntimeConfig`
14
+ * (e.g. `execution.maxConsecutiveMistakes`,
15
+ * `execution.loopDetection`, `hookPolicies`) are
16
+ * consumed by `SessionRuntime` / `MistakeTracker` /
17
+ * `LoopDetectionTracker` — not passed through here. See §3.2.1's
18
+ * "Fields with no corresponding AgentRuntimeConfig slot" note.
19
+ */
20
+
21
+ import type {
22
+ AgentConfig,
23
+ AgentMessage,
24
+ AgentModel,
25
+ AgentRuntimeConfig,
26
+ AgentRuntimePlugin,
27
+ AgentTelemetry,
28
+ AgentTool,
29
+ BasicLogger,
30
+ } from "@clinebot/shared";
31
+ import type { HookBridge } from "../hooks/hook-bridge";
32
+
33
+ /**
34
+ * Inputs required to assemble an `AgentRuntimeConfig`. Distinct from
35
+ * `AgentConfig` because some of these (the model adapter, the hook
36
+ * bridge's runtime-hooks bag, a resolved plugin list) can only be
37
+ * produced inside `SessionRuntime`.
38
+ */
39
+ export interface CreateAgentRuntimeConfigInput {
40
+ readonly agentConfig: AgentConfig;
41
+ /**
42
+ * Core/hub runtime session identifier used for host lifecycle operations,
43
+ * event routing, persistence, and approval delivery.
44
+ */
45
+ readonly sessionId?: string;
46
+ readonly agentId: string;
47
+ /**
48
+ * Agent conversation/transcript identifier used by tools, hooks, telemetry,
49
+ * and model history correlation.
50
+ */
51
+ readonly conversationId?: string;
52
+ readonly parentAgentId?: string;
53
+ /** The role label for teammates (`AgentConfig.role` in sub-agent configs). */
54
+ readonly agentRole?: string;
55
+ /** Pre-built model adapter (produced by `apiHandlerToAgentModel`). */
56
+ readonly model: AgentModel;
57
+ readonly logger?: BasicLogger;
58
+ readonly telemetry?: AgentTelemetry;
59
+ /** Pre-built tool array (builtins + plugin-contributed + session extras). */
60
+ readonly tools?: readonly AgentTool<unknown, unknown>[];
61
+ /** Pre-resolved plugin list from the plugin loader. */
62
+ readonly plugins?: readonly AgentRuntimePlugin[];
63
+ /**
64
+ * Optional hook bridge. When provided, `AgentRuntimeConfig.hooks`
65
+ * is populated from `hookBridge.toRuntimeHooks()`. Omit for
66
+ * runtimes that do not need hook dispatch (e.g. unit tests).
67
+ */
68
+ readonly hookBridge?: HookBridge;
69
+ /** Seed messages (usually `session.conversation.getMessages()`). */
70
+ readonly initialMessages?: readonly AgentMessage[];
71
+ /**
72
+ * Override for `AgentRuntimeConfig.systemPrompt` — useful when
73
+ * the caller has composed additional guidance (e.g. via
74
+ * `LocalRuntimeHost.composeSystemPrompt`). Defaults to
75
+ * `agentConfig.systemPrompt`.
76
+ */
77
+ readonly systemPrompt?: string;
78
+ }
79
+
80
+ /**
81
+ * Produce an `AgentRuntimeConfig` per PLAN.md §3.2.1 mapping.
82
+ */
83
+ export function createAgentRuntimeConfig(
84
+ input: CreateAgentRuntimeConfigInput,
85
+ ): AgentRuntimeConfig {
86
+ const { agentConfig } = input;
87
+
88
+ const modelOptions = buildModelOptions(agentConfig);
89
+ const messageModelInfo = buildMessageModelInfo(agentConfig);
90
+ const hooks = input.hookBridge?.toRuntimeHooks();
91
+ const toolExecution = resolveToolExecution(agentConfig.maxParallelToolCalls);
92
+
93
+ const config: AgentRuntimeConfig = {
94
+ sessionId: input.sessionId ?? agentConfig.sessionId,
95
+ agentId: input.agentId,
96
+ conversationId: input.conversationId,
97
+ agentRole: input.agentRole,
98
+ systemPrompt: input.systemPrompt ?? agentConfig.systemPrompt,
99
+ messageModelInfo,
100
+ model: input.model,
101
+ modelOptions,
102
+ tools: input.tools,
103
+ hooks,
104
+ plugins: input.plugins,
105
+ logger: input.logger ?? agentConfig.logger,
106
+ telemetry: input.telemetry ?? mapTelemetry(agentConfig.telemetry),
107
+ initialMessages: input.initialMessages,
108
+ maxIterations: agentConfig.maxIterations,
109
+ toolExecution,
110
+ toolPolicies: agentConfig.toolPolicies,
111
+ requestToolApproval: agentConfig.requestToolApproval,
112
+ };
113
+
114
+ return config;
115
+ }
116
+
117
+ // =============================================================================
118
+ // Helpers
119
+ // =============================================================================
120
+
121
+ /**
122
+ * Collect the provider-/reasoning-related fields from `AgentConfig`
123
+ * into `AgentRuntimeConfig.modelOptions`. Kept undefined when every
124
+ * field is undefined so the runtime does not receive an empty object.
125
+ */
126
+ export function buildModelOptions(
127
+ config: AgentConfig,
128
+ ): Record<string, unknown> | undefined {
129
+ const options: Record<string, unknown> = {};
130
+ if (config.thinking !== undefined) {
131
+ options.thinking = config.thinking;
132
+ }
133
+ if (config.reasoningEffort !== undefined) {
134
+ options.reasoningEffort = config.reasoningEffort;
135
+ }
136
+ if (config.thinkingBudgetTokens !== undefined) {
137
+ options.thinkingBudgetTokens = config.thinkingBudgetTokens;
138
+ }
139
+ if (config.maxTokensPerTurn !== undefined) {
140
+ options.maxTokensPerTurn = config.maxTokensPerTurn;
141
+ }
142
+ if (config.apiTimeoutMs !== undefined) {
143
+ options.apiTimeoutMs = config.apiTimeoutMs;
144
+ }
145
+ return Object.keys(options).length > 0 ? options : undefined;
146
+ }
147
+
148
+ /**
149
+ * Compose `messageModelInfo` from the provider-related fields per
150
+ * §3.2.1: `{ id: modelId, provider: providerId, family:
151
+ * providerConfig?.family }`.
152
+ */
153
+ export function buildMessageModelInfo(
154
+ config: AgentConfig,
155
+ ): AgentMessage["modelInfo"] {
156
+ const family = (config.providerConfig as { family?: string } | undefined)
157
+ ?.family;
158
+ return {
159
+ id: config.modelId,
160
+ provider: config.providerId,
161
+ family,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * `"parallel"` when `maxParallelToolCalls ≥ 2`, `"sequential"` when
167
+ * `1`, `undefined` when the caller did not specify.
168
+ */
169
+ export function resolveToolExecution(
170
+ maxParallelToolCalls: number | undefined,
171
+ ): "sequential" | "parallel" | undefined {
172
+ if (maxParallelToolCalls === undefined) {
173
+ return undefined;
174
+ }
175
+ return maxParallelToolCalls >= 2 ? "parallel" : "sequential";
176
+ }
177
+
178
+ /**
179
+ * Adapt the full `ITelemetryService` to the minimal `AgentTelemetry`
180
+ * shape. The runtime only calls `capture(event, properties)`; the
181
+ * remaining methods are host concerns and stay on the
182
+ * `ITelemetryService` instance owned by `SessionRuntime`.
183
+ */
184
+ export function mapTelemetry(
185
+ telemetry: AgentConfig["telemetry"],
186
+ ): AgentTelemetry | undefined {
187
+ if (!telemetry) {
188
+ return undefined;
189
+ }
190
+ return {
191
+ capture: (event, properties) => {
192
+ // The runtime's `AgentTelemetry.capture` signature uses
193
+ // `Record<string, unknown>`, which is wider than
194
+ // `TelemetryProperties`. We cast at the boundary — any
195
+ // non-telemetry-compatible values are ignored by the
196
+ // underlying service.
197
+ telemetry.capture({
198
+ event,
199
+ properties: properties as
200
+ | Parameters<typeof telemetry.capture>[0]["properties"]
201
+ | undefined,
202
+ });
203
+ },
204
+ };
205
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Pure error-classification and tool-call-feedback helpers.
3
+ *
4
+ * @see PLAN.md §3.1 — moved from `packages/agents/src/api/error-handling.ts` lines 20–145.
5
+ *
6
+ * Consumed by `SessionRuntime` (and `MistakeTracker`) when composing
7
+ * follow-up turns after API failures or invalid tool calls.
8
+ */
9
+
10
+ import type * as LlmsProviders from "@clinebot/llms";
11
+ import type { ToolCallRecord } from "@clinebot/shared";
12
+
13
+ // =============================================================================
14
+ // API Error Classification
15
+ // =============================================================================
16
+
17
+ const NON_RECOVERABLE_STATUS_CODES = [
18
+ 400, 401, 402, 403, 404, 405, 406, 409, 410, 429,
19
+ ];
20
+
21
+ const NON_RECOVERABLE_PHRASES = [
22
+ "not found",
23
+ "unsupported for",
24
+ "missing api key",
25
+ "invalid api key",
26
+ "authentication",
27
+ "unauthorized",
28
+ "forbidden",
29
+ "overloaded",
30
+ "insufficient balance",
31
+ ];
32
+
33
+ export function isNonRecoverableApiError(error: Error): boolean {
34
+ const message = error.message.toLowerCase();
35
+
36
+ if (
37
+ NON_RECOVERABLE_STATUS_CODES.some((code) =>
38
+ new RegExp(`(?:\\b|\\"code\\"\\s*:\\s*)${code}(?:\\b|\\s)`).test(message),
39
+ )
40
+ ) {
41
+ return true;
42
+ }
43
+
44
+ if (NON_RECOVERABLE_PHRASES.some((s) => message.includes(s))) {
45
+ return true;
46
+ }
47
+
48
+ return false;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Invalid Tool Call Feedback
53
+ // =============================================================================
54
+
55
+ export interface InvalidToolCall {
56
+ id: string;
57
+ name?: string;
58
+ input?: unknown;
59
+ reason: "missing_name" | "missing_arguments" | "invalid_arguments";
60
+ }
61
+
62
+ function extractParseError(input: unknown): string | undefined {
63
+ if (
64
+ input &&
65
+ typeof input === "object" &&
66
+ !Array.isArray(input) &&
67
+ typeof (input as { parse_error?: unknown }).parse_error === "string"
68
+ ) {
69
+ return (input as { parse_error: string }).parse_error;
70
+ }
71
+ return undefined;
72
+ }
73
+
74
+ export function buildInvalidToolCallFeedback(
75
+ invalidToolCalls: readonly InvalidToolCall[],
76
+ ): string {
77
+ const details = invalidToolCalls
78
+ .map((call) => {
79
+ const name = call.name?.trim() || "(unknown tool)";
80
+ const parseError = extractParseError(call.input);
81
+ const reason =
82
+ call.reason === "missing_name"
83
+ ? "missing tool name"
84
+ : call.reason === "missing_arguments"
85
+ ? "missing arguments"
86
+ : (parseError ?? "arguments could not be parsed as JSON");
87
+ return `${name} [${call.id}]: ${reason}`;
88
+ })
89
+ .join("; ");
90
+ return `One or more tool calls were invalid or missing required parameters (${details}). Retry with valid tool names and arguments.`;
91
+ }
92
+
93
+ export function buildInvalidToolResultMessage(
94
+ invalidToolCalls: readonly InvalidToolCall[],
95
+ ): LlmsProviders.Message {
96
+ return {
97
+ role: "user",
98
+ content: invalidToolCalls.map((call) => ({
99
+ type: "tool_result" as const,
100
+ tool_use_id: call.id,
101
+ content: JSON.stringify({
102
+ toolName: call.name?.trim() || "(unknown tool)",
103
+ query: call.input ?? {},
104
+ result: "",
105
+ error:
106
+ call.reason === "missing_name"
107
+ ? "Tool call was missing a tool name"
108
+ : call.reason === "missing_arguments"
109
+ ? "Tool call was missing required arguments"
110
+ : (extractParseError(call.input) ??
111
+ "Tool call arguments could not be parsed as JSON"),
112
+ success: false,
113
+ }),
114
+ is_error: true,
115
+ })),
116
+ };
117
+ }
118
+
119
+ // =============================================================================
120
+ // Failed Tool Call Feedback
121
+ // =============================================================================
122
+
123
+ export function buildFailedToolCallFeedback(
124
+ toolResults: readonly ToolCallRecord[],
125
+ ): string {
126
+ const failed = toolResults.filter((record) => !!record.error);
127
+ if (failed.length === 0) {
128
+ return "";
129
+ }
130
+ const details = failed
131
+ .slice(0, 3)
132
+ .map((record) => {
133
+ const message = String(record.error ?? "unknown tool error")
134
+ .replace(/\s+/g, " ")
135
+ .trim();
136
+ return `${record.name}: ${message}`;
137
+ })
138
+ .join("; ");
139
+ return failed.length > 3
140
+ ? `${details}; +${failed.length - 3} more failed tool call(s)`
141
+ : details;
142
+ }
@@ -1,5 +1,10 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import type * as LlmsProviders from "@clinebot/llms";
2
4
  import { formatDisplayUserInput, normalizeUserInput } from "@clinebot/shared";
5
+ import { resolveSessionDataDir } from "@clinebot/shared/storage";
6
+ import type { SessionManifest } from "../session/session-manifest";
7
+ import { SessionManifestSchema } from "../session/session-manifest";
3
8
  import type {
4
9
  SessionHistoryMetadata,
5
10
  SessionHistoryRecord,
@@ -7,6 +12,12 @@ import type {
7
12
  } from "../types/sessions";
8
13
  import type { RuntimeHost } from "./runtime-host";
9
14
 
15
+ export interface SessionHistoryListOptions {
16
+ limit?: number;
17
+ includeManifestFallback?: boolean;
18
+ hydrate?: boolean;
19
+ }
20
+
10
21
  type StoredSessionMessage = LlmsProviders.Message & {
11
22
  metrics?: {
12
23
  cost?: number;
@@ -51,6 +62,103 @@ function asHistoryMetadata(value: unknown): SessionHistoryMetadata | undefined {
51
62
  return { ...(value as Record<string, unknown>) };
52
63
  }
53
64
 
65
+ function normalizeHistoryLimit(limit: number | undefined): number {
66
+ const value = limit ?? 200;
67
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 200;
68
+ }
69
+
70
+ function extractSessionRecencyToken(sessionId: string): number {
71
+ const matches = sessionId.match(/\d{13,}/g);
72
+ if (!matches || matches.length === 0) {
73
+ return 0;
74
+ }
75
+ let best = 0;
76
+ for (const match of matches) {
77
+ const value = Number.parseInt(match, 10);
78
+ if (Number.isFinite(value) && value > best) {
79
+ best = value;
80
+ }
81
+ }
82
+ return best;
83
+ }
84
+
85
+ function manifestToSessionRecord(manifest: SessionManifest): SessionRecord {
86
+ return {
87
+ sessionId: manifest.session_id,
88
+ source: manifest.source,
89
+ pid: manifest.pid,
90
+ startedAt: manifest.started_at,
91
+ endedAt: manifest.ended_at ?? null,
92
+ exitCode: manifest.exit_code ?? null,
93
+ status: manifest.status,
94
+ interactive: manifest.interactive,
95
+ provider: manifest.provider,
96
+ model: manifest.model,
97
+ cwd: manifest.cwd,
98
+ workspaceRoot: manifest.workspace_root,
99
+ teamName: manifest.team_name,
100
+ enableTools: manifest.enable_tools,
101
+ enableSpawn: manifest.enable_spawn,
102
+ enableTeams: manifest.enable_teams,
103
+ isSubagent: false,
104
+ prompt: manifest.prompt,
105
+ metadata: manifest.metadata,
106
+ messagesPath: manifest.messages_path,
107
+ updatedAt: manifest.ended_at ?? manifest.started_at,
108
+ };
109
+ }
110
+
111
+ async function listManifestHistoryRows(
112
+ limit: number,
113
+ ): Promise<SessionRecord[]> {
114
+ const requestedLimit = normalizeHistoryLimit(limit);
115
+ const sessionsDir = resolveSessionDataDir();
116
+ const entries = await readdir(sessionsDir, { withFileTypes: true }).catch(
117
+ () => [],
118
+ );
119
+ const candidateEntries = entries
120
+ .filter((entry) => entry.isDirectory())
121
+ .map((entry) => ({
122
+ entry,
123
+ recency: extractSessionRecencyToken(entry.name.trim()),
124
+ }))
125
+ .sort(
126
+ (left, right) =>
127
+ right.recency - left.recency ||
128
+ right.entry.name.localeCompare(left.entry.name),
129
+ )
130
+ .slice(0, requestedLimit);
131
+ const rows = await Promise.all(
132
+ candidateEntries.map(async ({ entry }) => {
133
+ const sessionId = entry.name.trim();
134
+ if (!sessionId) {
135
+ return undefined;
136
+ }
137
+ const manifestPath = join(sessionsDir, sessionId, `${sessionId}.json`);
138
+ const raw = await readFile(manifestPath, "utf8").catch(() => undefined);
139
+ if (!raw) {
140
+ return undefined;
141
+ }
142
+ let parsedJson: unknown;
143
+ try {
144
+ parsedJson = JSON.parse(raw) as unknown;
145
+ } catch {
146
+ return undefined;
147
+ }
148
+ const parsedManifest = SessionManifestSchema.safeParse(parsedJson);
149
+ if (!parsedManifest.success) {
150
+ return undefined;
151
+ }
152
+ return manifestToSessionRecord(parsedManifest.data);
153
+ }),
154
+ );
155
+
156
+ return rows
157
+ .filter((row): row is SessionRecord => Boolean(row))
158
+ .sort((left, right) => right.startedAt.localeCompare(left.startedAt))
159
+ .slice(0, requestedLimit);
160
+ }
161
+
54
162
  function extractTextFromContent(
55
163
  content: LlmsProviders.Message["content"],
56
164
  ): string {
@@ -235,3 +343,32 @@ export async function hydrateSessionHistory(
235
343
  }),
236
344
  );
237
345
  }
346
+
347
+ export async function listSessionHistory(
348
+ host: Pick<RuntimeHost, "list" | "readMessages">,
349
+ options: SessionHistoryListOptions = {},
350
+ ): Promise<SessionHistoryRecord[]> {
351
+ const limit = normalizeHistoryLimit(options.limit);
352
+ const backendRows = (await host.list(limit)).slice(0, limit);
353
+ const manifestRows =
354
+ options.includeManifestFallback === true && backendRows.length < limit
355
+ ? await listManifestHistoryRows(Math.min(Math.max(limit * 2, 100), 500))
356
+ : [];
357
+ const merged = new Map<string, SessionRecord>();
358
+ for (const row of [...backendRows, ...manifestRows]) {
359
+ if (merged.has(row.sessionId)) {
360
+ continue;
361
+ }
362
+ merged.set(row.sessionId, row);
363
+ }
364
+ const rows =
365
+ manifestRows.length === 0
366
+ ? backendRows
367
+ : Array.from(merged.values())
368
+ .sort((left, right) => right.startedAt.localeCompare(left.startedAt))
369
+ .slice(0, limit);
370
+ if (options.hydrate === false) {
371
+ return rows.map((row) => normalizeHistoryRow(row));
372
+ }
373
+ return await hydrateSessionHistory(host, rows);
374
+ }
@@ -3,6 +3,7 @@ import {
3
3
  ensureCompatibleLocalHubUrl,
4
4
  resolveCompatibleLocalHubUrl,
5
5
  } from "../hub/client";
6
+ import { prewarmDetachedHubServer } from "../hub/daemon";
6
7
  import { SqliteSessionStore } from "../services/storage/sqlite-session-store";
7
8
  import { resolveCoreDistinctId } from "../services/telemetry/distinct-id";
8
9
  import { FileSessionService } from "../session/file-session-service";
@@ -33,6 +34,23 @@ export type SessionBackend = CoreSessionService | FileSessionService;
33
34
  let cachedBackend: SessionBackend | undefined;
34
35
  let backendInitPromise: Promise<SessionBackend> | undefined;
35
36
 
37
+ function prewarmLocalHubIfNeeded(
38
+ configuredMode: RuntimeHostMode,
39
+ options: ClineCoreOptions,
40
+ ): void {
41
+ if (configuredMode !== "auto" && configuredMode !== "hub") {
42
+ return;
43
+ }
44
+ if (options.hub?.endpoint?.trim()) {
45
+ return;
46
+ }
47
+ prewarmDetachedHubServer(
48
+ options.hub?.workspaceRoot?.trim() ||
49
+ options.hub?.cwd?.trim() ||
50
+ process.cwd(),
51
+ );
52
+ }
53
+
36
54
  async function reconcileDeadSessionsIfSupported(
37
55
  backend: SessionBackend,
38
56
  ): Promise<void> {
@@ -108,6 +126,7 @@ export async function createRuntimeHost(
108
126
  const distinctId = resolveCoreDistinctId(options.distinctId);
109
127
  options.telemetry?.setDistinctId(distinctId);
110
128
  const configuredMode = resolveConfiguredBackendMode(options);
129
+ prewarmLocalHubIfNeeded(configuredMode, options);
111
130
  if (configuredMode === "remote") {
112
131
  const remoteEndpoint = options.remote?.endpoint?.trim();
113
132
  if (!remoteEndpoint) {
@@ -125,6 +144,7 @@ export async function createRuntimeHost(
125
144
  displayName: options.remote?.displayName,
126
145
  workspaceRoot: options.remote?.workspaceRoot,
127
146
  cwd: options.remote?.cwd,
147
+ requestToolApproval: options.requestToolApproval,
128
148
  });
129
149
  }
130
150
  if (configuredMode === "hub") {
@@ -149,6 +169,7 @@ export async function createRuntimeHost(
149
169
  authToken: options.hub?.authToken,
150
170
  clientType: options.hub?.clientType,
151
171
  displayName: options.hub?.displayName,
172
+ requestToolApproval: options.requestToolApproval,
152
173
  },
153
174
  {
154
175
  workspaceRoot: options.hub?.workspaceRoot,
@@ -173,6 +194,7 @@ export async function createRuntimeHost(
173
194
  authToken: options.hub?.authToken,
174
195
  clientType: options.hub?.clientType,
175
196
  displayName: options.hub?.displayName,
197
+ requestToolApproval: options.requestToolApproval,
176
198
  },
177
199
  {
178
200
  workspaceRoot: options.hub?.workspaceRoot,