@clinebot/core 0.0.0 → 0.0.3

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 (59) hide show
  1. package/README.md +7 -7
  2. package/dist/default-tools/definitions.d.ts +1 -1
  3. package/dist/default-tools/executors/index.d.ts +1 -1
  4. package/dist/default-tools/index.d.ts +2 -1
  5. package/dist/default-tools/model-tool-routing.d.ts +33 -0
  6. package/dist/default-tools/schemas.d.ts +13 -7
  7. package/dist/index.browser.d.ts +1 -0
  8. package/dist/index.browser.js +220 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.js +47 -47
  11. package/dist/index.node.d.ts +36 -0
  12. package/dist/index.node.js +622 -0
  13. package/dist/providers/local-provider-service.d.ts +37 -0
  14. package/dist/session/default-session-manager.d.ts +3 -1
  15. package/dist/session/session-host.d.ts +2 -2
  16. package/dist/session/session-manager.d.ts +8 -0
  17. package/dist/session/unified-session-persistence-service.d.ts +1 -1
  18. package/dist/session/utils/helpers.d.ts +11 -0
  19. package/dist/session/utils/types.d.ts +42 -0
  20. package/dist/session/utils/usage.d.ts +9 -0
  21. package/dist/storage/provider-settings-manager.d.ts +2 -0
  22. package/dist/types/config.d.ts +8 -1
  23. package/dist/types.d.ts +1 -1
  24. package/package.json +11 -32
  25. package/src/default-tools/definitions.test.ts +130 -1
  26. package/src/default-tools/definitions.ts +7 -3
  27. package/src/default-tools/executors/editor.ts +10 -9
  28. package/src/default-tools/executors/file-read.test.ts +1 -1
  29. package/src/default-tools/executors/file-read.ts +11 -6
  30. package/src/default-tools/executors/index.ts +1 -1
  31. package/src/default-tools/index.ts +6 -1
  32. package/src/default-tools/model-tool-routing.test.ts +86 -0
  33. package/src/default-tools/model-tool-routing.ts +132 -0
  34. package/src/default-tools/schemas.ts +49 -52
  35. package/src/index.browser.ts +1 -0
  36. package/src/{server/index.ts → index.node.ts} +51 -109
  37. package/src/index.ts +41 -2
  38. package/src/input/file-indexer.ts +28 -2
  39. package/src/providers/local-provider-service.ts +591 -0
  40. package/src/runtime/runtime-builder.test.ts +69 -0
  41. package/src/runtime/runtime-builder.ts +20 -0
  42. package/src/runtime/runtime-parity.test.ts +20 -9
  43. package/src/session/default-session-manager.e2e.test.ts +11 -1
  44. package/src/session/default-session-manager.test.ts +270 -0
  45. package/src/session/default-session-manager.ts +109 -191
  46. package/src/session/index.ts +7 -2
  47. package/src/session/session-host.ts +30 -18
  48. package/src/session/session-manager.ts +11 -0
  49. package/src/session/unified-session-persistence-service.ts +11 -5
  50. package/src/session/utils/helpers.ts +148 -0
  51. package/src/session/utils/types.ts +46 -0
  52. package/src/session/utils/usage.ts +32 -0
  53. package/src/storage/provider-settings-legacy-migration.test.ts +3 -3
  54. package/src/storage/provider-settings-manager.test.ts +34 -0
  55. package/src/storage/provider-settings-manager.ts +22 -1
  56. package/src/types/config.ts +13 -0
  57. package/src/types.ts +1 -0
  58. package/dist/server/index.d.ts +0 -47
  59. package/dist/server/index.js +0 -641
@@ -30,7 +30,7 @@ import {
30
30
  mergeAgentHooks,
31
31
  } from "../runtime/hook-file-hooks";
32
32
  import { DefaultRuntimeBuilder } from "../runtime/runtime-builder";
33
- import type { BuiltRuntime, RuntimeBuilder } from "../runtime/session-runtime";
33
+ import type { RuntimeBuilder } from "../runtime/session-runtime";
34
34
  import { ProviderSettingsManager } from "../storage/provider-settings-manager";
35
35
  import {
36
36
  buildTeamProgressSummary,
@@ -53,6 +53,7 @@ import {
53
53
  import { nowIso } from "./session-artifacts";
54
54
  import type {
55
55
  SendSessionInput,
56
+ SessionAccumulatedUsage,
56
57
  SessionManager,
57
58
  StartSessionInput,
58
59
  StartSessionResult,
@@ -63,47 +64,26 @@ import type {
63
64
  RootSessionArtifacts,
64
65
  SessionRowShape,
65
66
  } from "./session-service";
67
+ import {
68
+ extractWorkspaceMetadataFromSystemPrompt,
69
+ hasRuntimeHooks,
70
+ mergeAgentExtensions,
71
+ serializeAgentEvent,
72
+ toSessionRecord,
73
+ withLatestAssistantTurnMetadata,
74
+ } from "./utils/helpers";
75
+ import type {
76
+ ActiveSession,
77
+ PreparedTurnInput,
78
+ TeamRunUpdate,
79
+ } from "./utils/types";
80
+ import {
81
+ accumulateUsageTotals,
82
+ createInitialAccumulatedUsage,
83
+ } from "./utils/usage";
66
84
 
67
85
  type SessionBackend = CoreSessionService | RpcCoreSessionService;
68
86
 
69
- type ActiveSession = {
70
- sessionId: string;
71
- config: CoreSessionConfig;
72
- artifacts?: RootSessionArtifacts;
73
- source: SessionSource;
74
- startedAt: string;
75
- pendingPrompt?: string;
76
- runtime: BuiltRuntime;
77
- agent: Agent;
78
- started: boolean;
79
- aborting: boolean;
80
- interactive: boolean;
81
- activeTeamRunIds: Set<string>;
82
- pendingTeamRunUpdates: TeamRunUpdate[];
83
- teamRunWaiters: Array<() => void>;
84
- pluginSandboxShutdown?: () => Promise<void>;
85
- };
86
-
87
- type TeamRunUpdate = {
88
- runId: string;
89
- agentId: string;
90
- taskId?: string;
91
- status: "completed" | "failed" | "cancelled" | "interrupted";
92
- error?: string;
93
- iterations?: number;
94
- };
95
-
96
- type StoredMessageWithMetadata = LlmsProviders.MessageWithMetadata & {
97
- providerId?: string;
98
- modelId?: string;
99
- };
100
-
101
- type PreparedTurnInput = {
102
- prompt: string;
103
- userImages?: string[];
104
- userFiles?: string[];
105
- };
106
-
107
87
  export interface DefaultSessionManagerOptions {
108
88
  distinctId: string;
109
89
  sessionService: SessionBackend;
@@ -136,134 +116,6 @@ async function loadUserFileContent(path: string): Promise<string> {
136
116
  return content;
137
117
  }
138
118
 
139
- function hasRuntimeHooks(hooks: AgentConfig["hooks"]): boolean {
140
- if (!hooks) {
141
- return false;
142
- }
143
- return Object.values(hooks).some((value) => typeof value === "function");
144
- }
145
-
146
- function mergeAgentExtensions(
147
- explicitExtensions: AgentConfig["extensions"] | undefined,
148
- loadedExtensions: AgentConfig["extensions"] | undefined,
149
- ): AgentConfig["extensions"] {
150
- const merged = [...(explicitExtensions ?? []), ...(loadedExtensions ?? [])];
151
- if (merged.length === 0) {
152
- return undefined;
153
- }
154
- const deduped: NonNullable<AgentConfig["extensions"]> = [];
155
- const seenNames = new Set<string>();
156
- for (const extension of merged) {
157
- if (seenNames.has(extension.name)) {
158
- continue;
159
- }
160
- seenNames.add(extension.name);
161
- deduped.push(extension);
162
- }
163
- return deduped;
164
- }
165
-
166
- function serializeAgentEvent(event: AgentEvent): string {
167
- return JSON.stringify(event, (_key, value) => {
168
- if (value instanceof Error) {
169
- return {
170
- name: value.name,
171
- message: value.message,
172
- stack: value.stack,
173
- };
174
- }
175
- return value;
176
- });
177
- }
178
-
179
- function withLatestAssistantTurnMetadata(
180
- messages: LlmsProviders.Message[],
181
- result: AgentResult,
182
- ): StoredMessageWithMetadata[] {
183
- const next = messages.map((message) => ({
184
- ...message,
185
- })) as StoredMessageWithMetadata[];
186
- const assistantIndex = [...next]
187
- .reverse()
188
- .findIndex((message) => message.role === "assistant");
189
- if (assistantIndex === -1) {
190
- return next;
191
- }
192
-
193
- const targetIndex = next.length - 1 - assistantIndex;
194
- const target = next[targetIndex];
195
- const usage = result.usage;
196
- next[targetIndex] = {
197
- ...target,
198
- providerId: target.providerId ?? result.model.provider,
199
- modelId: target.modelId ?? result.model.id,
200
- modelInfo: target.modelInfo ?? {
201
- id: result.model.id,
202
- provider: result.model.provider,
203
- },
204
- metrics: {
205
- ...(target.metrics ?? {}),
206
- inputTokens: usage.inputTokens,
207
- outputTokens: usage.outputTokens,
208
- cacheReadTokens: usage.cacheReadTokens,
209
- cacheWriteTokens: usage.cacheWriteTokens,
210
- cost: usage.totalCost,
211
- },
212
- ts: target.ts ?? result.endedAt.getTime(),
213
- };
214
- return next;
215
- }
216
-
217
- function toSessionRecord(row: SessionRowShape): SessionRecord {
218
- const metadata =
219
- typeof row.metadata_json === "string" && row.metadata_json.trim().length > 0
220
- ? (() => {
221
- try {
222
- const parsed = JSON.parse(row.metadata_json) as unknown;
223
- if (
224
- parsed &&
225
- typeof parsed === "object" &&
226
- !Array.isArray(parsed)
227
- ) {
228
- return parsed as Record<string, unknown>;
229
- }
230
- } catch {
231
- // Ignore malformed metadata payloads.
232
- }
233
- return undefined;
234
- })()
235
- : undefined;
236
- return {
237
- sessionId: row.session_id,
238
- source: row.source as SessionSource,
239
- pid: row.pid,
240
- startedAt: row.started_at,
241
- endedAt: row.ended_at ?? null,
242
- exitCode: row.exit_code ?? null,
243
- status: row.status,
244
- interactive: row.interactive === 1,
245
- provider: row.provider,
246
- model: row.model,
247
- cwd: row.cwd,
248
- workspaceRoot: row.workspace_root,
249
- teamName: row.team_name ?? undefined,
250
- enableTools: row.enable_tools === 1,
251
- enableSpawn: row.enable_spawn === 1,
252
- enableTeams: row.enable_teams === 1,
253
- parentSessionId: row.parent_session_id ?? undefined,
254
- parentAgentId: row.parent_agent_id ?? undefined,
255
- agentId: row.agent_id ?? undefined,
256
- conversationId: row.conversation_id ?? undefined,
257
- isSubagent: row.is_subagent === 1,
258
- prompt: row.prompt ?? undefined,
259
- metadata,
260
- transcriptPath: row.transcript_path,
261
- hookPath: row.hook_path,
262
- messagesPath: row.messages_path ?? undefined,
263
- updatedAt: row.updated_at ?? nowIso(),
264
- };
265
- }
266
-
267
119
  export class DefaultSessionManager implements SessionManager {
268
120
  private readonly sessionService: SessionBackend;
269
121
  private readonly runtimeBuilder: RuntimeBuilder;
@@ -277,6 +129,7 @@ export class DefaultSessionManager implements SessionManager {
277
129
  ) => Promise<ToolApprovalResult>;
278
130
  private readonly listeners = new Set<(event: CoreSessionEvent) => void>();
279
131
  private readonly sessions = new Map<string, ActiveSession>();
132
+ private readonly usageBySession = new Map<string, SessionAccumulatedUsage>();
280
133
 
281
134
  constructor(options: DefaultSessionManagerOptions) {
282
135
  const homeDir = homedir();
@@ -319,10 +172,16 @@ export class DefaultSessionManager implements SessionManager {
319
172
  baseUrl: config.baseUrl ?? settings.baseUrl,
320
173
  headers: config.headers ?? settings.headers,
321
174
  reasoning:
322
- typeof config.thinking === "boolean"
175
+ typeof config.thinking === "boolean" ||
176
+ typeof config.reasoningEffort === "string"
323
177
  ? {
324
178
  ...(settings.reasoning ?? {}),
325
- enabled: config.thinking,
179
+ ...(typeof config.thinking === "boolean"
180
+ ? { enabled: config.thinking }
181
+ : {}),
182
+ ...(typeof config.reasoningEffort === "string"
183
+ ? { effort: config.reasoningEffort }
184
+ : {}),
326
185
  }
327
186
  : settings.reasoning,
328
187
  };
@@ -341,6 +200,7 @@ export class DefaultSessionManager implements SessionManager {
341
200
  requestedSessionId.length > 0
342
201
  ? requestedSessionId
343
202
  : `${Date.now()}_${nanoid(5)}`;
203
+ this.usageBySession.set(sessionId, createInitialAccumulatedUsage());
344
204
  const sessionsDir =
345
205
  ((await this.invokeOptionalValue("ensureSessionsDir")) as
346
206
  | string
@@ -440,8 +300,11 @@ export class DefaultSessionManager implements SessionManager {
440
300
  knownModels: providerConfig.knownModels,
441
301
  providerConfig,
442
302
  thinking: effectiveConfig.thinking,
303
+ reasoningEffort:
304
+ effectiveConfig.reasoningEffort ?? providerConfig.reasoningEffort,
443
305
  systemPrompt: effectiveConfig.systemPrompt,
444
306
  maxIterations: effectiveConfig.maxIterations,
307
+ maxConsecutiveMistakes: effectiveConfig.maxConsecutiveMistakes,
445
308
  tools,
446
309
  hooks: effectiveHooks,
447
310
  extensions: effectiveExtensions,
@@ -451,9 +314,22 @@ export class DefaultSessionManager implements SessionManager {
451
314
  toolPolicies: input.toolPolicies ?? this.defaultToolPolicies,
452
315
  requestToolApproval:
453
316
  input.requestToolApproval ?? this.defaultRequestToolApproval,
317
+ onConsecutiveMistakeLimitReached:
318
+ effectiveConfig.onConsecutiveMistakeLimitReached,
454
319
  completionGuard: runtime.completionGuard,
455
320
  logger: runtime.logger ?? effectiveConfig.logger,
456
321
  onEvent: (event: AgentEvent) => {
322
+ const liveSession = this.sessions.get(sessionId);
323
+ if (event.type === "usage" && liveSession?.turnUsageBaseline) {
324
+ this.usageBySession.set(
325
+ sessionId,
326
+ accumulateUsageTotals(liveSession.turnUsageBaseline, {
327
+ inputTokens: event.totalInputTokens,
328
+ outputTokens: event.totalOutputTokens,
329
+ totalCost: event.totalCost,
330
+ }),
331
+ );
332
+ }
457
333
  this.emit({
458
334
  type: "agent_event",
459
335
  payload: {
@@ -541,6 +417,16 @@ export class DefaultSessionManager implements SessionManager {
541
417
  }
542
418
  }
543
419
 
420
+ async getAccumulatedUsage(
421
+ sessionId: string,
422
+ ): Promise<SessionAccumulatedUsage | undefined> {
423
+ const usage = this.usageBySession.get(sessionId);
424
+ if (!usage) {
425
+ return undefined;
426
+ }
427
+ return { ...usage };
428
+ }
429
+
544
430
  async abort(sessionId: string): Promise<void> {
545
431
  const session = this.sessions.get(sessionId);
546
432
  if (!session) {
@@ -578,6 +464,7 @@ export class DefaultSessionManager implements SessionManager {
578
464
  });
579
465
  }),
580
466
  );
467
+ this.usageBySession.clear();
581
468
  }
582
469
 
583
470
  async get(sessionId: string): Promise<SessionRecord | undefined> {
@@ -598,6 +485,9 @@ export class DefaultSessionManager implements SessionManager {
598
485
  "deleteSession",
599
486
  sessionId,
600
487
  );
488
+ if (result.deleted) {
489
+ this.usageBySession.delete(sessionId);
490
+ }
601
491
  return result.deleted;
602
492
  }
603
493
 
@@ -709,28 +599,50 @@ export class DefaultSessionManager implements SessionManager {
709
599
  const shouldContinue =
710
600
  session.started || session.agent.getMessages().length > 0;
711
601
  const baselineMessages = session.agent.getMessages();
712
- const result = shouldContinue
713
- ? await this.runWithAuthRetry(
714
- session,
715
- () => session.agent.continue(prompt, userImages, userFiles),
716
- baselineMessages,
717
- )
718
- : await this.runWithAuthRetry(
719
- session,
720
- () => session.agent.run(prompt, userImages, userFiles),
721
- baselineMessages,
722
- );
723
- session.started = true;
724
- const persistedMessages = withLatestAssistantTurnMetadata(
725
- result.messages,
726
- result,
727
- );
728
- await this.invoke<void>(
729
- "persistSessionMessages",
730
- session.sessionId,
731
- persistedMessages,
732
- );
733
- return result;
602
+ const usageBaseline =
603
+ this.usageBySession.get(session.sessionId) ??
604
+ createInitialAccumulatedUsage();
605
+ session.turnUsageBaseline = usageBaseline;
606
+ try {
607
+ const result = shouldContinue
608
+ ? await this.runWithAuthRetry(
609
+ session,
610
+ () => session.agent.continue(prompt, userImages, userFiles),
611
+ baselineMessages,
612
+ )
613
+ : await this.runWithAuthRetry(
614
+ session,
615
+ () => session.agent.run(prompt, userImages, userFiles),
616
+ baselineMessages,
617
+ );
618
+ session.started = true;
619
+ const persistedMessages = withLatestAssistantTurnMetadata(
620
+ result.messages,
621
+ result,
622
+ );
623
+ this.usageBySession.set(
624
+ session.sessionId,
625
+ accumulateUsageTotals(usageBaseline, result.usage),
626
+ );
627
+ await this.invoke<void>(
628
+ "persistSessionMessages",
629
+ session.sessionId,
630
+ persistedMessages,
631
+ session.config.systemPrompt,
632
+ );
633
+ return result;
634
+ } catch (error) {
635
+ // Persist whatever was rendered so far even when a turn fails.
636
+ await this.invoke<void>(
637
+ "persistSessionMessages",
638
+ session.sessionId,
639
+ session.agent.getMessages(),
640
+ session.config.systemPrompt,
641
+ );
642
+ throw error;
643
+ } finally {
644
+ session.turnUsageBaseline = undefined;
645
+ }
734
646
  }
735
647
 
736
648
  private async prepareTurnInput(
@@ -963,10 +875,15 @@ export class DefaultSessionManager implements SessionManager {
963
875
  return createSpawnAgentTool({
964
876
  providerId: config.providerId,
965
877
  modelId: config.modelId,
878
+ cwd: config.cwd,
966
879
  apiKey: config.apiKey,
967
880
  baseUrl: config.baseUrl,
968
881
  providerConfig: config.providerConfig,
969
882
  knownModels: config.knownModels,
883
+ clineWorkspaceMetadata:
884
+ config.providerId === "cline"
885
+ ? extractWorkspaceMetadataFromSystemPrompt(config.systemPrompt)
886
+ : undefined,
970
887
  createSubAgentTools,
971
888
  hooks: config.hooks,
972
889
  extensions: config.extensions,
@@ -1039,6 +956,7 @@ export class DefaultSessionManager implements SessionManager {
1039
956
  event.agentId,
1040
957
  "failed",
1041
958
  `[error] ${event.error.message}`,
959
+ event.messages,
1042
960
  );
1043
961
  break;
1044
962
  }
@@ -6,10 +6,15 @@ export {
6
6
  makeTeamTaskSubSessionId,
7
7
  sanitizeSessionToken,
8
8
  } from "./session-graph";
9
- export type { CreateSessionHostOptions, SessionHost } from "./session-host";
10
- export { createSessionHost } from "./session-host";
9
+ export type {
10
+ CreateSessionHostOptions,
11
+ SessionBackend,
12
+ SessionHost,
13
+ } from "./session-host";
14
+ export { createSessionHost, resolveSessionBackend } from "./session-host";
11
15
  export type {
12
16
  SendSessionInput,
17
+ SessionAccumulatedUsage,
13
18
  SessionManager,
14
19
  StartSessionInput,
15
20
  StartSessionResult,
@@ -6,7 +6,7 @@ import type {
6
6
  ToolApprovalRequest,
7
7
  ToolApprovalResult,
8
8
  } from "@clinebot/agents";
9
- import { getRpcServerHealth } from "@clinebot/rpc";
9
+ import { getRpcServerDefaultAddress, getRpcServerHealth } from "@clinebot/rpc";
10
10
  import { resolveSessionDataDir } from "@clinebot/shared/storage";
11
11
  import { nanoid } from "nanoid";
12
12
  import type { ToolExecutors } from "../default-tools";
@@ -17,9 +17,9 @@ import type { SessionManager } from "./session-manager";
17
17
  import { CoreSessionService } from "./session-service";
18
18
 
19
19
  const DEFAULT_RPC_ADDRESS =
20
- process.env.CLINE_RPC_ADDRESS?.trim() || "127.0.0.1:4317";
20
+ process.env.CLINE_RPC_ADDRESS?.trim() || getRpcServerDefaultAddress();
21
21
 
22
- type SessionBackend = RpcCoreSessionService | CoreSessionService;
22
+ export type SessionBackend = RpcCoreSessionService | CoreSessionService;
23
23
 
24
24
  let cachedBackend: SessionBackend | undefined;
25
25
  let backendInitPromise: Promise<SessionBackend> | undefined;
@@ -41,24 +41,35 @@ export interface CreateSessionHostOptions {
41
41
 
42
42
  export type SessionHost = SessionManager;
43
43
 
44
- function isLikelyScriptEntryPath(pathValue: string | undefined): boolean {
45
- if (!pathValue) {
46
- return false;
47
- }
48
- return /\.(?:[cm]?[jt]s|tsx?)$/i.test(pathValue);
49
- }
50
-
51
44
  function startRpcServerInBackground(address: string): void {
52
- const launcher = process.argv[0];
53
- const entry = process.argv[1];
54
- const startArgs = ["rpc", "start", "--address", address];
55
- const args =
56
- entry && isLikelyScriptEntryPath(entry) ? [entry, ...startArgs] : startArgs;
45
+ const launcher = process.execPath;
46
+ const entryArg = process.argv[1]?.trim();
47
+ if (!entryArg) {
48
+ return;
49
+ }
50
+ const entry = resolve(process.cwd(), entryArg);
51
+ if (!existsSync(entry)) {
52
+ return;
53
+ }
54
+ const conditionsArg = process.execArgv.find((arg) =>
55
+ arg.startsWith("--conditions="),
56
+ );
57
+ const args = [
58
+ ...(conditionsArg ? [conditionsArg] : []),
59
+ entry,
60
+ "rpc",
61
+ "start",
62
+ "--address",
63
+ address,
64
+ ];
57
65
 
58
66
  const child = spawn(launcher, args, {
59
67
  detached: true,
60
68
  stdio: "ignore",
61
- env: process.env,
69
+ env: {
70
+ ...process.env,
71
+ CLINE_NO_INTERACTIVE: "1",
72
+ },
62
73
  cwd: process.cwd(),
63
74
  });
64
75
  child.unref();
@@ -116,7 +127,7 @@ function resolveHostDistinctId(explicitDistinctId: string | undefined): string {
116
127
  return generatedDistinctId;
117
128
  }
118
129
 
119
- async function resolveBackend(
130
+ export async function resolveSessionBackend(
120
131
  options: CreateSessionHostOptions,
121
132
  ): Promise<SessionBackend> {
122
133
  if (cachedBackend) {
@@ -179,7 +190,8 @@ async function resolveBackend(
179
190
  export async function createSessionHost(
180
191
  options: CreateSessionHostOptions,
181
192
  ): Promise<SessionHost> {
182
- const backend = options.sessionService ?? (await resolveBackend(options));
193
+ const backend =
194
+ options.sessionService ?? (await resolveSessionBackend(options));
183
195
  return new DefaultSessionManager({
184
196
  sessionService: backend,
185
197
  defaultToolExecutors: options.defaultToolExecutors,
@@ -40,9 +40,20 @@ export interface SendSessionInput {
40
40
  userFiles?: string[];
41
41
  }
42
42
 
43
+ export interface SessionAccumulatedUsage {
44
+ inputTokens: number;
45
+ outputTokens: number;
46
+ cacheReadTokens: number;
47
+ cacheWriteTokens: number;
48
+ totalCost: number;
49
+ }
50
+
43
51
  export interface SessionManager {
44
52
  start(input: StartSessionInput): Promise<StartSessionResult>;
45
53
  send(input: SendSessionInput): Promise<AgentResult | undefined>;
54
+ getAccumulatedUsage(
55
+ sessionId: string,
56
+ ): Promise<SessionAccumulatedUsage | undefined>;
46
57
  abort(sessionId: string): Promise<void>;
47
58
  stop(sessionId: string): Promise<void>;
48
59
  dispose(reason?: string): Promise<void>;
@@ -499,15 +499,21 @@ export class UnifiedSessionPersistenceService {
499
499
  async persistSessionMessages(
500
500
  sessionId: string,
501
501
  messages: LlmsProviders.Message[],
502
+ systemPrompt?: string,
502
503
  ): Promise<void> {
503
504
  const path =
504
505
  (await this.sessionPathFromStore(sessionId, "messages_path")) ??
505
506
  this.sessionMessagesPath(sessionId);
506
- writeFileSync(
507
- path,
508
- `${JSON.stringify({ version: 1, updated_at: nowIso(), messages }, null, 2)}\n`,
509
- "utf8",
510
- );
507
+ const payload: {
508
+ version: number;
509
+ updated_at: string;
510
+ systemPrompt?: string;
511
+ messages: LlmsProviders.Message[];
512
+ } = { version: 1, updated_at: nowIso(), messages };
513
+ if (systemPrompt !== undefined && systemPrompt !== "") {
514
+ payload.systemPrompt = systemPrompt;
515
+ }
516
+ writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
511
517
  }
512
518
 
513
519
  async applySubagentStatus(