@clinebot/core 0.0.21 → 0.0.23

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 (260) hide show
  1. package/dist/ClineCore.d.ts +110 -0
  2. package/dist/ClineCore.d.ts.map +1 -0
  3. package/dist/account/cline-account-service.d.ts +2 -1
  4. package/dist/account/cline-account-service.d.ts.map +1 -1
  5. package/dist/account/index.d.ts +1 -1
  6. package/dist/account/index.d.ts.map +1 -1
  7. package/dist/account/rpc.d.ts +3 -1
  8. package/dist/account/rpc.d.ts.map +1 -1
  9. package/dist/account/types.d.ts +3 -0
  10. package/dist/account/types.d.ts.map +1 -1
  11. package/dist/agents/plugin-loader.d.ts.map +1 -1
  12. package/dist/agents/plugin-sandbox-bootstrap.js +17 -17
  13. package/dist/auth/client.d.ts +1 -1
  14. package/dist/auth/client.d.ts.map +1 -1
  15. package/dist/auth/cline.d.ts +1 -1
  16. package/dist/auth/cline.d.ts.map +1 -1
  17. package/dist/auth/codex.d.ts +1 -1
  18. package/dist/auth/codex.d.ts.map +1 -1
  19. package/dist/auth/oca.d.ts +1 -1
  20. package/dist/auth/oca.d.ts.map +1 -1
  21. package/dist/auth/utils.d.ts +2 -2
  22. package/dist/auth/utils.d.ts.map +1 -1
  23. package/dist/index.d.ts +50 -5
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +949 -0
  26. package/dist/providers/local-provider-service.d.ts +4 -4
  27. package/dist/providers/local-provider-service.d.ts.map +1 -1
  28. package/dist/runtime/runtime-builder.d.ts +1 -0
  29. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  30. package/dist/runtime/session-runtime.d.ts +2 -1
  31. package/dist/runtime/session-runtime.d.ts.map +1 -1
  32. package/dist/runtime/team-runtime-registry.d.ts +13 -0
  33. package/dist/runtime/team-runtime-registry.d.ts.map +1 -0
  34. package/dist/session/default-session-manager.d.ts +2 -2
  35. package/dist/session/default-session-manager.d.ts.map +1 -1
  36. package/dist/session/rpc-runtime-ensure.d.ts +53 -0
  37. package/dist/session/rpc-runtime-ensure.d.ts.map +1 -0
  38. package/dist/session/session-config-builder.d.ts +2 -3
  39. package/dist/session/session-config-builder.d.ts.map +1 -1
  40. package/dist/session/session-host.d.ts +8 -18
  41. package/dist/session/session-host.d.ts.map +1 -1
  42. package/dist/session/session-manager.d.ts +1 -1
  43. package/dist/session/session-manager.d.ts.map +1 -1
  44. package/dist/session/session-manifest.d.ts +1 -2
  45. package/dist/session/session-manifest.d.ts.map +1 -1
  46. package/dist/session/unified-session-persistence-service.d.ts +2 -2
  47. package/dist/session/unified-session-persistence-service.d.ts.map +1 -1
  48. package/dist/session/utils/helpers.d.ts +1 -1
  49. package/dist/session/utils/helpers.d.ts.map +1 -1
  50. package/dist/session/utils/types.d.ts +1 -1
  51. package/dist/session/utils/types.d.ts.map +1 -1
  52. package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  53. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
  54. package/dist/telemetry/distinct-id.d.ts +2 -0
  55. package/dist/telemetry/distinct-id.d.ts.map +1 -0
  56. package/dist/telemetry/{opentelemetry.d.ts → index.d.ts} +1 -1
  57. package/dist/telemetry/index.d.ts.map +1 -0
  58. package/dist/telemetry/index.js +28 -0
  59. package/dist/tools/constants.d.ts +1 -1
  60. package/dist/tools/constants.d.ts.map +1 -1
  61. package/dist/tools/definitions.d.ts +3 -3
  62. package/dist/tools/definitions.d.ts.map +1 -1
  63. package/dist/tools/executors/apply-patch.d.ts +1 -1
  64. package/dist/tools/executors/apply-patch.d.ts.map +1 -1
  65. package/dist/tools/executors/bash.d.ts +1 -1
  66. package/dist/tools/executors/bash.d.ts.map +1 -1
  67. package/dist/tools/executors/editor.d.ts +1 -1
  68. package/dist/tools/executors/editor.d.ts.map +1 -1
  69. package/dist/tools/executors/file-read.d.ts +1 -1
  70. package/dist/tools/executors/file-read.d.ts.map +1 -1
  71. package/dist/tools/executors/index.d.ts +14 -14
  72. package/dist/tools/executors/index.d.ts.map +1 -1
  73. package/dist/tools/executors/search.d.ts +1 -1
  74. package/dist/tools/executors/search.d.ts.map +1 -1
  75. package/dist/tools/executors/web-fetch.d.ts +1 -1
  76. package/dist/tools/executors/web-fetch.d.ts.map +1 -1
  77. package/dist/tools/helpers.d.ts +1 -1
  78. package/dist/tools/helpers.d.ts.map +1 -1
  79. package/dist/tools/index.d.ts +10 -10
  80. package/dist/tools/index.d.ts.map +1 -1
  81. package/dist/tools/model-tool-routing.d.ts +1 -1
  82. package/dist/tools/model-tool-routing.d.ts.map +1 -1
  83. package/dist/tools/presets.d.ts +1 -1
  84. package/dist/tools/presets.d.ts.map +1 -1
  85. package/dist/types/common.d.ts +17 -8
  86. package/dist/types/common.d.ts.map +1 -1
  87. package/dist/types/config.d.ts +4 -3
  88. package/dist/types/config.d.ts.map +1 -1
  89. package/dist/types/provider-settings.d.ts +1 -1
  90. package/dist/types/provider-settings.d.ts.map +1 -1
  91. package/dist/types.d.ts +5 -2
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/version.d.ts +2 -0
  94. package/dist/version.d.ts.map +1 -0
  95. package/package.json +44 -38
  96. package/src/ClineCore.ts +137 -0
  97. package/src/account/cline-account-service.test.ts +101 -0
  98. package/src/account/cline-account-service.ts +300 -0
  99. package/src/account/featurebase-token.test.ts +175 -0
  100. package/src/account/index.ts +23 -0
  101. package/src/account/rpc.test.ts +63 -0
  102. package/src/account/rpc.ts +185 -0
  103. package/src/account/types.ts +102 -0
  104. package/src/agents/agent-config-loader.test.ts +236 -0
  105. package/src/agents/agent-config-loader.ts +108 -0
  106. package/src/agents/agent-config-parser.ts +198 -0
  107. package/src/agents/hooks-config-loader.test.ts +20 -0
  108. package/src/agents/hooks-config-loader.ts +118 -0
  109. package/src/agents/index.ts +85 -0
  110. package/src/agents/plugin-config-loader.test.ts +140 -0
  111. package/src/agents/plugin-config-loader.ts +97 -0
  112. package/src/agents/plugin-loader.test.ts +210 -0
  113. package/src/agents/plugin-loader.ts +175 -0
  114. package/src/agents/plugin-sandbox-bootstrap.ts +448 -0
  115. package/src/agents/plugin-sandbox.test.ts +296 -0
  116. package/src/agents/plugin-sandbox.ts +341 -0
  117. package/src/agents/unified-config-file-watcher.test.ts +196 -0
  118. package/src/agents/unified-config-file-watcher.ts +483 -0
  119. package/src/agents/user-instruction-config-loader.test.ts +158 -0
  120. package/src/agents/user-instruction-config-loader.ts +438 -0
  121. package/src/auth/client.test.ts +40 -0
  122. package/src/auth/client.ts +25 -0
  123. package/src/auth/cline.test.ts +130 -0
  124. package/src/auth/cline.ts +420 -0
  125. package/src/auth/codex.test.ts +170 -0
  126. package/src/auth/codex.ts +491 -0
  127. package/src/auth/oca.test.ts +215 -0
  128. package/src/auth/oca.ts +573 -0
  129. package/src/auth/server.ts +216 -0
  130. package/src/auth/types.ts +81 -0
  131. package/src/auth/utils.test.ts +128 -0
  132. package/src/auth/utils.ts +247 -0
  133. package/src/chat/chat-schema.ts +82 -0
  134. package/src/index.ts +479 -0
  135. package/src/input/file-indexer.d.ts +11 -0
  136. package/src/input/file-indexer.test.ts +127 -0
  137. package/src/input/file-indexer.ts +327 -0
  138. package/src/input/index.ts +7 -0
  139. package/src/input/mention-enricher.test.ts +85 -0
  140. package/src/input/mention-enricher.ts +122 -0
  141. package/src/mcp/config-loader.test.ts +238 -0
  142. package/src/mcp/config-loader.ts +219 -0
  143. package/src/mcp/index.ts +26 -0
  144. package/src/mcp/manager.test.ts +106 -0
  145. package/src/mcp/manager.ts +262 -0
  146. package/src/mcp/types.ts +88 -0
  147. package/src/providers/local-provider-registry.ts +232 -0
  148. package/src/providers/local-provider-service.test.ts +783 -0
  149. package/src/providers/local-provider-service.ts +471 -0
  150. package/src/runtime/commands.test.ts +98 -0
  151. package/src/runtime/commands.ts +83 -0
  152. package/src/runtime/hook-file-hooks.test.ts +237 -0
  153. package/src/runtime/hook-file-hooks.ts +859 -0
  154. package/src/runtime/index.ts +37 -0
  155. package/src/runtime/rules.ts +34 -0
  156. package/src/runtime/runtime-builder.team-persistence.test.ts +245 -0
  157. package/src/runtime/runtime-builder.test.ts +371 -0
  158. package/src/runtime/runtime-builder.ts +631 -0
  159. package/src/runtime/runtime-parity.test.ts +143 -0
  160. package/src/runtime/sandbox/subprocess-sandbox.ts +231 -0
  161. package/src/runtime/session-runtime.ts +49 -0
  162. package/src/runtime/skills.ts +44 -0
  163. package/src/runtime/team-runtime-registry.ts +46 -0
  164. package/src/runtime/tool-approval.ts +104 -0
  165. package/src/runtime/workflows.test.ts +119 -0
  166. package/src/runtime/workflows.ts +45 -0
  167. package/src/session/default-session-manager.e2e.test.ts +384 -0
  168. package/src/session/default-session-manager.test.ts +1931 -0
  169. package/src/session/default-session-manager.ts +1422 -0
  170. package/src/session/file-session-service.ts +280 -0
  171. package/src/session/index.ts +45 -0
  172. package/src/session/rpc-runtime-ensure.ts +521 -0
  173. package/src/session/rpc-session-service.ts +107 -0
  174. package/src/session/rpc-spawn-lease.test.ts +49 -0
  175. package/src/session/rpc-spawn-lease.ts +122 -0
  176. package/src/session/runtime-oauth-token-manager.test.ts +137 -0
  177. package/src/session/runtime-oauth-token-manager.ts +272 -0
  178. package/src/session/session-agent-events.ts +248 -0
  179. package/src/session/session-artifacts.ts +106 -0
  180. package/src/session/session-config-builder.ts +113 -0
  181. package/src/session/session-graph.ts +92 -0
  182. package/src/session/session-host.test.ts +89 -0
  183. package/src/session/session-host.ts +205 -0
  184. package/src/session/session-manager.ts +69 -0
  185. package/src/session/session-manifest.ts +29 -0
  186. package/src/session/session-service.team-persistence.test.ts +48 -0
  187. package/src/session/session-service.ts +673 -0
  188. package/src/session/session-team-coordination.ts +229 -0
  189. package/src/session/session-telemetry.ts +100 -0
  190. package/src/session/sqlite-rpc-session-backend.ts +303 -0
  191. package/src/session/unified-session-persistence-service.test.ts +85 -0
  192. package/src/session/unified-session-persistence-service.ts +994 -0
  193. package/src/session/utils/helpers.ts +139 -0
  194. package/src/session/utils/types.ts +57 -0
  195. package/src/session/utils/usage.ts +32 -0
  196. package/src/session/workspace-manager.ts +98 -0
  197. package/src/session/workspace-manifest.ts +100 -0
  198. package/src/storage/artifact-store.ts +1 -0
  199. package/src/storage/file-team-store.ts +257 -0
  200. package/src/storage/index.ts +11 -0
  201. package/src/storage/provider-settings-legacy-migration.test.ts +424 -0
  202. package/src/storage/provider-settings-legacy-migration.ts +826 -0
  203. package/src/storage/provider-settings-manager.test.ts +191 -0
  204. package/src/storage/provider-settings-manager.ts +152 -0
  205. package/src/storage/session-store.ts +1 -0
  206. package/src/storage/sqlite-session-store.ts +275 -0
  207. package/src/storage/sqlite-team-store.ts +454 -0
  208. package/src/storage/team-store.ts +40 -0
  209. package/src/team/index.ts +4 -0
  210. package/src/team/projections.ts +285 -0
  211. package/src/telemetry/ITelemetryAdapter.ts +94 -0
  212. package/src/telemetry/LoggerTelemetryAdapter.test.ts +42 -0
  213. package/src/telemetry/LoggerTelemetryAdapter.ts +114 -0
  214. package/src/telemetry/OpenTelemetryAdapter.test.ts +157 -0
  215. package/src/telemetry/OpenTelemetryAdapter.ts +348 -0
  216. package/src/telemetry/OpenTelemetryProvider.test.ts +113 -0
  217. package/src/telemetry/OpenTelemetryProvider.ts +325 -0
  218. package/src/telemetry/TelemetryService.test.ts +134 -0
  219. package/src/telemetry/TelemetryService.ts +141 -0
  220. package/src/telemetry/core-events.ts +400 -0
  221. package/src/telemetry/distinct-id.test.ts +57 -0
  222. package/src/telemetry/distinct-id.ts +58 -0
  223. package/src/telemetry/index.ts +20 -0
  224. package/src/tools/constants.ts +35 -0
  225. package/src/tools/definitions.test.ts +704 -0
  226. package/src/tools/definitions.ts +709 -0
  227. package/src/tools/executors/apply-patch-parser.ts +520 -0
  228. package/src/tools/executors/apply-patch.ts +359 -0
  229. package/src/tools/executors/bash.test.ts +87 -0
  230. package/src/tools/executors/bash.ts +207 -0
  231. package/src/tools/executors/editor.test.ts +35 -0
  232. package/src/tools/executors/editor.ts +219 -0
  233. package/src/tools/executors/file-read.test.ts +49 -0
  234. package/src/tools/executors/file-read.ts +110 -0
  235. package/src/tools/executors/index.ts +87 -0
  236. package/src/tools/executors/search.ts +278 -0
  237. package/src/tools/executors/web-fetch.ts +259 -0
  238. package/src/tools/helpers.ts +130 -0
  239. package/src/tools/index.ts +169 -0
  240. package/src/tools/model-tool-routing.test.ts +86 -0
  241. package/src/tools/model-tool-routing.ts +132 -0
  242. package/src/tools/presets.test.ts +62 -0
  243. package/src/tools/presets.ts +168 -0
  244. package/src/tools/schemas.ts +327 -0
  245. package/src/tools/types.ts +329 -0
  246. package/src/types/common.ts +26 -0
  247. package/src/types/config.ts +86 -0
  248. package/src/types/events.ts +74 -0
  249. package/src/types/index.ts +24 -0
  250. package/src/types/provider-settings.ts +43 -0
  251. package/src/types/sessions.ts +16 -0
  252. package/src/types/storage.ts +64 -0
  253. package/src/types/workspace.ts +7 -0
  254. package/src/types.ts +132 -0
  255. package/src/version.ts +3 -0
  256. package/dist/index.node.d.ts +0 -47
  257. package/dist/index.node.d.ts.map +0 -1
  258. package/dist/index.node.js +0 -948
  259. package/dist/telemetry/opentelemetry.d.ts.map +0 -1
  260. package/dist/telemetry/opentelemetry.js +0 -27
@@ -0,0 +1,248 @@
1
+ import type { AgentEvent } from "@clinebot/agents";
2
+ import {
3
+ captureConversationTurnEvent,
4
+ captureDiffEditFailure,
5
+ captureProviderApiError,
6
+ captureSkillUsed,
7
+ captureTokenUsage,
8
+ captureToolUsage,
9
+ type TelemetryAgentIdentityProperties,
10
+ } from "../telemetry/core-events";
11
+ import type { CoreSessionConfig } from "../types/config";
12
+ import type { CoreSessionEvent } from "../types/events";
13
+ import type { SessionAccumulatedUsage } from "./session-manager";
14
+ import { serializeAgentEvent } from "./utils/helpers";
15
+ import type { ActiveSession } from "./utils/types";
16
+ import { accumulateUsageTotals } from "./utils/usage";
17
+
18
+ export function extractSkillNameFromToolInput(
19
+ input: unknown,
20
+ ): string | undefined {
21
+ if (!input || typeof input !== "object") return undefined;
22
+ const record = input as Record<string, unknown>;
23
+ const skillName = record.skill ?? record.skill_name ?? record.skillName;
24
+ if (typeof skillName !== "string") return undefined;
25
+ const trimmed = skillName.trim();
26
+ return trimmed.length > 0 ? trimmed : undefined;
27
+ }
28
+
29
+ export interface AgentEventContext {
30
+ sessionId: string;
31
+ config: CoreSessionConfig;
32
+ liveSession: ActiveSession | undefined;
33
+ usageBySession: Map<string, SessionAccumulatedUsage>;
34
+ persistMessages: (
35
+ sessionId: string,
36
+ messages: unknown[],
37
+ systemPrompt?: string,
38
+ ) => void;
39
+ emit: (event: CoreSessionEvent) => void;
40
+ }
41
+
42
+ export interface AgentTelemetryContextOverrides {
43
+ agentId?: string;
44
+ conversationId?: string;
45
+ parentAgentId?: string | null;
46
+ createdByAgentId?: string;
47
+ teamId?: string;
48
+ teamName?: string;
49
+ teamRole?: "lead" | "teammate";
50
+ teamAgentId?: string;
51
+ isPrimaryAgentEvent?: boolean;
52
+ }
53
+
54
+ export function extractAgentEventMetadata(event: AgentEvent): {
55
+ agentId?: string;
56
+ conversationId?: string;
57
+ parentAgentId?: string;
58
+ } {
59
+ if (!event || typeof event !== "object") {
60
+ return {};
61
+ }
62
+ const record = event as unknown as Record<string, unknown>;
63
+ return {
64
+ agentId: typeof record.agentId === "string" ? record.agentId : undefined,
65
+ conversationId:
66
+ typeof record.conversationId === "string"
67
+ ? record.conversationId
68
+ : undefined,
69
+ parentAgentId:
70
+ typeof record.parentAgentId === "string"
71
+ ? record.parentAgentId
72
+ : undefined,
73
+ };
74
+ }
75
+
76
+ export function buildTelemetryAgentIdentity(
77
+ context: AgentTelemetryContextOverrides,
78
+ ): TelemetryAgentIdentityProperties | undefined {
79
+ const agentId = context.agentId?.trim();
80
+ if (!agentId) {
81
+ return undefined;
82
+ }
83
+ const parentAgentId = context.parentAgentId?.trim() || undefined;
84
+ const teamRole = context.teamRole;
85
+ let agentKind: TelemetryAgentIdentityProperties["agentKind"] = "root";
86
+ if (teamRole === "teammate") {
87
+ agentKind = "team_teammate";
88
+ } else if (teamRole === "lead") {
89
+ agentKind = "team_lead";
90
+ } else if (parentAgentId) {
91
+ agentKind = "subagent";
92
+ }
93
+ return {
94
+ agentId,
95
+ agentKind,
96
+ conversationId: context.conversationId?.trim() || undefined,
97
+ parentAgentId,
98
+ createdByAgentId:
99
+ context.createdByAgentId?.trim() || parentAgentId || undefined,
100
+ isSubagent: Boolean(parentAgentId),
101
+ teamId: context.teamId?.trim() || undefined,
102
+ teamName: context.teamName?.trim() || undefined,
103
+ teamRole,
104
+ teamAgentId: context.teamAgentId?.trim() || undefined,
105
+ };
106
+ }
107
+
108
+ export function handleAgentEvent(
109
+ ctx: AgentEventContext,
110
+ event: AgentEvent,
111
+ overrides?: AgentTelemetryContextOverrides,
112
+ ): void {
113
+ const { sessionId, config, liveSession, emit } = ctx;
114
+ const telemetry = config.telemetry;
115
+ const teamRuntime = liveSession?.runtime.teamRuntime;
116
+ const isPrimaryAgentEvent = overrides?.isPrimaryAgentEvent ?? true;
117
+ const eventMetadata = extractAgentEventMetadata(event);
118
+ const agentIdentity = buildTelemetryAgentIdentity({
119
+ agentId: overrides?.agentId ?? eventMetadata.agentId,
120
+ conversationId: overrides?.conversationId ?? eventMetadata.conversationId,
121
+ parentAgentId: overrides?.parentAgentId ?? eventMetadata.parentAgentId,
122
+ createdByAgentId: overrides?.createdByAgentId,
123
+ teamId: overrides?.teamId ?? teamRuntime?.getTeamId(),
124
+ teamName: overrides?.teamName ?? teamRuntime?.getTeamName(),
125
+ teamRole: overrides?.teamRole,
126
+ teamAgentId: overrides?.teamAgentId,
127
+ });
128
+
129
+ if (
130
+ event.type === "content_start" &&
131
+ event.contentType === "tool" &&
132
+ event.toolName === "skills"
133
+ ) {
134
+ const skillName = extractSkillNameFromToolInput(event.input);
135
+ if (skillName) {
136
+ captureSkillUsed(telemetry, {
137
+ ulid: sessionId,
138
+ skillName,
139
+ skillSource: "project",
140
+ skillsAvailableGlobal: 0,
141
+ skillsAvailableProject: 0,
142
+ provider: config.providerId,
143
+ modelId: config.modelId,
144
+ ...agentIdentity,
145
+ });
146
+ }
147
+ }
148
+
149
+ if (event.type === "content_end" && event.contentType === "tool") {
150
+ const toolName = event.toolName ?? "unknown";
151
+ const success = !event.error;
152
+ captureToolUsage(telemetry, {
153
+ ulid: sessionId,
154
+ tool: toolName,
155
+ autoApproved: undefined,
156
+ success,
157
+ modelId: config.modelId,
158
+ provider: config.providerId,
159
+ ...agentIdentity,
160
+ });
161
+ if (!success && (toolName === "editor" || toolName === "apply_patch")) {
162
+ captureDiffEditFailure(telemetry, {
163
+ ulid: sessionId,
164
+ modelId: config.modelId,
165
+ provider: config.providerId,
166
+ errorType: event.error,
167
+ ...agentIdentity,
168
+ });
169
+ }
170
+ }
171
+
172
+ if (event.type === "notice" && event.reason === "api_error") {
173
+ captureProviderApiError(telemetry, {
174
+ ulid: sessionId,
175
+ model: config.modelId,
176
+ provider: config.providerId,
177
+ errorMessage: event.message,
178
+ ...agentIdentity,
179
+ });
180
+ }
181
+
182
+ if (event.type === "error") {
183
+ captureProviderApiError(telemetry, {
184
+ ulid: sessionId,
185
+ model: config.modelId,
186
+ provider: config.providerId,
187
+ errorMessage: event.error?.message ?? "unknown error",
188
+ ...agentIdentity,
189
+ });
190
+ }
191
+
192
+ if (
193
+ event.type === "usage" &&
194
+ isPrimaryAgentEvent &&
195
+ liveSession?.turnUsageBaseline
196
+ ) {
197
+ ctx.usageBySession.set(
198
+ sessionId,
199
+ accumulateUsageTotals(liveSession.turnUsageBaseline, {
200
+ inputTokens: event.inputTokens,
201
+ outputTokens: event.outputTokens,
202
+ cacheWriteTokens: event.cacheWriteTokens,
203
+ cacheReadTokens: event.cacheReadTokens,
204
+ totalCost: event.cost,
205
+ }),
206
+ );
207
+ captureConversationTurnEvent(telemetry, {
208
+ ulid: sessionId,
209
+ provider: config.providerId,
210
+ model: config.modelId,
211
+ source: "assistant",
212
+ mode: config.mode,
213
+ ...agentIdentity,
214
+ });
215
+ captureTokenUsage(telemetry, {
216
+ ulid: sessionId,
217
+ tokensIn: event.inputTokens,
218
+ tokensOut: event.outputTokens,
219
+ cacheWriteTokens: event.cacheWriteTokens,
220
+ cacheReadTokens: event.cacheReadTokens,
221
+ totalCost: event.cost,
222
+ model: config.modelId,
223
+ ...agentIdentity,
224
+ });
225
+ }
226
+
227
+ if (event.type === "iteration_end" && isPrimaryAgentEvent) {
228
+ ctx.persistMessages(
229
+ sessionId,
230
+ liveSession?.agent.getMessages() ?? [],
231
+ liveSession?.config.systemPrompt,
232
+ );
233
+ }
234
+
235
+ emit({
236
+ type: "agent_event",
237
+ payload: { sessionId, event },
238
+ });
239
+ emit({
240
+ type: "chunk",
241
+ payload: {
242
+ sessionId,
243
+ stream: "agent",
244
+ chunk: serializeAgentEvent(event),
245
+ ts: Date.now(),
246
+ },
247
+ });
248
+ }
@@ -0,0 +1,106 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ rmdirSync,
6
+ unlinkSync,
7
+ } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+
10
+ export function nowIso(): string {
11
+ return new Date().toISOString();
12
+ }
13
+
14
+ export function unlinkIfExists(path: string | null | undefined): void {
15
+ if (!path || !existsSync(path)) {
16
+ return;
17
+ }
18
+ try {
19
+ unlinkSync(path);
20
+ } catch {
21
+ // Best effort cleanup.
22
+ }
23
+ }
24
+
25
+ export interface SessionArtifactPaths {
26
+ transcriptPath: string;
27
+ hookPath: string;
28
+ messagesPath: string;
29
+ }
30
+
31
+ export class SessionArtifacts {
32
+ constructor(private readonly ensureSessionsDir: () => string) {}
33
+
34
+ public sessionArtifactsDir(sessionId: string): string {
35
+ return join(this.ensureSessionsDir(), sessionId);
36
+ }
37
+
38
+ public ensureSessionArtifactsDir(sessionId: string): string {
39
+ const dir = this.sessionArtifactsDir(sessionId);
40
+ if (!existsSync(dir)) {
41
+ mkdirSync(dir, { recursive: true });
42
+ }
43
+ return dir;
44
+ }
45
+
46
+ public sessionTranscriptPath(sessionId: string): string {
47
+ return join(this.ensureSessionArtifactsDir(sessionId), `${sessionId}.log`);
48
+ }
49
+
50
+ public sessionHookPath(sessionId: string): string {
51
+ return join(
52
+ this.ensureSessionArtifactsDir(sessionId),
53
+ `${sessionId}.hooks.jsonl`,
54
+ );
55
+ }
56
+
57
+ public sessionMessagesPath(sessionId: string): string {
58
+ return join(
59
+ this.ensureSessionArtifactsDir(sessionId),
60
+ `${sessionId}.messages.json`,
61
+ );
62
+ }
63
+
64
+ public sessionManifestPath(sessionId: string, ensureDir = true): string {
65
+ const base = ensureDir
66
+ ? this.ensureSessionArtifactsDir(sessionId)
67
+ : this.sessionArtifactsDir(sessionId);
68
+ return join(base, `${sessionId}.json`);
69
+ }
70
+
71
+ public removeSessionDirIfEmpty(sessionId: string): void {
72
+ let dir = this.sessionArtifactsDir(sessionId);
73
+ const sessionsDir = this.ensureSessionsDir();
74
+ while (dir.startsWith(sessionsDir) && dir !== sessionsDir) {
75
+ if (!existsSync(dir)) {
76
+ dir = dirname(dir);
77
+ continue;
78
+ }
79
+ try {
80
+ if (readdirSync(dir).length > 0) {
81
+ break;
82
+ }
83
+ rmdirSync(dir);
84
+ } catch {
85
+ // Best-effort cleanup.
86
+ break;
87
+ }
88
+ dir = dirname(dir);
89
+ }
90
+ }
91
+
92
+ public subagentArtifactPaths(
93
+ sessionId: string,
94
+ subAgentId: string,
95
+ activeTeamTaskSessionId?: string,
96
+ ): SessionArtifactPaths {
97
+ void subAgentId;
98
+ void activeTeamTaskSessionId;
99
+ const dir = this.ensureSessionArtifactsDir(sessionId);
100
+ return {
101
+ transcriptPath: join(dir, `${sessionId}.log`),
102
+ hookPath: join(dir, `${sessionId}.hooks.jsonl`),
103
+ messagesPath: join(dir, `${sessionId}.messages.json`),
104
+ };
105
+ }
106
+ }
@@ -0,0 +1,113 @@
1
+ import type { ITelemetryService } from "@clinebot/shared";
2
+ import { resolveAndLoadAgentPlugins } from "../agents/plugin-config-loader";
3
+ import {
4
+ createHookAuditHooks,
5
+ createHookConfigFileHooks,
6
+ mergeAgentHooks,
7
+ } from "../runtime/hook-file-hooks";
8
+ import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
9
+ import type { CoreSessionConfig } from "../types/config";
10
+ import {
11
+ type ProviderConfig,
12
+ type ProviderSettings,
13
+ toProviderConfig,
14
+ } from "../types/provider-settings";
15
+ import type { StartSessionInput } from "./session-manager";
16
+ import { hasRuntimeHooks, mergeAgentExtensions } from "./utils/helpers";
17
+
18
+ export function resolveWorkspacePath(config: CoreSessionConfig): string {
19
+ return config.workspaceRoot ?? config.cwd;
20
+ }
21
+
22
+ export async function buildEffectiveConfig(
23
+ input: StartSessionInput,
24
+ hookPath: string,
25
+ sessionId: string,
26
+ defaultTelemetry: ITelemetryService | undefined,
27
+ onPluginEvent?: (event: { name: string; payload?: unknown }) => void,
28
+ ): Promise<{
29
+ config: CoreSessionConfig;
30
+ pluginSandboxShutdown?: () => Promise<void>;
31
+ }> {
32
+ const workspacePath = resolveWorkspacePath(input.config);
33
+
34
+ const fileHooks = createHookConfigFileHooks({
35
+ cwd: input.config.cwd,
36
+ workspacePath,
37
+ rootSessionId: sessionId,
38
+ hookLogPath: hookPath,
39
+ logger: input.config.logger,
40
+ });
41
+ const auditHooks = hasRuntimeHooks(input.config.hooks)
42
+ ? undefined
43
+ : createHookAuditHooks({
44
+ hookLogPath: hookPath,
45
+ rootSessionId: sessionId,
46
+ workspacePath,
47
+ });
48
+ const effectiveHooks = mergeAgentHooks([
49
+ input.config.hooks,
50
+ fileHooks,
51
+ auditHooks,
52
+ ]);
53
+
54
+ const loadedPlugins = await resolveAndLoadAgentPlugins({
55
+ pluginPaths: input.config.pluginPaths,
56
+ workspacePath,
57
+ cwd: input.config.cwd,
58
+ onEvent: onPluginEvent,
59
+ });
60
+ const effectiveExtensions = mergeAgentExtensions(
61
+ input.config.extensions,
62
+ loadedPlugins.extensions,
63
+ );
64
+
65
+ return {
66
+ config: {
67
+ ...input.config,
68
+ hooks: effectiveHooks,
69
+ extensions: effectiveExtensions,
70
+ telemetry: input.config.telemetry ?? defaultTelemetry,
71
+ },
72
+ pluginSandboxShutdown: loadedPlugins.shutdown,
73
+ };
74
+ }
75
+
76
+ export function buildResolvedProviderConfig(
77
+ config: CoreSessionConfig,
78
+ providerSettingsManager: ProviderSettingsManager,
79
+ resolveReasoningFn: (
80
+ config: CoreSessionConfig,
81
+ storedReasoning: ProviderSettings["reasoning"],
82
+ ) => ProviderSettings["reasoning"],
83
+ ): ProviderConfig {
84
+ const stored = providerSettingsManager.getProviderSettings(config.providerId);
85
+ const settings: ProviderSettings = {
86
+ ...(stored ?? {}),
87
+ provider: config.providerId,
88
+ model: config.modelId,
89
+ apiKey: config.apiKey ?? stored?.apiKey,
90
+ baseUrl: config.baseUrl ?? stored?.baseUrl,
91
+ headers: config.headers ?? stored?.headers,
92
+ reasoning: resolveReasoningFn(config, stored?.reasoning),
93
+ };
94
+ const providerConfig = toProviderConfig(settings);
95
+ if (config.knownModels) {
96
+ providerConfig.knownModels = config.knownModels;
97
+ }
98
+ return providerConfig;
99
+ }
100
+
101
+ export function resolveReasoningSettings(
102
+ config: CoreSessionConfig,
103
+ storedReasoning: ProviderSettings["reasoning"],
104
+ ): ProviderSettings["reasoning"] {
105
+ const hasThinking = typeof config.thinking === "boolean";
106
+ const hasEffort = typeof config.reasoningEffort === "string";
107
+ if (!hasThinking && !hasEffort) return storedReasoning;
108
+ return {
109
+ ...(storedReasoning ?? {}),
110
+ ...(hasThinking ? { enabled: config.thinking } : {}),
111
+ ...(hasEffort ? { effort: config.reasoningEffort } : {}),
112
+ };
113
+ }
@@ -0,0 +1,92 @@
1
+ import type { HookEventPayload } from "@clinebot/agents";
2
+ import { nanoid } from "nanoid";
3
+ import type { SessionStatus } from "../types/common";
4
+
5
+ export function sanitizeSessionToken(value: string): string {
6
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
7
+ }
8
+
9
+ export function makeSubSessionId(
10
+ rootSessionId: string,
11
+ agentId: string,
12
+ ): string {
13
+ const root = sanitizeSessionToken(rootSessionId);
14
+ const agent = sanitizeSessionToken(agentId);
15
+ const joined = `${root}__${agent}`;
16
+ return joined.length > 180 ? joined.slice(0, 180) : joined;
17
+ }
18
+
19
+ export function makeTeamTaskSubSessionId(
20
+ rootSessionId: string,
21
+ agentId: string,
22
+ ): string {
23
+ const root = sanitizeSessionToken(rootSessionId);
24
+ const agent = sanitizeSessionToken(agentId);
25
+ return `${root}__teamtask__${agent}__${nanoid(6)}`;
26
+ }
27
+
28
+ export function parseTeamTaskSubSessionId(
29
+ sessionId: string,
30
+ ): { rootSessionId: string; agentId: string; teamTaskId: string } | null {
31
+ const marker = "__teamtask__";
32
+ const markerIndex = sessionId.indexOf(marker);
33
+ if (markerIndex <= 0) {
34
+ return null;
35
+ }
36
+ const rootSessionId = sessionId.slice(0, markerIndex);
37
+ const remainder = sessionId.slice(markerIndex + marker.length);
38
+ const lastSeparator = remainder.lastIndexOf("__");
39
+ if (lastSeparator <= 0) {
40
+ return null;
41
+ }
42
+ const agentId = remainder.slice(0, lastSeparator);
43
+ const teamTaskId = remainder.slice(lastSeparator + 2);
44
+ if (!rootSessionId || !agentId || !teamTaskId) {
45
+ return null;
46
+ }
47
+ return { rootSessionId, agentId, teamTaskId };
48
+ }
49
+
50
+ export function parseSubSessionId(
51
+ sessionId: string,
52
+ ): { rootSessionId: string; agentId: string } | null {
53
+ if (parseTeamTaskSubSessionId(sessionId)) {
54
+ return null;
55
+ }
56
+ const separator = "__";
57
+ const separatorIndex = sessionId.indexOf(separator);
58
+ if (separatorIndex <= 0) {
59
+ return null;
60
+ }
61
+ const rootSessionId = sessionId.slice(0, separatorIndex);
62
+ const agentId = sessionId.slice(separatorIndex + separator.length);
63
+ if (!rootSessionId || !agentId) {
64
+ return null;
65
+ }
66
+ return { rootSessionId, agentId };
67
+ }
68
+
69
+ export function deriveSubsessionStatus(event: HookEventPayload): SessionStatus {
70
+ switch (event.hookName) {
71
+ case "agent_end":
72
+ return "completed";
73
+ case "agent_error":
74
+ return "failed";
75
+ case "session_shutdown": {
76
+ const reason = String(event.reason ?? "").toLowerCase();
77
+ if (
78
+ reason.includes("cancel") ||
79
+ reason.includes("abort") ||
80
+ reason.includes("interrupt")
81
+ ) {
82
+ return "cancelled";
83
+ }
84
+ if (reason.includes("fail") || reason.includes("error")) {
85
+ return "failed";
86
+ }
87
+ return "completed";
88
+ }
89
+ default:
90
+ return "running";
91
+ }
92
+ }
@@ -0,0 +1,89 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { FileSessionService } from "./file-session-service";
3
+
4
+ const sqliteInitMock = vi.hoisted(() => vi.fn());
5
+ const ensureRpcRuntimeAddressMock = vi.hoisted(() => vi.fn());
6
+ const resolveRpcOwnerContextMock = vi.hoisted(() =>
7
+ vi.fn(() => ({
8
+ ownerId: "core-owner",
9
+ buildId: "core-build",
10
+ discoveryPath: "/tmp/rpc-owner.json",
11
+ })),
12
+ );
13
+ const rpcHealthByAddress = vi.hoisted(() => new Map<string, unknown>());
14
+
15
+ vi.mock("@clinebot/rpc", () => ({
16
+ RPC_BUILD_VERSION: "rpc-build-test",
17
+ getRpcServerDefaultAddress: () => "ws://127.0.0.1:0",
18
+ getRpcServerHealth: vi.fn(async (address: string) => {
19
+ return rpcHealthByAddress.get(address);
20
+ }),
21
+ }));
22
+
23
+ vi.mock("./rpc-runtime-ensure", () => ({
24
+ ensureRpcRuntimeAddress: ensureRpcRuntimeAddressMock,
25
+ resolveRpcOwnerContext: resolveRpcOwnerContextMock,
26
+ }));
27
+
28
+ vi.mock("../storage/sqlite-session-store", () => ({
29
+ SqliteSessionStore: class {
30
+ init(): void {
31
+ sqliteInitMock();
32
+ }
33
+ },
34
+ }));
35
+
36
+ describe("resolveSessionBackend", () => {
37
+ afterEach(() => {
38
+ sqliteInitMock.mockReset();
39
+ ensureRpcRuntimeAddressMock.mockReset();
40
+ resolveRpcOwnerContextMock.mockClear();
41
+ rpcHealthByAddress.clear();
42
+ vi.resetModules();
43
+ });
44
+
45
+ it("falls back to file session storage when sqlite initialization fails", async () => {
46
+ const { resolveSessionBackend } = await import("./session-host");
47
+ sqliteInitMock.mockImplementation(() => {
48
+ throw new Error("sqlite unavailable");
49
+ });
50
+
51
+ const backend = await resolveSessionBackend({ backendMode: "local" });
52
+ expect(backend).toBeInstanceOf(FileSessionService);
53
+ });
54
+
55
+ it("connects to the ensured rpc address when the sidecar relocates ports", async () => {
56
+ const { resolveSessionBackend } = await import("./session-host");
57
+ ensureRpcRuntimeAddressMock.mockResolvedValue({
58
+ address: "ws://127.0.0.1:12345",
59
+ action: "new-port",
60
+ owner: {
61
+ ownerId: "core-owner",
62
+ buildId: "core-build",
63
+ discoveryPath: "/tmp/rpc-owner.json",
64
+ },
65
+ });
66
+ rpcHealthByAddress.set("ws://127.0.0.1:12345", {
67
+ running: true,
68
+ serverId: "rpc-server",
69
+ });
70
+
71
+ const backend = await resolveSessionBackend({
72
+ backendMode: "auto",
73
+ rpc: {
74
+ address: "ws://127.0.0.1:4317",
75
+ connectAttempts: 1,
76
+ connectDelayMs: 0,
77
+ },
78
+ });
79
+
80
+ expect(ensureRpcRuntimeAddressMock).toHaveBeenCalledWith(
81
+ "ws://127.0.0.1:4317",
82
+ expect.objectContaining({
83
+ spawnIfNeeded: expect.any(Function),
84
+ resolveOwner: expect.any(Function),
85
+ }),
86
+ );
87
+ expect(backend).not.toBeInstanceOf(FileSessionService);
88
+ });
89
+ });