@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,139 @@
1
+ import type { AgentConfig, AgentEvent, AgentResult } from "@clinebot/agents";
2
+ import type * as LlmsProviders from "@clinebot/llms/providers";
3
+ import type { SessionSource } from "../../types/common";
4
+ import type { SessionRecord } from "../../types/sessions";
5
+ import type { SessionRow } from "../session-service";
6
+ import type { StoredMessageWithMetadata } from "./types";
7
+
8
+ const WORKSPACE_CONFIGURATION_MARKER = "# Workspace Configuration";
9
+
10
+ export function extractWorkspaceMetadataFromSystemPrompt(
11
+ systemPrompt: string,
12
+ ): string | undefined {
13
+ const markerIndex = systemPrompt.lastIndexOf(WORKSPACE_CONFIGURATION_MARKER);
14
+ if (markerIndex < 0) {
15
+ return undefined;
16
+ }
17
+ const metadata = systemPrompt.slice(markerIndex).trim();
18
+ return metadata.length > 0 ? metadata : undefined;
19
+ }
20
+
21
+ export function hasRuntimeHooks(hooks: AgentConfig["hooks"]): boolean {
22
+ if (!hooks) {
23
+ return false;
24
+ }
25
+ return Object.values(hooks).some((value) => typeof value === "function");
26
+ }
27
+
28
+ export function mergeAgentExtensions(
29
+ explicitExtensions: AgentConfig["extensions"] | undefined,
30
+ loadedExtensions: AgentConfig["extensions"] | undefined,
31
+ ): AgentConfig["extensions"] {
32
+ const merged = [...(explicitExtensions ?? []), ...(loadedExtensions ?? [])];
33
+ if (merged.length === 0) {
34
+ return undefined;
35
+ }
36
+ const deduped: NonNullable<AgentConfig["extensions"]> = [];
37
+ const seenNames = new Set<string>();
38
+ for (const extension of merged) {
39
+ if (seenNames.has(extension.name)) {
40
+ continue;
41
+ }
42
+ seenNames.add(extension.name);
43
+ deduped.push(extension);
44
+ }
45
+ return deduped;
46
+ }
47
+
48
+ export function serializeAgentEvent(event: AgentEvent): string {
49
+ return JSON.stringify(event, (_key, value) => {
50
+ if (value instanceof Error) {
51
+ return {
52
+ name: value.name,
53
+ message: value.message,
54
+ stack: value.stack,
55
+ };
56
+ }
57
+ return value;
58
+ });
59
+ }
60
+
61
+ export function withLatestAssistantTurnMetadata(
62
+ messages: LlmsProviders.Message[],
63
+ result: AgentResult,
64
+ previousMessages: LlmsProviders.MessageWithMetadata[] = [],
65
+ ): StoredMessageWithMetadata[] {
66
+ const next = messages.map((message, index) => {
67
+ const previous = previousMessages[index];
68
+ const sameMessage =
69
+ previous?.role === message.role &&
70
+ JSON.stringify(previous.content) === JSON.stringify(message.content);
71
+ return sameMessage
72
+ ? ({
73
+ ...previous,
74
+ ...message,
75
+ } as StoredMessageWithMetadata)
76
+ : ({ ...message } as StoredMessageWithMetadata);
77
+ });
78
+ const assistantIndex = [...next]
79
+ .reverse()
80
+ .findIndex((message) => message.role === "assistant");
81
+ if (assistantIndex === -1) {
82
+ return next;
83
+ }
84
+
85
+ const targetIndex = next.length - 1 - assistantIndex;
86
+ const target = next[targetIndex];
87
+ const usage = result.usage;
88
+ next[targetIndex] = {
89
+ ...target,
90
+ providerId: target.providerId ?? result.model.provider,
91
+ modelId: target.modelId ?? result.model.id,
92
+ modelInfo: target.modelInfo ?? {
93
+ id: result.model.id,
94
+ provider: result.model.provider,
95
+ },
96
+ metrics: {
97
+ ...(target.metrics ?? {}),
98
+ inputTokens: usage.inputTokens,
99
+ outputTokens: usage.outputTokens,
100
+ cacheReadTokens: usage.cacheReadTokens,
101
+ cacheWriteTokens: usage.cacheWriteTokens,
102
+ cost: usage.totalCost,
103
+ },
104
+ ts: target.ts ?? result.endedAt.getTime(),
105
+ };
106
+ return next;
107
+ }
108
+
109
+ export function toSessionRecord(row: SessionRow): SessionRecord {
110
+ return {
111
+ sessionId: row.sessionId,
112
+ source: row.source as SessionSource,
113
+ pid: row.pid,
114
+ startedAt: row.startedAt,
115
+ endedAt: row.endedAt ?? null,
116
+ exitCode: row.exitCode ?? null,
117
+ status: row.status,
118
+ interactive: row.interactive,
119
+ provider: row.provider,
120
+ model: row.model,
121
+ cwd: row.cwd,
122
+ workspaceRoot: row.workspaceRoot,
123
+ teamName: row.teamName ?? undefined,
124
+ enableTools: row.enableTools,
125
+ enableSpawn: row.enableSpawn,
126
+ enableTeams: row.enableTeams,
127
+ parentSessionId: row.parentSessionId ?? undefined,
128
+ parentAgentId: row.parentAgentId ?? undefined,
129
+ agentId: row.agentId ?? undefined,
130
+ conversationId: row.conversationId ?? undefined,
131
+ isSubagent: row.isSubagent,
132
+ prompt: row.prompt ?? undefined,
133
+ metadata: row.metadata ?? undefined,
134
+ transcriptPath: row.transcriptPath,
135
+ hookPath: row.hookPath,
136
+ messagesPath: row.messagesPath ?? undefined,
137
+ updatedAt: row.updatedAt,
138
+ };
139
+ }
@@ -0,0 +1,57 @@
1
+ import type { Agent } from "@clinebot/agents";
2
+ import type * as LlmsProviders from "@clinebot/llms/providers";
3
+ import type { BuiltRuntime } from "../../runtime/session-runtime";
4
+ import type { SessionSource } from "../../types/common";
5
+ import type { CoreSessionConfig } from "../../types/config";
6
+ import type { SessionAccumulatedUsage } from "../session-manager";
7
+ import type { RootSessionArtifacts } from "../session-service";
8
+
9
+ export type ActiveSession = {
10
+ sessionId: string;
11
+ config: CoreSessionConfig;
12
+ artifacts?: RootSessionArtifacts;
13
+ source: SessionSource;
14
+ startedAt: string;
15
+ pendingPrompt?: string;
16
+ runtime: BuiltRuntime;
17
+ agent: Agent;
18
+ started: boolean;
19
+ aborting: boolean;
20
+ interactive: boolean;
21
+ persistedMessages?: LlmsProviders.MessageWithMetadata[];
22
+ activeTeamRunIds: Set<string>;
23
+ pendingTeamRunUpdates: TeamRunUpdate[];
24
+ teamRunWaiters: Array<() => void>;
25
+ pendingPrompts: PendingPrompt[];
26
+ drainingPendingPrompts: boolean;
27
+ pluginSandboxShutdown?: () => Promise<void>;
28
+ turnUsageBaseline?: SessionAccumulatedUsage;
29
+ };
30
+
31
+ export type PendingPrompt = {
32
+ id: string;
33
+ prompt: string;
34
+ delivery: "queue" | "steer";
35
+ userImages?: string[];
36
+ userFiles?: string[];
37
+ };
38
+
39
+ export type TeamRunUpdate = {
40
+ runId: string;
41
+ agentId: string;
42
+ taskId?: string;
43
+ status: "completed" | "failed" | "cancelled" | "interrupted";
44
+ error?: string;
45
+ iterations?: number;
46
+ };
47
+
48
+ export type StoredMessageWithMetadata = LlmsProviders.MessageWithMetadata & {
49
+ providerId?: string;
50
+ modelId?: string;
51
+ };
52
+
53
+ export type PreparedTurnInput = {
54
+ prompt: string;
55
+ userImages?: string[];
56
+ userFiles?: string[];
57
+ };
@@ -0,0 +1,32 @@
1
+ import type { SessionAccumulatedUsage } from "../session-manager";
2
+
3
+ export function createInitialAccumulatedUsage(): SessionAccumulatedUsage {
4
+ return {
5
+ inputTokens: 0,
6
+ outputTokens: 0,
7
+ cacheReadTokens: 0,
8
+ cacheWriteTokens: 0,
9
+ totalCost: 0,
10
+ };
11
+ }
12
+
13
+ export function accumulateUsageTotals(
14
+ baseline: SessionAccumulatedUsage,
15
+ usage: {
16
+ inputTokens?: number;
17
+ outputTokens?: number;
18
+ cacheReadTokens?: number;
19
+ cacheWriteTokens?: number;
20
+ totalCost?: number;
21
+ },
22
+ ): SessionAccumulatedUsage {
23
+ return {
24
+ inputTokens: baseline.inputTokens + Math.max(0, usage.inputTokens ?? 0),
25
+ outputTokens: baseline.outputTokens + Math.max(0, usage.outputTokens ?? 0),
26
+ cacheReadTokens:
27
+ baseline.cacheReadTokens + Math.max(0, usage.cacheReadTokens ?? 0),
28
+ cacheWriteTokens:
29
+ baseline.cacheWriteTokens + Math.max(0, usage.cacheWriteTokens ?? 0),
30
+ totalCost: baseline.totalCost + Math.max(0, usage.totalCost ?? 0),
31
+ };
32
+ }
@@ -0,0 +1,98 @@
1
+ import type { WorkspaceInfo } from "../types/workspace";
2
+ import {
3
+ generateWorkspaceInfo,
4
+ normalizeWorkspacePath,
5
+ upsertWorkspaceInfo,
6
+ type WorkspaceManifest,
7
+ WorkspaceManifestSchema,
8
+ } from "./workspace-manifest";
9
+
10
+ export interface WorkspaceManagerEvent {
11
+ type: "workspace_added" | "workspace_switched";
12
+ workspace: WorkspaceInfo;
13
+ }
14
+
15
+ export interface WorkspaceManager {
16
+ addWorkspacePath(workspacePath: string): Promise<WorkspaceInfo>;
17
+ switchWorkspace(workspacePath: string): Promise<WorkspaceInfo>;
18
+ subscribe(listener: (event: WorkspaceManagerEvent) => void): () => void;
19
+ getCurrentWorkspace(): WorkspaceInfo | undefined;
20
+ getWorkspace(workspacePath: string): WorkspaceInfo | undefined;
21
+ listWorkspaces(): WorkspaceInfo[];
22
+ getManifest(): WorkspaceManifest;
23
+ }
24
+
25
+ export class InMemoryWorkspaceManager implements WorkspaceManager {
26
+ private manifest: WorkspaceManifest;
27
+ private readonly listeners = new Set<
28
+ (event: WorkspaceManagerEvent) => void
29
+ >();
30
+
31
+ constructor(manifest?: WorkspaceManifest) {
32
+ this.manifest = WorkspaceManifestSchema.parse(
33
+ manifest ?? { workspaces: {} },
34
+ );
35
+ }
36
+
37
+ async addWorkspacePath(workspacePath: string): Promise<WorkspaceInfo> {
38
+ const info = await generateWorkspaceInfo(workspacePath);
39
+ this.manifest = upsertWorkspaceInfo(this.manifest, info);
40
+ this.emit({ type: "workspace_added", workspace: info });
41
+ return info;
42
+ }
43
+
44
+ async switchWorkspace(workspacePath: string): Promise<WorkspaceInfo> {
45
+ const normalizedPath = normalizeWorkspacePath(workspacePath);
46
+ const existing = this.manifest.workspaces[normalizedPath];
47
+ if (existing) {
48
+ this.manifest = WorkspaceManifestSchema.parse({
49
+ ...this.manifest,
50
+ currentWorkspacePath: normalizedPath,
51
+ });
52
+ this.emit({ type: "workspace_switched", workspace: existing });
53
+ return existing;
54
+ }
55
+
56
+ const added = await this.addWorkspacePath(normalizedPath);
57
+ this.manifest = WorkspaceManifestSchema.parse({
58
+ ...this.manifest,
59
+ currentWorkspacePath: added.rootPath,
60
+ });
61
+ this.emit({ type: "workspace_switched", workspace: added });
62
+ return added;
63
+ }
64
+
65
+ subscribe(listener: (event: WorkspaceManagerEvent) => void): () => void {
66
+ this.listeners.add(listener);
67
+ return () => {
68
+ this.listeners.delete(listener);
69
+ };
70
+ }
71
+
72
+ getCurrentWorkspace(): WorkspaceInfo | undefined {
73
+ const currentPath = this.manifest.currentWorkspacePath;
74
+ if (!currentPath) {
75
+ return undefined;
76
+ }
77
+ return this.manifest.workspaces[currentPath];
78
+ }
79
+
80
+ getWorkspace(workspacePath: string): WorkspaceInfo | undefined {
81
+ const normalizedPath = normalizeWorkspacePath(workspacePath);
82
+ return this.manifest.workspaces[normalizedPath];
83
+ }
84
+
85
+ listWorkspaces(): WorkspaceInfo[] {
86
+ return Object.values(this.manifest.workspaces);
87
+ }
88
+
89
+ getManifest(): WorkspaceManifest {
90
+ return this.manifest;
91
+ }
92
+
93
+ private emit(event: WorkspaceManagerEvent): void {
94
+ for (const listener of this.listeners) {
95
+ listener(event);
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,100 @@
1
+ import { basename, resolve } from "node:path";
2
+ import simpleGit from "simple-git";
3
+ import { z } from "zod";
4
+ import type { WorkspaceInfo } from "../types/workspace";
5
+
6
+ export const WorkspaceInfoSchema = z.object({
7
+ rootPath: z.string().min(1),
8
+ hint: z.string().min(1).optional(),
9
+ associatedRemoteUrls: z.array(z.string().min(1)).optional(),
10
+ latestGitCommitHash: z.string().min(1).optional(),
11
+ latestGitBranchName: z.string().min(1).optional(),
12
+ });
13
+
14
+ export const WorkspaceManifestSchema = z.object({
15
+ currentWorkspacePath: z.string().min(1).optional(),
16
+ workspaces: z.record(z.string().min(1), WorkspaceInfoSchema),
17
+ });
18
+
19
+ export type WorkspaceManifest = z.infer<typeof WorkspaceManifestSchema>;
20
+
21
+ export function emptyWorkspaceManifest(): WorkspaceManifest {
22
+ return { workspaces: {} };
23
+ }
24
+
25
+ export function normalizeWorkspacePath(workspacePath: string): string {
26
+ return resolve(workspacePath);
27
+ }
28
+
29
+ export async function generateWorkspaceInfo(
30
+ workspacePath: string,
31
+ ): Promise<WorkspaceInfo> {
32
+ const rootPath = normalizeWorkspacePath(workspacePath);
33
+ const info: WorkspaceInfo = {
34
+ rootPath,
35
+ hint: basename(rootPath),
36
+ };
37
+
38
+ try {
39
+ const git = simpleGit({ baseDir: rootPath });
40
+ const isRepo = await git.checkIsRepo();
41
+ if (!isRepo) {
42
+ return info;
43
+ }
44
+
45
+ const remotes = await git.getRemotes(true);
46
+ if (remotes.length > 0) {
47
+ const associatedRemoteUrls = remotes.map((remote) => {
48
+ const remoteUrl = remote.refs.fetch || remote.refs.push;
49
+ return `${remote.name}: ${remoteUrl}`;
50
+ });
51
+ info.associatedRemoteUrls = associatedRemoteUrls;
52
+ }
53
+
54
+ const latestGitCommitHash = (await git.revparse(["HEAD"])).trim();
55
+ if (latestGitCommitHash.length > 0) {
56
+ info.latestGitCommitHash = latestGitCommitHash;
57
+ }
58
+
59
+ const latestGitBranchName = (await git.branch()).current.trim();
60
+ if (latestGitBranchName.length > 0) {
61
+ info.latestGitBranchName = latestGitBranchName;
62
+ }
63
+ } catch {
64
+ // Non-git workspaces keep only path + hint.
65
+ }
66
+
67
+ return info;
68
+ }
69
+
70
+ export function upsertWorkspaceInfo(
71
+ manifest: WorkspaceManifest,
72
+ info: WorkspaceInfo,
73
+ ): WorkspaceManifest {
74
+ const nextManifest: WorkspaceManifest = {
75
+ ...manifest,
76
+ workspaces: {
77
+ ...manifest.workspaces,
78
+ [info.rootPath]: info,
79
+ },
80
+ };
81
+ if (!nextManifest.currentWorkspacePath) {
82
+ nextManifest.currentWorkspacePath = info.rootPath;
83
+ }
84
+ return WorkspaceManifestSchema.parse(nextManifest);
85
+ }
86
+
87
+ export async function buildWorkspaceMetadata(cwd: string): Promise<string> {
88
+ const workspaceInfo = await generateWorkspaceInfo(cwd);
89
+ const workspaceConfig = {
90
+ workspaces: {
91
+ [workspaceInfo.rootPath]: {
92
+ hint: workspaceInfo.hint,
93
+ associatedRemoteUrls: workspaceInfo.associatedRemoteUrls,
94
+ latestGitCommitHash: workspaceInfo.latestGitCommitHash,
95
+ latestGitBranchName: workspaceInfo.latestGitBranchName,
96
+ },
97
+ },
98
+ };
99
+ return `# Workspace Configuration\n${JSON.stringify(workspaceConfig, null, 2)}`;
100
+ }
@@ -0,0 +1 @@
1
+ export type { ArtifactStore } from "../types/storage";
@@ -0,0 +1,257 @@
1
+ import {
2
+ appendFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ renameSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { join } from "node:path";
11
+ import type {
12
+ TeamEvent,
13
+ TeamRuntimeState,
14
+ TeamTeammateSpec,
15
+ } from "@clinebot/agents";
16
+ import { resolveTeamDataDir } from "@clinebot/shared/storage";
17
+ import type { TeamStore } from "../types/storage";
18
+
19
+ function nowIso(): string {
20
+ return new Date().toISOString();
21
+ }
22
+
23
+ function sanitizeTeamName(name: string): string {
24
+ return name
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9._-]+/g, "-")
27
+ .replace(/^-+|-+$/g, "");
28
+ }
29
+
30
+ function reviveTeamRuntimeStateDates(
31
+ state: TeamRuntimeState,
32
+ ): TeamRuntimeState {
33
+ return {
34
+ ...state,
35
+ tasks: state.tasks.map((task) => ({
36
+ ...task,
37
+ createdAt: new Date(task.createdAt),
38
+ updatedAt: new Date(task.updatedAt),
39
+ })),
40
+ mailbox: state.mailbox.map((message) => ({
41
+ ...message,
42
+ sentAt: new Date(message.sentAt),
43
+ readAt: message.readAt ? new Date(message.readAt) : undefined,
44
+ })),
45
+ missionLog: state.missionLog.map((entry) => ({
46
+ ...entry,
47
+ ts: new Date(entry.ts),
48
+ })),
49
+ runs: (state.runs ?? []).map((run) => ({
50
+ ...run,
51
+ startedAt: new Date(run.startedAt),
52
+ endedAt: run.endedAt ? new Date(run.endedAt) : undefined,
53
+ nextAttemptAt: run.nextAttemptAt
54
+ ? new Date(run.nextAttemptAt)
55
+ : undefined,
56
+ heartbeatAt: run.heartbeatAt ? new Date(run.heartbeatAt) : undefined,
57
+ })),
58
+ outcomes: (state.outcomes ?? []).map((outcome) => ({
59
+ ...outcome,
60
+ createdAt: new Date(outcome.createdAt),
61
+ finalizedAt: outcome.finalizedAt
62
+ ? new Date(outcome.finalizedAt)
63
+ : undefined,
64
+ })),
65
+ outcomeFragments: (state.outcomeFragments ?? []).map((fragment) => ({
66
+ ...fragment,
67
+ createdAt: new Date(fragment.createdAt),
68
+ reviewedAt: fragment.reviewedAt
69
+ ? new Date(fragment.reviewedAt)
70
+ : undefined,
71
+ })),
72
+ };
73
+ }
74
+
75
+ interface PersistedTeamEnvelope {
76
+ version: 1;
77
+ updatedAt: string;
78
+ teamState: TeamRuntimeState;
79
+ teammates: TeamTeammateSpec[];
80
+ }
81
+
82
+ export interface FileTeamStoreOptions {
83
+ teamDir?: string;
84
+ }
85
+
86
+ export interface TeamRuntimeLoadResult {
87
+ state?: TeamRuntimeState;
88
+ teammates: TeamTeammateSpec[];
89
+ interruptedRunIds: string[];
90
+ }
91
+
92
+ export class FileTeamStore implements TeamStore {
93
+ private readonly teamDirPath: string;
94
+
95
+ constructor(options: FileTeamStoreOptions = {}) {
96
+ this.teamDirPath = options.teamDir ?? resolveTeamDataDir();
97
+ }
98
+
99
+ init(): void {
100
+ this.ensureTeamDir();
101
+ }
102
+
103
+ listTeamNames(): string[] {
104
+ if (!existsSync(this.teamDirPath)) {
105
+ return [];
106
+ }
107
+ return readdirSync(this.teamDirPath, { withFileTypes: true })
108
+ .filter((entry) => entry.isDirectory())
109
+ .filter((entry) => existsSync(this.statePath(entry.name)))
110
+ .map((entry) => entry.name)
111
+ .sort();
112
+ }
113
+
114
+ readState(teamName: string): TeamRuntimeState | undefined {
115
+ const envelope = this.readEnvelope(teamName);
116
+ return envelope?.teamState
117
+ ? reviveTeamRuntimeStateDates(envelope.teamState)
118
+ : undefined;
119
+ }
120
+
121
+ readHistory(teamName: string, limit = 200): unknown[] {
122
+ const historyPath = this.historyPath(teamName);
123
+ if (!existsSync(historyPath)) {
124
+ return [];
125
+ }
126
+ return readFileSync(historyPath, "utf8")
127
+ .split("\n")
128
+ .map((line) => line.trim())
129
+ .filter(Boolean)
130
+ .map((line) => {
131
+ try {
132
+ return JSON.parse(line) as unknown;
133
+ } catch {
134
+ return undefined;
135
+ }
136
+ })
137
+ .filter((item): item is unknown => item !== undefined)
138
+ .reverse()
139
+ .slice(0, limit);
140
+ }
141
+
142
+ loadRuntime(teamName: string): TeamRuntimeLoadResult {
143
+ const envelope = this.readEnvelope(teamName);
144
+ const interruptedRunIds = this.markInProgressRunsInterrupted(
145
+ teamName,
146
+ "runtime_recovered",
147
+ );
148
+ return {
149
+ state: envelope?.teamState
150
+ ? reviveTeamRuntimeStateDates(envelope.teamState)
151
+ : undefined,
152
+ teammates: envelope?.teammates ?? [],
153
+ interruptedRunIds,
154
+ };
155
+ }
156
+
157
+ handleTeamEvent(teamName: string, event: TeamEvent): void {
158
+ this.ensureTeamSubdir(teamName);
159
+ appendFileSync(
160
+ this.historyPath(teamName),
161
+ `${JSON.stringify({ ts: nowIso(), eventType: event.type, payload: event })}\n`,
162
+ "utf8",
163
+ );
164
+ }
165
+
166
+ persistRuntime(
167
+ teamName: string,
168
+ state: TeamRuntimeState,
169
+ teammates: TeamTeammateSpec[],
170
+ ): void {
171
+ this.ensureTeamSubdir(teamName);
172
+ const envelope: PersistedTeamEnvelope = {
173
+ version: 1,
174
+ updatedAt: nowIso(),
175
+ teamState: state,
176
+ teammates,
177
+ };
178
+ const path = this.statePath(teamName);
179
+ const tempPath = `${path}.tmp`;
180
+ writeFileSync(tempPath, `${JSON.stringify(envelope, null, 2)}\n`, "utf8");
181
+ renameSync(tempPath, path);
182
+ }
183
+
184
+ markInProgressRunsInterrupted(teamName: string, reason: string): string[] {
185
+ const envelope = this.readEnvelope(teamName);
186
+ if (!envelope?.teamState?.runs?.length) {
187
+ return [];
188
+ }
189
+ const interrupted = envelope.teamState.runs
190
+ .filter((run) => run.status === "queued" || run.status === "running")
191
+ .map((run) => run.id);
192
+ if (interrupted.length === 0) {
193
+ return [];
194
+ }
195
+ const endedAt = new Date();
196
+ envelope.teamState = {
197
+ ...envelope.teamState,
198
+ runs: envelope.teamState.runs.map((run) =>
199
+ run.status === "queued" || run.status === "running"
200
+ ? {
201
+ ...run,
202
+ status: "interrupted",
203
+ error: reason,
204
+ endedAt,
205
+ }
206
+ : run,
207
+ ),
208
+ };
209
+ this.persistRuntime(teamName, envelope.teamState, envelope.teammates);
210
+ return interrupted;
211
+ }
212
+
213
+ private ensureTeamDir(): string {
214
+ if (!existsSync(this.teamDirPath)) {
215
+ mkdirSync(this.teamDirPath, { recursive: true });
216
+ }
217
+ return this.teamDirPath;
218
+ }
219
+
220
+ private ensureTeamSubdir(teamName: string): string {
221
+ const path = join(this.ensureTeamDir(), sanitizeTeamName(teamName));
222
+ if (!existsSync(path)) {
223
+ mkdirSync(path, { recursive: true });
224
+ }
225
+ return path;
226
+ }
227
+
228
+ private statePath(teamName: string): string {
229
+ return join(this.ensureTeamDir(), sanitizeTeamName(teamName), "state.json");
230
+ }
231
+
232
+ private historyPath(teamName: string): string {
233
+ return join(
234
+ this.ensureTeamDir(),
235
+ sanitizeTeamName(teamName),
236
+ "task-history.jsonl",
237
+ );
238
+ }
239
+
240
+ private readEnvelope(teamName: string): PersistedTeamEnvelope | undefined {
241
+ const path = this.statePath(teamName);
242
+ if (!existsSync(path)) {
243
+ return undefined;
244
+ }
245
+ try {
246
+ const parsed = JSON.parse(
247
+ readFileSync(path, "utf8"),
248
+ ) as PersistedTeamEnvelope;
249
+ if (parsed?.version === 1 && parsed.teamState) {
250
+ return parsed;
251
+ }
252
+ } catch {
253
+ // Ignore invalid persistence and fall back to undefined.
254
+ }
255
+ return undefined;
256
+ }
257
+ }
@@ -0,0 +1,11 @@
1
+ export type { ArtifactStore } from "./artifact-store";
2
+ export {
3
+ type MigrateLegacyProviderSettingsOptions,
4
+ type MigrateLegacyProviderSettingsResult,
5
+ migrateLegacyProviderSettings,
6
+ } from "./provider-settings-legacy-migration";
7
+ export { ProviderSettingsManager } from "./provider-settings-manager";
8
+ export type { SessionStore } from "./session-store";
9
+ export { SqliteSessionStore } from "./sqlite-session-store";
10
+ export type { TeamStore } from "./team-store";
11
+ export { SqliteTeamStore, type SqliteTeamStoreOptions } from "./team-store";