@clinebot/core 0.0.35 → 0.0.36

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 (335) hide show
  1. package/README.md +1 -2
  2. package/dist/ClineCore.d.ts +53 -39
  3. package/dist/ClineCore.d.ts.map +1 -1
  4. package/dist/account/index.d.ts +1 -1
  5. package/dist/account/index.d.ts.map +1 -1
  6. package/dist/account/rpc.d.ts +6 -6
  7. package/dist/account/rpc.d.ts.map +1 -1
  8. package/dist/cron/index.d.ts +6 -0
  9. package/dist/cron/index.d.ts.map +1 -0
  10. package/dist/cron/resource-limiter.d.ts +9 -0
  11. package/dist/cron/resource-limiter.d.ts.map +1 -0
  12. package/dist/cron/schedule-command-service.d.ts +10 -0
  13. package/dist/cron/schedule-command-service.d.ts.map +1 -0
  14. package/dist/cron/schedule-service.d.ts +100 -0
  15. package/dist/cron/schedule-service.d.ts.map +1 -0
  16. package/dist/cron/scheduler.d.ts +66 -0
  17. package/dist/cron/scheduler.d.ts.map +1 -0
  18. package/dist/cron/sqlite-schedule-store.d.ts +52 -0
  19. package/dist/cron/sqlite-schedule-store.d.ts.map +1 -0
  20. package/dist/extensions/config/agent-config-loader.d.ts +4 -3
  21. package/dist/extensions/config/agent-config-loader.d.ts.map +1 -1
  22. package/dist/extensions/config/runtime-commands.d.ts +1 -0
  23. package/dist/extensions/config/runtime-commands.d.ts.map +1 -1
  24. package/dist/extensions/config/user-instruction-config-loader.d.ts +1 -0
  25. package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -1
  26. package/dist/extensions/context/agentic-compaction.d.ts +2 -2
  27. package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
  28. package/dist/extensions/context/compaction-shared.d.ts +5 -4
  29. package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
  30. package/dist/extensions/context/compaction.d.ts.map +1 -1
  31. package/dist/extensions/plugin/plugin-config-loader.d.ts +9 -2
  32. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  33. package/dist/extensions/plugin/plugin-loader.d.ts +5 -3
  34. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  35. package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -1
  36. package/dist/extensions/plugin/plugin-sandbox.d.ts +15 -2
  37. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  38. package/dist/extensions/plugin/plugin-targeting.d.ts +7 -0
  39. package/dist/extensions/plugin/plugin-targeting.d.ts.map +1 -0
  40. package/dist/extensions/plugin-sandbox-bootstrap.js +211 -211
  41. package/dist/extensions/tools/definitions.d.ts +1 -1
  42. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  43. package/dist/extensions/tools/executors/apply-patch.d.ts +3 -1
  44. package/dist/extensions/tools/executors/apply-patch.d.ts.map +1 -1
  45. package/dist/extensions/tools/executors/search.d.ts +1 -1
  46. package/dist/extensions/tools/executors/search.d.ts.map +1 -1
  47. package/dist/extensions/tools/index.d.ts +2 -0
  48. package/dist/extensions/tools/index.d.ts.map +1 -1
  49. package/dist/extensions/tools/presets.d.ts +26 -43
  50. package/dist/extensions/tools/presets.d.ts.map +1 -1
  51. package/dist/extensions/tools/runtime.d.ts +25 -0
  52. package/dist/extensions/tools/runtime.d.ts.map +1 -0
  53. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  54. package/dist/extensions/tools/team/team-tools.d.ts +1 -0
  55. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  56. package/dist/hooks/hook-file-hooks.d.ts +4 -1
  57. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  58. package/dist/hooks/index.d.ts +0 -1
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/subprocess.d.ts +8 -1
  61. package/dist/hooks/subprocess.d.ts.map +1 -1
  62. package/dist/hub/browser-websocket.d.ts +18 -0
  63. package/dist/hub/browser-websocket.d.ts.map +1 -0
  64. package/dist/hub/client.d.ts +45 -0
  65. package/dist/hub/client.d.ts.map +1 -0
  66. package/dist/hub/connect.d.ts +15 -0
  67. package/dist/hub/connect.d.ts.map +1 -0
  68. package/dist/hub/daemon-entry.d.ts +2 -0
  69. package/dist/hub/daemon-entry.d.ts.map +1 -0
  70. package/dist/hub/daemon-entry.js +1045 -0
  71. package/dist/hub/daemon.d.ts +5 -0
  72. package/dist/hub/daemon.d.ts.map +1 -0
  73. package/dist/hub/defaults.d.ts +13 -0
  74. package/dist/hub/defaults.d.ts.map +1 -0
  75. package/dist/hub/discovery.d.ts +29 -0
  76. package/dist/hub/discovery.d.ts.map +1 -0
  77. package/dist/hub/index.d.ts +15 -0
  78. package/dist/hub/index.d.ts.map +1 -0
  79. package/dist/hub/index.js +1044 -0
  80. package/dist/hub/native-transport.d.ts +17 -0
  81. package/dist/hub/native-transport.d.ts.map +1 -0
  82. package/dist/hub/runtime-handlers.d.ts +11 -0
  83. package/dist/hub/runtime-handlers.d.ts.map +1 -0
  84. package/dist/hub/server.d.ts +86 -0
  85. package/dist/hub/server.d.ts.map +1 -0
  86. package/dist/hub/session-client.d.ts +87 -0
  87. package/dist/hub/session-client.d.ts.map +1 -0
  88. package/dist/hub/start-shared-server.d.ts +19 -0
  89. package/dist/hub/start-shared-server.d.ts.map +1 -0
  90. package/dist/hub/transport.d.ts +8 -0
  91. package/dist/hub/transport.d.ts.map +1 -0
  92. package/dist/hub/ui-client.d.ts +44 -0
  93. package/dist/hub/ui-client.d.ts.map +1 -0
  94. package/dist/hub/workspace.d.ts +4 -0
  95. package/dist/hub/workspace.d.ts.map +1 -0
  96. package/dist/index.d.ts +26 -15
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +498 -476
  99. package/dist/llms/configured-provider-registry.d.ts +28 -0
  100. package/dist/llms/configured-provider-registry.d.ts.map +1 -0
  101. package/dist/llms/provider-defaults.d.ts +27 -0
  102. package/dist/llms/provider-defaults.d.ts.map +1 -0
  103. package/dist/llms/provider-settings.d.ts +202 -0
  104. package/dist/llms/provider-settings.d.ts.map +1 -0
  105. package/dist/llms/runtime-config.d.ts +4 -0
  106. package/dist/llms/runtime-config.d.ts.map +1 -0
  107. package/dist/llms/runtime-registry.d.ts +20 -0
  108. package/dist/llms/runtime-registry.d.ts.map +1 -0
  109. package/dist/llms/runtime-types.d.ts +85 -0
  110. package/dist/llms/runtime-types.d.ts.map +1 -0
  111. package/dist/runtime/host.d.ts +1 -2
  112. package/dist/runtime/host.d.ts.map +1 -1
  113. package/dist/runtime/rules.d.ts +1 -0
  114. package/dist/runtime/rules.d.ts.map +1 -1
  115. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  116. package/dist/runtime/runtime-host.d.ts +22 -24
  117. package/dist/runtime/runtime-host.d.ts.map +1 -1
  118. package/dist/runtime/runtime-oauth-token-manager.d.ts.map +1 -1
  119. package/dist/runtime/session-runtime.d.ts +1 -19
  120. package/dist/runtime/session-runtime.d.ts.map +1 -1
  121. package/dist/services/global-settings.d.ts +12 -0
  122. package/dist/services/global-settings.d.ts.map +1 -0
  123. package/dist/services/local-runtime-bootstrap.d.ts +9 -3
  124. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  125. package/dist/services/plugin-tools.d.ts +16 -0
  126. package/dist/services/plugin-tools.d.ts.map +1 -0
  127. package/dist/services/providers/local-provider-registry.d.ts +4 -4
  128. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  129. package/dist/services/providers/local-provider-service.d.ts +13 -13
  130. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  131. package/dist/services/session-data.d.ts +1 -1
  132. package/dist/services/session-data.d.ts.map +1 -1
  133. package/dist/services/storage/provider-settings-legacy-migration.d.ts +1 -1
  134. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  135. package/dist/services/telemetry/index.js +28 -15
  136. package/dist/services/workspace-manifest.d.ts +11 -0
  137. package/dist/services/workspace-manifest.d.ts.map +1 -1
  138. package/dist/session/persistence-service.d.ts +11 -23
  139. package/dist/session/persistence-service.d.ts.map +1 -1
  140. package/dist/session/session-manifest-store.d.ts +22 -0
  141. package/dist/session/session-manifest-store.d.ts.map +1 -0
  142. package/dist/session/session-row.d.ts +93 -0
  143. package/dist/session/session-row.d.ts.map +1 -0
  144. package/dist/session/session-service.d.ts +2 -102
  145. package/dist/session/session-service.d.ts.map +1 -1
  146. package/dist/session/subagent-session-manager.d.ts +36 -0
  147. package/dist/session/subagent-session-manager.d.ts.map +1 -0
  148. package/dist/session/team-persistence-store.d.ts +24 -0
  149. package/dist/session/team-persistence-store.d.ts.map +1 -0
  150. package/dist/transports/hub.d.ts +47 -0
  151. package/dist/transports/hub.d.ts.map +1 -0
  152. package/dist/transports/local.d.ts +10 -6
  153. package/dist/transports/local.d.ts.map +1 -1
  154. package/dist/transports/remote.d.ts +10 -0
  155. package/dist/transports/remote.d.ts.map +1 -0
  156. package/dist/transports/runtime-host-support.d.ts +3 -2
  157. package/dist/transports/runtime-host-support.d.ts.map +1 -1
  158. package/dist/types/chat-schema.d.ts +10 -12
  159. package/dist/types/chat-schema.d.ts.map +1 -1
  160. package/dist/types/config.d.ts +8 -7
  161. package/dist/types/config.d.ts.map +1 -1
  162. package/dist/types/provider-settings.d.ts +4 -5
  163. package/dist/types/provider-settings.d.ts.map +1 -1
  164. package/dist/types/session.d.ts +2 -1
  165. package/dist/types/session.d.ts.map +1 -1
  166. package/dist/types.d.ts +8 -1
  167. package/dist/types.d.ts.map +1 -1
  168. package/package.json +20 -6
  169. package/src/ClineCore.ts +68 -40
  170. package/src/account/index.ts +3 -3
  171. package/src/account/rpc.ts +12 -12
  172. package/src/cron/index.ts +5 -0
  173. package/src/cron/resource-limiter.ts +46 -0
  174. package/src/cron/schedule-command-service.ts +193 -0
  175. package/src/cron/schedule-service.ts +703 -0
  176. package/src/cron/scheduler.ts +637 -0
  177. package/src/cron/sqlite-schedule-store.ts +708 -0
  178. package/src/extensions/config/agent-config-loader.ts +17 -7
  179. package/src/extensions/config/runtime-commands.ts +6 -0
  180. package/src/extensions/config/user-instruction-config-loader.ts +1 -0
  181. package/src/extensions/context/agentic-compaction.ts +3 -3
  182. package/src/extensions/context/basic-compaction.ts +2 -2
  183. package/src/extensions/context/compaction-shared.ts +5 -4
  184. package/src/extensions/context/compaction.ts +3 -3
  185. package/src/extensions/plugin/plugin-config-loader.ts +17 -2
  186. package/src/extensions/plugin/plugin-loader.ts +48 -4
  187. package/src/extensions/plugin/plugin-module-import.ts +0 -2
  188. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +93 -39
  189. package/src/extensions/plugin/plugin-sandbox.ts +47 -27
  190. package/src/extensions/plugin/plugin-targeting.ts +32 -0
  191. package/src/extensions/tools/definitions.ts +30 -49
  192. package/src/extensions/tools/executors/apply-patch.ts +69 -80
  193. package/src/extensions/tools/executors/search.ts +195 -3
  194. package/src/extensions/tools/index.ts +10 -0
  195. package/src/extensions/tools/presets.ts +31 -46
  196. package/src/extensions/tools/runtime.ts +261 -0
  197. package/src/extensions/tools/schemas.ts +4 -2
  198. package/src/extensions/tools/team/team-tools.ts +21 -0
  199. package/src/hooks/hook-file-hooks.ts +8 -2
  200. package/src/hooks/index.ts +0 -7
  201. package/src/hooks/subprocess-runner.ts +1 -1
  202. package/src/hooks/subprocess.ts +9 -0
  203. package/src/hub/browser-websocket.ts +137 -0
  204. package/src/hub/client.ts +574 -0
  205. package/src/hub/connect.ts +156 -0
  206. package/src/hub/daemon-entry.ts +87 -0
  207. package/src/hub/daemon.ts +181 -0
  208. package/src/hub/defaults.ts +43 -0
  209. package/src/hub/discovery.ts +247 -0
  210. package/src/hub/index.ts +14 -0
  211. package/src/hub/native-transport.ts +31 -0
  212. package/src/hub/runtime-handlers.ts +140 -0
  213. package/src/hub/server.ts +1888 -0
  214. package/src/hub/session-client.ts +460 -0
  215. package/src/hub/start-shared-server.ts +58 -0
  216. package/src/hub/transport.ts +14 -0
  217. package/src/hub/ui-client.ts +122 -0
  218. package/src/hub/workspace.ts +19 -0
  219. package/src/index.ts +124 -68
  220. package/src/llms/configured-provider-registry.ts +193 -0
  221. package/src/llms/provider-defaults.ts +637 -0
  222. package/src/llms/provider-settings.ts +263 -0
  223. package/src/llms/runtime-config.ts +43 -0
  224. package/src/llms/runtime-registry.ts +171 -0
  225. package/src/llms/runtime-types.ts +121 -0
  226. package/src/runtime/host.ts +107 -269
  227. package/src/runtime/index.ts +1 -0
  228. package/src/runtime/rules.ts +12 -0
  229. package/src/runtime/runtime-builder.ts +24 -8
  230. package/src/runtime/runtime-host.ts +89 -61
  231. package/src/runtime/runtime-oauth-token-manager.ts +11 -15
  232. package/src/runtime/session-runtime.ts +0 -24
  233. package/src/services/global-settings.ts +122 -0
  234. package/src/services/local-runtime-bootstrap.ts +51 -13
  235. package/src/services/plugin-tools.ts +85 -0
  236. package/src/services/providers/local-provider-registry.ts +6 -6
  237. package/src/services/providers/local-provider-service.ts +42 -37
  238. package/src/services/session-data.ts +15 -9
  239. package/src/services/storage/provider-settings-legacy-migration.ts +6 -4
  240. package/src/services/storage/provider-settings-manager.ts +1 -1
  241. package/src/services/workspace-manifest.ts +18 -0
  242. package/src/session/file-session-service.ts +1 -1
  243. package/src/session/index.ts +6 -27
  244. package/src/session/persistence-service.ts +119 -504
  245. package/src/session/session-manifest-store.ts +158 -0
  246. package/src/session/session-row.ts +199 -0
  247. package/src/session/session-service.ts +17 -376
  248. package/src/session/session-team-coordination.ts +1 -1
  249. package/src/session/subagent-session-manager.ts +397 -0
  250. package/src/session/team-persistence-store.ts +176 -0
  251. package/src/transports/hub.ts +656 -0
  252. package/src/transports/local.ts +135 -40
  253. package/src/transports/remote.ts +26 -0
  254. package/src/transports/runtime-host-support.ts +63 -9
  255. package/src/types/chat-schema.ts +4 -5
  256. package/src/types/config.ts +8 -7
  257. package/src/types/provider-settings.ts +11 -7
  258. package/src/types/session.ts +2 -4
  259. package/src/types.ts +27 -1
  260. package/dist/hooks/persistent.d.ts +0 -64
  261. package/dist/hooks/persistent.d.ts.map +0 -1
  262. package/dist/runtime/rpc-runtime-ensure.d.ts +0 -65
  263. package/dist/runtime/rpc-runtime-ensure.d.ts.map +0 -1
  264. package/dist/runtime/rpc-spawn-lease.d.ts +0 -8
  265. package/dist/runtime/rpc-spawn-lease.d.ts.map +0 -1
  266. package/dist/session/rpc-session-service.d.ts +0 -16
  267. package/dist/session/rpc-session-service.d.ts.map +0 -1
  268. package/dist/session/sqlite-rpc-session-backend.d.ts +0 -31
  269. package/dist/session/sqlite-rpc-session-backend.d.ts.map +0 -1
  270. package/dist/transports/rpc.d.ts +0 -51
  271. package/dist/transports/rpc.d.ts.map +0 -1
  272. package/src/ClineCore.test.ts +0 -226
  273. package/src/account/cline-account-service.test.ts +0 -185
  274. package/src/account/featurebase-token.test.ts +0 -175
  275. package/src/account/rpc.test.ts +0 -63
  276. package/src/auth/bounded-ttl-cache.test.ts +0 -38
  277. package/src/auth/client.test.ts +0 -69
  278. package/src/auth/cline.test.ts +0 -267
  279. package/src/auth/codex.test.ts +0 -170
  280. package/src/auth/oca.test.ts +0 -340
  281. package/src/auth/server.test.ts +0 -287
  282. package/src/auth/utils.test.ts +0 -128
  283. package/src/extensions/config/agent-config-loader.test.ts +0 -236
  284. package/src/extensions/config/hooks-config-loader.test.ts +0 -20
  285. package/src/extensions/config/runtime-commands.test.ts +0 -115
  286. package/src/extensions/config/unified-config-file-watcher.test.ts +0 -196
  287. package/src/extensions/config/user-instruction-config-loader.test.ts +0 -246
  288. package/src/extensions/context/compaction.test.ts +0 -483
  289. package/src/extensions/mcp/config-loader.test.ts +0 -238
  290. package/src/extensions/mcp/manager.test.ts +0 -105
  291. package/src/extensions/plugin/plugin-config-loader.test.ts +0 -184
  292. package/src/extensions/plugin/plugin-loader.test.ts +0 -292
  293. package/src/extensions/plugin/plugin-sandbox.test.ts +0 -423
  294. package/src/extensions/tools/definitions.test.ts +0 -780
  295. package/src/extensions/tools/executors/bash.test.ts +0 -87
  296. package/src/extensions/tools/executors/editor.test.ts +0 -35
  297. package/src/extensions/tools/executors/file-read.test.ts +0 -125
  298. package/src/extensions/tools/model-tool-routing.test.ts +0 -86
  299. package/src/extensions/tools/presets.test.ts +0 -70
  300. package/src/extensions/tools/team/multi-agent.lifecycle.test.ts +0 -455
  301. package/src/extensions/tools/team/spawn-agent-tool.test.ts +0 -381
  302. package/src/extensions/tools/team/team-tools.test.ts +0 -918
  303. package/src/hooks/checkpoint-hooks.test.ts +0 -168
  304. package/src/hooks/hook-file-hooks.test.ts +0 -311
  305. package/src/hooks/persistent.ts +0 -661
  306. package/src/runtime/history.test.ts +0 -114
  307. package/src/runtime/host.test.ts +0 -230
  308. package/src/runtime/rpc-runtime-ensure.test.ts +0 -123
  309. package/src/runtime/rpc-runtime-ensure.ts +0 -659
  310. package/src/runtime/rpc-spawn-lease.test.ts +0 -81
  311. package/src/runtime/rpc-spawn-lease.ts +0 -156
  312. package/src/runtime/runtime-builder.team-persistence.test.ts +0 -245
  313. package/src/runtime/runtime-builder.test.ts +0 -615
  314. package/src/runtime/runtime-oauth-token-manager.test.ts +0 -137
  315. package/src/runtime/runtime-parity.test.ts +0 -143
  316. package/src/services/providers/local-provider-service.test.ts +0 -1062
  317. package/src/services/session-data.test.ts +0 -160
  318. package/src/services/storage/provider-settings-legacy-migration.test.ts +0 -424
  319. package/src/services/storage/provider-settings-manager.test.ts +0 -191
  320. package/src/services/telemetry/OpenTelemetryAdapter.test.ts +0 -157
  321. package/src/services/telemetry/OpenTelemetryProvider.test.ts +0 -326
  322. package/src/services/telemetry/TelemetryLoggerSink.test.ts +0 -42
  323. package/src/services/telemetry/TelemetryService.test.ts +0 -134
  324. package/src/services/telemetry/distinct-id.test.ts +0 -57
  325. package/src/services/workspace/file-indexer.d.ts +0 -11
  326. package/src/services/workspace/file-indexer.test.ts +0 -156
  327. package/src/services/workspace/mention-enricher.test.ts +0 -106
  328. package/src/session/persistence-service.test.ts +0 -300
  329. package/src/session/rpc-session-service.ts +0 -114
  330. package/src/session/session-service.team-persistence.test.ts +0 -48
  331. package/src/session/sqlite-rpc-session-backend.ts +0 -301
  332. package/src/transports/local.e2e.test.ts +0 -380
  333. package/src/transports/local.test.ts +0 -2559
  334. package/src/transports/rpc.test.ts +0 -82
  335. package/src/transports/rpc.ts +0 -665
@@ -0,0 +1,397 @@
1
+ import { appendFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type * as LlmsProviders from "@clinebot/llms";
4
+ import type { AgentResult } from "@clinebot/shared";
5
+ import { resolveRootSessionId } from "@clinebot/shared";
6
+ import { ensureHookLogDir } from "@clinebot/shared/storage";
7
+ import { z } from "zod";
8
+ import type {
9
+ SubAgentEndContext,
10
+ SubAgentStartContext,
11
+ } from "../extensions/tools/team";
12
+ import type { HookEventPayload } from "../hooks";
13
+ import { nowIso } from "../services/session-artifacts";
14
+ import { resolveMetadataWithTitle } from "../services/session-data";
15
+ import type { SessionStatus } from "../types/common";
16
+ import type {
17
+ SessionPersistenceAdapter,
18
+ StoredMessageWithMetadata,
19
+ } from "../types/session";
20
+ import {
21
+ deriveSubsessionStatus,
22
+ makeSubSessionId,
23
+ makeTeamTaskSubSessionId,
24
+ } from "./session-graph";
25
+ import type { SessionManifestStore } from "./session-manifest-store";
26
+ import type { SessionRow, UpsertSubagentInput } from "./session-row";
27
+
28
+ const SUBSESSION_SOURCE = "subagent";
29
+ const SpawnAgentInputSchema = z.looseObject({
30
+ task: z.string().optional(),
31
+ systemPrompt: z.string().optional(),
32
+ });
33
+
34
+ export class SubagentSessionManager {
35
+ private readonly teamTaskSessionsByAgent = new Map<string, string[]>();
36
+ private readonly teamTaskLastHeartbeatBySession = new Map<string, number>();
37
+ private readonly teamTaskLastProgressLineBySession = new Map<
38
+ string,
39
+ string
40
+ >();
41
+
42
+ constructor(
43
+ private readonly adapter: SessionPersistenceAdapter,
44
+ private readonly manifestStore: SessionManifestStore,
45
+ private readonly toPersistedMessages: (
46
+ messages: LlmsProviders.Message[] | undefined,
47
+ result?: AgentResult,
48
+ previousMessages?: LlmsProviders.Message[],
49
+ ) => StoredMessageWithMetadata[] | undefined,
50
+ private readonly heartbeatLogIntervalMs: number,
51
+ ) {}
52
+
53
+ private teamTaskQueueKey(rootSessionId: string, agentId: string): string {
54
+ return `${rootSessionId}::${agentId}`;
55
+ }
56
+
57
+ private activeTeamTaskSessionId(
58
+ rootSessionId: string,
59
+ parentAgentId: string,
60
+ ): string | undefined {
61
+ const queue = this.teamTaskSessionsByAgent.get(
62
+ this.teamTaskQueueKey(rootSessionId, parentAgentId),
63
+ );
64
+ return queue?.at(-1);
65
+ }
66
+
67
+ private buildSubsessionRow(
68
+ root: SessionRow,
69
+ opts: {
70
+ sessionId: string;
71
+ parentSessionId: string;
72
+ parentAgentId: string;
73
+ agentId: string;
74
+ conversationId?: string | null;
75
+ prompt: string;
76
+ startedAt: string;
77
+ messagesPath: string;
78
+ },
79
+ ): SessionRow {
80
+ return {
81
+ sessionId: opts.sessionId,
82
+ source: SUBSESSION_SOURCE,
83
+ pid: process.ppid,
84
+ startedAt: opts.startedAt,
85
+ endedAt: null,
86
+ exitCode: null,
87
+ status: "running",
88
+ statusLock: 0,
89
+ interactive: false,
90
+ provider: root.provider,
91
+ model: root.model,
92
+ cwd: root.cwd,
93
+ workspaceRoot: root.workspaceRoot,
94
+ teamName: root.teamName ?? null,
95
+ enableTools: root.enableTools,
96
+ enableSpawn: root.enableSpawn,
97
+ enableTeams: root.enableTeams,
98
+ parentSessionId: opts.parentSessionId,
99
+ parentAgentId: opts.parentAgentId,
100
+ agentId: opts.agentId,
101
+ conversationId: opts.conversationId ?? null,
102
+ isSubagent: true,
103
+ prompt: opts.prompt,
104
+ metadata: resolveMetadataWithTitle({ prompt: opts.prompt }),
105
+ hookPath: "",
106
+ messagesPath: opts.messagesPath,
107
+ updatedAt: opts.startedAt,
108
+ };
109
+ }
110
+
111
+ async queueSpawnRequest(event: HookEventPayload): Promise<void> {
112
+ if (event.hookName !== "tool_call" || event.parent_agent_id !== null)
113
+ return;
114
+ if (event.tool_call?.name !== "spawn_agent") return;
115
+
116
+ const rootSessionId = resolveRootSessionId(event.sessionContext);
117
+ if (!rootSessionId) return;
118
+
119
+ const parsed = SpawnAgentInputSchema.safeParse(event.tool_call.input);
120
+ await this.adapter.enqueueSpawnRequest({
121
+ rootSessionId,
122
+ parentAgentId: event.agent_id,
123
+ task: parsed.success ? parsed.data.task : undefined,
124
+ systemPrompt: parsed.success ? parsed.data.systemPrompt : undefined,
125
+ });
126
+ }
127
+
128
+ async upsertSubagentSession(
129
+ input: UpsertSubagentInput,
130
+ ): Promise<string | undefined> {
131
+ const rootSessionId = input.rootSessionId;
132
+ if (!rootSessionId) return undefined;
133
+
134
+ const root = await this.adapter.getSession(rootSessionId);
135
+ if (!root) return undefined;
136
+
137
+ const sessionId = makeSubSessionId(rootSessionId, input.agentId);
138
+ const existing = await this.adapter.getSession(sessionId);
139
+ const startedAt = nowIso();
140
+ const artifactPaths = this.manifestStore.artifacts.subagentArtifactPaths(
141
+ sessionId,
142
+ input.agentId,
143
+ this.activeTeamTaskSessionId(rootSessionId, input.parentAgentId),
144
+ );
145
+
146
+ let prompt = input.prompt ?? existing?.prompt ?? undefined;
147
+ if (!prompt) {
148
+ prompt =
149
+ (await this.adapter.claimSpawnRequest(
150
+ rootSessionId,
151
+ input.parentAgentId,
152
+ )) ?? `Subagent run by ${input.parentAgentId}`;
153
+ }
154
+
155
+ if (!existing) {
156
+ await this.adapter.upsertSession(
157
+ this.buildSubsessionRow(root, {
158
+ sessionId,
159
+ parentSessionId: rootSessionId,
160
+ parentAgentId: input.parentAgentId,
161
+ agentId: input.agentId,
162
+ conversationId: input.conversationId,
163
+ prompt,
164
+ startedAt,
165
+ ...artifactPaths,
166
+ }),
167
+ );
168
+ this.manifestStore.initializeMessagesFile(
169
+ sessionId,
170
+ artifactPaths.messagesPath,
171
+ startedAt,
172
+ );
173
+ return sessionId;
174
+ }
175
+
176
+ await this.adapter.updateSession({
177
+ sessionId,
178
+ setRunning: true,
179
+ parentSessionId: rootSessionId,
180
+ parentAgentId: input.parentAgentId,
181
+ agentId: input.agentId,
182
+ conversationId: input.conversationId,
183
+ prompt: existing.prompt ?? prompt ?? null,
184
+ metadata: resolveMetadataWithTitle({
185
+ metadata: existing.metadata ?? undefined,
186
+ prompt: existing.prompt ?? prompt ?? null,
187
+ }),
188
+ expectedStatusLock: existing.statusLock,
189
+ });
190
+ return sessionId;
191
+ }
192
+
193
+ async upsertSubagentSessionFromHook(
194
+ event: HookEventPayload,
195
+ ): Promise<string | undefined> {
196
+ if (!event.parent_agent_id) return undefined;
197
+ const rootSessionId = resolveRootSessionId(event.sessionContext);
198
+ if (!rootSessionId) return undefined;
199
+ if (event.hookName === "session_shutdown") {
200
+ const sessionId = makeSubSessionId(rootSessionId, event.agent_id);
201
+ const existing = await this.adapter.getSession(sessionId);
202
+ return existing ? sessionId : undefined;
203
+ }
204
+ return await this.upsertSubagentSession({
205
+ agentId: event.agent_id,
206
+ parentAgentId: event.parent_agent_id,
207
+ conversationId: event.taskId,
208
+ rootSessionId,
209
+ });
210
+ }
211
+
212
+ async applySubagentStatus(
213
+ subSessionId: string,
214
+ event: HookEventPayload,
215
+ ): Promise<void> {
216
+ await this.applySubagentStatusBySessionId(
217
+ subSessionId,
218
+ deriveSubsessionStatus(event),
219
+ );
220
+ }
221
+
222
+ async applySubagentStatusBySessionId(
223
+ subSessionId: string,
224
+ status: SessionStatus,
225
+ ): Promise<void> {
226
+ const row = await this.adapter.getSession(subSessionId);
227
+ if (!row) return;
228
+ const endedAt = status === "running" ? null : nowIso();
229
+ const exitCode = status === "running" ? null : status === "failed" ? 1 : 0;
230
+ await this.adapter.updateSession({
231
+ sessionId: subSessionId,
232
+ status,
233
+ endedAt,
234
+ exitCode,
235
+ expectedStatusLock: row.statusLock,
236
+ });
237
+ }
238
+
239
+ async applyStatusToRunningChildSessions(
240
+ parentSessionId: string,
241
+ status: Exclude<SessionStatus, "running">,
242
+ ): Promise<void> {
243
+ if (!parentSessionId) return;
244
+ const rows = await this.adapter.listSessions({
245
+ limit: 2000,
246
+ parentSessionId,
247
+ status: "running",
248
+ });
249
+ for (const row of rows) {
250
+ await this.applySubagentStatusBySessionId(row.sessionId, status);
251
+ }
252
+ }
253
+
254
+ async onTeamTaskStart(
255
+ rootSessionId: string,
256
+ agentId: string,
257
+ message: string,
258
+ ): Promise<void> {
259
+ const root = await this.adapter.getSession(rootSessionId);
260
+ if (!root) return;
261
+ const sessionId = makeTeamTaskSubSessionId(rootSessionId, agentId);
262
+ const startedAt = nowIso();
263
+ const { messagesPath } = this.manifestStore.artifacts.subagentArtifactPaths(
264
+ sessionId,
265
+ agentId,
266
+ );
267
+ await this.adapter.upsertSession(
268
+ this.buildSubsessionRow(root, {
269
+ sessionId,
270
+ parentSessionId: rootSessionId,
271
+ parentAgentId: "lead",
272
+ agentId,
273
+ prompt: message || `Team task for ${agentId}`,
274
+ startedAt,
275
+ messagesPath,
276
+ }),
277
+ );
278
+ this.manifestStore.initializeMessagesFile(
279
+ sessionId,
280
+ messagesPath,
281
+ startedAt,
282
+ );
283
+ const key = this.teamTaskQueueKey(rootSessionId, agentId);
284
+ const queue = this.teamTaskSessionsByAgent.get(key) ?? [];
285
+ queue.push(sessionId);
286
+ this.teamTaskSessionsByAgent.set(key, queue);
287
+ }
288
+
289
+ async onTeamTaskEnd(
290
+ rootSessionId: string,
291
+ agentId: string,
292
+ status: SessionStatus,
293
+ _summary?: string,
294
+ result?: AgentResult,
295
+ messages?: LlmsProviders.Message[],
296
+ ): Promise<void> {
297
+ const key = this.teamTaskQueueKey(rootSessionId, agentId);
298
+ const queue = this.teamTaskSessionsByAgent.get(key);
299
+ if (!queue || queue.length === 0) return;
300
+ const sessionId = queue.shift();
301
+ if (queue.length === 0) this.teamTaskSessionsByAgent.delete(key);
302
+ if (!sessionId) return;
303
+ const teammateMessages = result?.messages ?? messages;
304
+ const persistedMessages = this.toPersistedMessages(
305
+ teammateMessages,
306
+ result,
307
+ messages,
308
+ );
309
+ if (persistedMessages) {
310
+ await this.manifestStore.persistSessionMessages(
311
+ sessionId,
312
+ persistedMessages,
313
+ );
314
+ }
315
+ await this.applySubagentStatusBySessionId(sessionId, status);
316
+ this.teamTaskLastHeartbeatBySession.delete(sessionId);
317
+ this.teamTaskLastProgressLineBySession.delete(sessionId);
318
+ }
319
+
320
+ async onTeamTaskProgress(
321
+ rootSessionId: string,
322
+ agentId: string,
323
+ progress: string,
324
+ options?: { kind?: "heartbeat" | "progress" | "text" },
325
+ ): Promise<void> {
326
+ const key = this.teamTaskQueueKey(rootSessionId, agentId);
327
+ const sessionId = this.teamTaskSessionsByAgent.get(key)?.[0];
328
+ if (!sessionId) return;
329
+ const trimmed = progress.trim();
330
+ if (!trimmed) return;
331
+ const kind = options?.kind ?? "progress";
332
+ if (kind === "heartbeat") {
333
+ const now = Date.now();
334
+ const last = this.teamTaskLastHeartbeatBySession.get(sessionId) ?? 0;
335
+ if (now - last < this.heartbeatLogIntervalMs) {
336
+ return;
337
+ }
338
+ this.teamTaskLastHeartbeatBySession.set(sessionId, now);
339
+ }
340
+ const line =
341
+ kind === "heartbeat"
342
+ ? "[progress] heartbeat"
343
+ : kind === "text"
344
+ ? `[progress] text: ${trimmed}`
345
+ : `[progress] ${trimmed}`;
346
+ if (this.teamTaskLastProgressLineBySession.get(sessionId) === line) return;
347
+ this.teamTaskLastProgressLineBySession.set(sessionId, line);
348
+ }
349
+
350
+ async handleSubAgentStart(
351
+ rootSessionId: string,
352
+ context: SubAgentStartContext,
353
+ ): Promise<void> {
354
+ const subSessionId = await this.upsertSubagentSession({
355
+ agentId: context.subAgentId,
356
+ parentAgentId: context.parentAgentId,
357
+ conversationId: context.conversationId,
358
+ prompt: context.input.task,
359
+ rootSessionId,
360
+ });
361
+ if (!subSessionId) return;
362
+ await this.applySubagentStatusBySessionId(subSessionId, "running");
363
+ }
364
+
365
+ async handleSubAgentEnd(
366
+ rootSessionId: string,
367
+ context: SubAgentEndContext,
368
+ ): Promise<void> {
369
+ const subSessionId = await this.upsertSubagentSession({
370
+ agentId: context.subAgentId,
371
+ parentAgentId: context.parentAgentId,
372
+ conversationId: context.conversationId,
373
+ prompt: context.input.task,
374
+ rootSessionId,
375
+ });
376
+ if (!subSessionId) return;
377
+ if (context.error) {
378
+ await this.applySubagentStatusBySessionId(subSessionId, "failed");
379
+ return;
380
+ }
381
+ const reason = context.result?.finishReason ?? "completed";
382
+ await this.applySubagentStatusBySessionId(
383
+ subSessionId,
384
+ reason === "aborted" ? "cancelled" : "completed",
385
+ );
386
+ }
387
+
388
+ appendSubagentHookAudit(event: HookEventPayload): void {
389
+ const envPath = process.env.CLINE_HOOKS_LOG_PATH?.trim() || undefined;
390
+ const logPath = envPath ?? join(ensureHookLogDir(), "hooks.jsonl");
391
+ appendFileSync(
392
+ logPath,
393
+ `${JSON.stringify({ ts: nowIso(), ...event })}\n`,
394
+ "utf8",
395
+ );
396
+ }
397
+ }
@@ -0,0 +1,176 @@
1
+ import {
2
+ appendFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ unlinkSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { join } from "node:path";
11
+ import type { TeamTeammateSpec } from "@clinebot/shared";
12
+ import { resolveTeamDataDir } from "@clinebot/shared/storage";
13
+ import type { AgentTeamsRuntime, TeamEvent } from "../extensions/tools/team";
14
+ import {
15
+ type PersistedTeamEnvelope,
16
+ reviveTeamStateDates,
17
+ type TeamRuntimeState,
18
+ } from "./session-row";
19
+
20
+ function sanitizeTeamName(name: string): string {
21
+ return name
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9._-]+/g, "-")
24
+ .replace(/^-+|-+$/g, "");
25
+ }
26
+
27
+ export interface FileTeamPersistenceStoreOptions {
28
+ teamName: string;
29
+ baseDir?: string;
30
+ }
31
+
32
+ export class FileTeamPersistenceStore {
33
+ private readonly dirPath: string;
34
+ private readonly statePath: string;
35
+ private readonly taskHistoryPath: string;
36
+ private readonly teammateSpecs: Map<string, TeamTeammateSpec> = new Map();
37
+
38
+ constructor(options: FileTeamPersistenceStoreOptions) {
39
+ const safeTeamName = sanitizeTeamName(options.teamName);
40
+ const baseDir = options.baseDir?.trim() || resolveTeamDataDir();
41
+ this.dirPath = join(baseDir, safeTeamName);
42
+ this.statePath = join(this.dirPath, "state.json");
43
+ this.taskHistoryPath = join(this.dirPath, "task-history.jsonl");
44
+ }
45
+
46
+ loadState(): TeamRuntimeState | undefined {
47
+ if (!existsSync(this.statePath)) {
48
+ return undefined;
49
+ }
50
+ try {
51
+ const raw = readFileSync(this.statePath, "utf8");
52
+ const parsed = JSON.parse(raw) as PersistedTeamEnvelope;
53
+ if (parsed.version !== 1 || !parsed.teamState) {
54
+ return undefined;
55
+ }
56
+ for (const spec of parsed.teammates ?? []) {
57
+ this.teammateSpecs.set(spec.agentId, spec);
58
+ }
59
+ return reviveTeamStateDates(parsed.teamState);
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ getTeammateSpecs(): TeamTeammateSpec[] {
66
+ return Array.from(this.teammateSpecs.values());
67
+ }
68
+
69
+ upsertTeammateSpec(spec: TeamTeammateSpec): void {
70
+ this.teammateSpecs.set(spec.agentId, spec);
71
+ }
72
+
73
+ removeTeammateSpec(agentId: string): void {
74
+ this.teammateSpecs.delete(agentId);
75
+ }
76
+
77
+ persist(runtime: AgentTeamsRuntime): void {
78
+ if (!this.hasPersistableState(runtime)) {
79
+ this.clearPersistedState();
80
+ return;
81
+ }
82
+ this.ensureDir();
83
+ const envelope: PersistedTeamEnvelope = {
84
+ version: 1,
85
+ updatedAt: new Date().toISOString(),
86
+ teamState: runtime.exportState(),
87
+ teammates: Array.from(this.teammateSpecs.values()),
88
+ };
89
+ const tmpPath = `${this.statePath}.tmp`;
90
+ writeFileSync(tmpPath, `${JSON.stringify(envelope, null, 2)}\n`, "utf8");
91
+ renameSync(tmpPath, this.statePath);
92
+ }
93
+
94
+ appendTaskHistory(event: TeamEvent): void {
95
+ let task: Record<string, unknown> = {};
96
+ switch (event.type) {
97
+ case "team_task_updated":
98
+ task = event.task as unknown as Record<string, unknown>;
99
+ break;
100
+ case "team_message":
101
+ task = {
102
+ agentId: event.message.fromAgentId,
103
+ toAgentId: event.message.toAgentId,
104
+ subject: event.message.subject,
105
+ taskId: event.message.taskId,
106
+ };
107
+ break;
108
+ case "team_mission_log":
109
+ task = {
110
+ agentId: event.entry.agentId,
111
+ kind: event.entry.kind,
112
+ summary: event.entry.summary,
113
+ taskId: event.entry.taskId,
114
+ };
115
+ break;
116
+ case "teammate_spawned":
117
+ case "teammate_shutdown":
118
+ case "task_start":
119
+ task = {
120
+ agentId: event.agentId,
121
+ message: "message" in event ? event.message : undefined,
122
+ };
123
+ break;
124
+ case "task_end":
125
+ task = {
126
+ agentId: event.agentId,
127
+ finishReason: event.result?.finishReason,
128
+ error: event.error?.message,
129
+ };
130
+ break;
131
+ case "agent_event":
132
+ task = {
133
+ agentId: event.agentId,
134
+ eventType: event.event.type,
135
+ };
136
+ break;
137
+ }
138
+ this.ensureDir();
139
+ appendFileSync(
140
+ this.taskHistoryPath,
141
+ `${JSON.stringify({
142
+ ts: new Date().toISOString(),
143
+ type: event.type,
144
+ task,
145
+ })}\n`,
146
+ "utf8",
147
+ );
148
+ }
149
+
150
+ private ensureDir(): void {
151
+ if (!existsSync(this.dirPath)) {
152
+ mkdirSync(this.dirPath, { recursive: true });
153
+ }
154
+ }
155
+
156
+ private hasPersistableState(runtime: AgentTeamsRuntime): boolean {
157
+ const state = runtime.exportState();
158
+ if (this.teammateSpecs.size > 0) {
159
+ return true;
160
+ }
161
+ if (state.members.some((member) => member.role === "teammate")) {
162
+ return true;
163
+ }
164
+ return (
165
+ state.tasks.length > 0 ||
166
+ state.mailbox.length > 0 ||
167
+ state.missionLog.length > 0
168
+ );
169
+ }
170
+
171
+ private clearPersistedState(): void {
172
+ if (existsSync(this.statePath)) {
173
+ unlinkSync(this.statePath);
174
+ }
175
+ }
176
+ }