@clinebot/core 0.0.3 → 0.0.5

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 (60) hide show
  1. package/README.md +7 -7
  2. package/dist/agents/agent-config-parser.d.ts +1 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.node.js +113 -93
  5. package/dist/runtime/session-runtime.d.ts +1 -1
  6. package/dist/session/default-session-manager.d.ts +1 -1
  7. package/dist/session/session-host.d.ts +1 -1
  8. package/dist/session/session-manager.d.ts +1 -1
  9. package/dist/session/unified-session-persistence-service.d.ts +4 -0
  10. package/dist/types/config.d.ts +1 -1
  11. package/package.json +13 -13
  12. package/src/agents/agent-config-parser.ts +1 -1
  13. package/src/index.ts +28 -19
  14. package/src/providers/local-provider-service.ts +25 -7
  15. package/src/runtime/runtime-builder.ts +2 -2
  16. package/src/runtime/runtime-parity.test.ts +1 -1
  17. package/src/runtime/session-runtime.ts +1 -1
  18. package/src/session/default-session-manager.ts +4 -5
  19. package/src/session/session-host.ts +1 -1
  20. package/src/session/session-manager.ts +1 -1
  21. package/src/session/unified-session-persistence-service.ts +213 -23
  22. package/src/types/config.ts +1 -1
  23. package/dist/index.browser.d.ts +0 -1
  24. package/dist/index.browser.js +0 -220
  25. package/dist/index.js +0 -220
  26. package/src/index.browser.ts +0 -1
  27. /package/dist/{default-tools → tools}/constants.d.ts +0 -0
  28. /package/dist/{default-tools → tools}/definitions.d.ts +0 -0
  29. /package/dist/{default-tools → tools}/executors/apply-patch-parser.d.ts +0 -0
  30. /package/dist/{default-tools → tools}/executors/apply-patch.d.ts +0 -0
  31. /package/dist/{default-tools → tools}/executors/bash.d.ts +0 -0
  32. /package/dist/{default-tools → tools}/executors/editor.d.ts +0 -0
  33. /package/dist/{default-tools → tools}/executors/file-read.d.ts +0 -0
  34. /package/dist/{default-tools → tools}/executors/index.d.ts +0 -0
  35. /package/dist/{default-tools → tools}/executors/search.d.ts +0 -0
  36. /package/dist/{default-tools → tools}/executors/web-fetch.d.ts +0 -0
  37. /package/dist/{default-tools → tools}/index.d.ts +0 -0
  38. /package/dist/{default-tools → tools}/model-tool-routing.d.ts +0 -0
  39. /package/dist/{default-tools → tools}/presets.d.ts +0 -0
  40. /package/dist/{default-tools → tools}/schemas.d.ts +0 -0
  41. /package/dist/{default-tools → tools}/types.d.ts +0 -0
  42. /package/src/{default-tools → tools}/constants.ts +0 -0
  43. /package/src/{default-tools → tools}/definitions.test.ts +0 -0
  44. /package/src/{default-tools → tools}/definitions.ts +0 -0
  45. /package/src/{default-tools → tools}/executors/apply-patch-parser.ts +0 -0
  46. /package/src/{default-tools → tools}/executors/apply-patch.ts +0 -0
  47. /package/src/{default-tools → tools}/executors/bash.ts +0 -0
  48. /package/src/{default-tools → tools}/executors/editor.ts +0 -0
  49. /package/src/{default-tools → tools}/executors/file-read.test.ts +0 -0
  50. /package/src/{default-tools → tools}/executors/file-read.ts +0 -0
  51. /package/src/{default-tools → tools}/executors/index.ts +0 -0
  52. /package/src/{default-tools → tools}/executors/search.ts +0 -0
  53. /package/src/{default-tools → tools}/executors/web-fetch.ts +0 -0
  54. /package/src/{default-tools → tools}/index.ts +0 -0
  55. /package/src/{default-tools → tools}/model-tool-routing.test.ts +0 -0
  56. /package/src/{default-tools → tools}/model-tool-routing.ts +0 -0
  57. /package/src/{default-tools → tools}/presets.test.ts +0 -0
  58. /package/src/{default-tools → tools}/presets.ts +0 -0
  59. /package/src/{default-tools → tools}/schemas.ts +0 -0
  60. /package/src/{default-tools → tools}/types.ts +0 -0
@@ -10,7 +10,7 @@ import type {
10
10
  SubAgentStartContext,
11
11
  } from "@clinebot/agents";
12
12
  import type { providers as LlmsProviders } from "@clinebot/llms";
13
- import { resolveRootSessionId } from "@clinebot/shared";
13
+ import { normalizeUserInput, resolveRootSessionId } from "@clinebot/shared";
14
14
  import { nanoid } from "nanoid";
15
15
  import { z } from "zod";
16
16
  import type { SessionStatus } from "../types/common";
@@ -48,6 +48,58 @@ function stringifyMetadataJson(
48
48
  return JSON.stringify(metadata);
49
49
  }
50
50
 
51
+ function normalizeSessionTitle(title?: string | null): string | undefined {
52
+ const trimmed = title?.trim();
53
+ return trimmed ? trimmed.slice(0, 120) : undefined;
54
+ }
55
+
56
+ function deriveSessionTitleFromPrompt(
57
+ prompt?: string | null,
58
+ ): string | undefined {
59
+ const normalizedPrompt = normalizeUserInput(prompt ?? "").trim();
60
+ if (!normalizedPrompt) {
61
+ return undefined;
62
+ }
63
+ const firstLine = normalizedPrompt.split("\n")[0]?.trim();
64
+ return normalizeSessionTitle(firstLine);
65
+ }
66
+
67
+ function normalizeMetadataForStorage(
68
+ metadata: Record<string, unknown> | null | undefined,
69
+ ): Record<string, unknown> | undefined {
70
+ if (!metadata) {
71
+ return undefined;
72
+ }
73
+ const next = { ...metadata };
74
+ if (typeof next.title === "string") {
75
+ const normalizedTitle = normalizeSessionTitle(next.title);
76
+ if (normalizedTitle) {
77
+ next.title = normalizedTitle;
78
+ } else {
79
+ delete next.title;
80
+ }
81
+ } else {
82
+ delete next.title;
83
+ }
84
+ return Object.keys(next).length > 0 ? next : undefined;
85
+ }
86
+
87
+ function metadataWithResolvedTitle(input: {
88
+ metadata?: Record<string, unknown> | null;
89
+ title?: string | null;
90
+ prompt?: string | null;
91
+ }): Record<string, unknown> | undefined {
92
+ const next = { ...(normalizeMetadataForStorage(input.metadata) ?? {}) };
93
+ const resolvedTitle =
94
+ input.title !== undefined
95
+ ? normalizeSessionTitle(input.title)
96
+ : deriveSessionTitleFromPrompt(input.prompt);
97
+ if (resolvedTitle) {
98
+ next.title = resolvedTitle;
99
+ }
100
+ return Object.keys(next).length > 0 ? next : undefined;
101
+ }
102
+
51
103
  export interface PersistedSessionUpdateInput {
52
104
  sessionId: string;
53
105
  expectedStatusLock?: number;
@@ -56,6 +108,7 @@ export interface PersistedSessionUpdateInput {
56
108
  exitCode?: number | null;
57
109
  prompt?: string | null;
58
110
  metadataJson?: string | null;
111
+ title?: string | null;
59
112
  parentSessionId?: string | null;
60
113
  parentAgentId?: string | null;
61
114
  agentId?: string | null;
@@ -173,6 +226,63 @@ export class UnifiedSessionPersistenceService {
173
226
  );
174
227
  }
175
228
 
229
+ private readSessionManifestFile(sessionId: string): {
230
+ path: string;
231
+ manifest?: SessionManifest;
232
+ } {
233
+ const manifestPath = this.sessionManifestPath(sessionId, false);
234
+ if (!existsSync(manifestPath)) {
235
+ return { path: manifestPath };
236
+ }
237
+ try {
238
+ const manifest = SessionManifestSchema.parse(
239
+ JSON.parse(readFileSync(manifestPath, "utf8")) as SessionManifest,
240
+ );
241
+ return { path: manifestPath, manifest };
242
+ } catch {
243
+ return { path: manifestPath };
244
+ }
245
+ }
246
+
247
+ private applyResolvedTitleToRow(row: SessionRowShape): SessionRowShape {
248
+ const existingMetadata =
249
+ typeof row.metadata_json === "string" &&
250
+ row.metadata_json.trim().length > 0
251
+ ? (() => {
252
+ try {
253
+ const parsed = JSON.parse(row.metadata_json) as unknown;
254
+ if (
255
+ parsed &&
256
+ typeof parsed === "object" &&
257
+ !Array.isArray(parsed)
258
+ ) {
259
+ return parsed as Record<string, unknown>;
260
+ }
261
+ } catch {
262
+ // Ignore malformed metadata payloads.
263
+ }
264
+ return undefined;
265
+ })()
266
+ : undefined;
267
+ const sanitizedMetadata = normalizeMetadataForStorage(existingMetadata);
268
+ const { manifest } = this.readSessionManifestFile(row.session_id);
269
+ const manifestTitle = normalizeSessionTitle(
270
+ typeof manifest?.metadata?.title === "string"
271
+ ? (manifest.metadata.title as string)
272
+ : undefined,
273
+ );
274
+ const resolvedMetadata = manifestTitle
275
+ ? {
276
+ ...(sanitizedMetadata ?? {}),
277
+ title: manifestTitle,
278
+ }
279
+ : sanitizedMetadata;
280
+ return {
281
+ ...row,
282
+ metadata_json: stringifyMetadataJson(resolvedMetadata),
283
+ };
284
+ }
285
+
176
286
  private createRootSessionId(): string {
177
287
  return `${Date.now()}_${nanoid(5)}`;
178
288
  }
@@ -207,9 +317,13 @@ export class UnifiedSessionPersistenceService {
207
317
  enable_spawn: input.enableSpawn,
208
318
  enable_teams: input.enableTeams,
209
319
  prompt: input.prompt?.trim() || undefined,
210
- metadata: input.metadata,
320
+ metadata: metadataWithResolvedTitle({
321
+ metadata: input.metadata,
322
+ prompt: input.prompt,
323
+ }),
211
324
  messages_path: messagesPath,
212
325
  });
326
+ const storedMetadata = normalizeMetadataForStorage(manifest.metadata);
213
327
 
214
328
  await this.adapter.upsertSession({
215
329
  session_id: sessionId,
@@ -235,7 +349,7 @@ export class UnifiedSessionPersistenceService {
235
349
  conversation_id: null,
236
350
  is_subagent: 0,
237
351
  prompt: manifest.prompt ?? null,
238
- metadata_json: stringifyMetadataJson(manifest.metadata),
352
+ metadata_json: stringifyMetadataJson(storedMetadata),
239
353
  transcript_path: transcriptPath,
240
354
  hook_path: hookPath,
241
355
  messages_path: messagesPath,
@@ -293,40 +407,86 @@ export class UnifiedSessionPersistenceService {
293
407
  sessionId: string;
294
408
  prompt?: string | null;
295
409
  metadata?: Record<string, unknown> | null;
410
+ title?: string | null;
296
411
  }): Promise<{ updated: boolean }> {
297
412
  for (let attempt = 0; attempt < 4; attempt++) {
298
413
  const row = await this.adapter.getSession(input.sessionId);
299
414
  if (!row || typeof row.status_lock !== "number") {
300
415
  return { updated: false };
301
416
  }
417
+ const sanitizedMetadata =
418
+ input.metadata === undefined
419
+ ? undefined
420
+ : normalizeMetadataForStorage(input.metadata);
421
+ const existingMetadata = (() => {
422
+ const raw = row.metadata_json?.trim();
423
+ if (!raw) {
424
+ return undefined;
425
+ }
426
+ try {
427
+ const parsed = JSON.parse(raw) as unknown;
428
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
429
+ return normalizeMetadataForStorage(
430
+ parsed as Record<string, unknown>,
431
+ );
432
+ }
433
+ } catch {
434
+ // Ignore malformed metadata payloads.
435
+ }
436
+ return undefined;
437
+ })();
438
+ const existingTitle = normalizeSessionTitle(
439
+ typeof existingMetadata?.title === "string"
440
+ ? (existingMetadata.title as string)
441
+ : undefined,
442
+ );
443
+ const nextTitle =
444
+ input.title !== undefined
445
+ ? normalizeSessionTitle(input.title)
446
+ : input.prompt !== undefined
447
+ ? deriveSessionTitleFromPrompt(input.prompt)
448
+ : existingTitle;
449
+ const nextMetadata =
450
+ input.metadata !== undefined
451
+ ? { ...(sanitizedMetadata ?? {}) }
452
+ : { ...(existingMetadata ?? {}) };
453
+ if (nextTitle) {
454
+ nextMetadata.title = nextTitle;
455
+ } else {
456
+ delete nextMetadata.title;
457
+ }
302
458
  const changed = await this.adapter.updateSession({
303
459
  sessionId: input.sessionId,
304
460
  prompt: input.prompt,
305
461
  metadataJson:
306
- input.metadata === undefined
462
+ input.metadata === undefined &&
463
+ input.prompt === undefined &&
464
+ input.title === undefined
307
465
  ? undefined
308
- : stringifyMetadataJson(input.metadata),
466
+ : stringifyMetadataJson(nextMetadata),
467
+ title: nextTitle,
309
468
  expectedStatusLock: row.status_lock,
310
469
  });
311
470
  if (!changed.updated) {
312
471
  continue;
313
472
  }
314
- const manifestPath = this.sessionManifestPath(input.sessionId, false);
315
- if (existsSync(manifestPath)) {
316
- try {
317
- const manifest = SessionManifestSchema.parse(
318
- JSON.parse(readFileSync(manifestPath, "utf8")) as SessionManifest,
319
- );
320
- if (input.prompt !== undefined) {
321
- manifest.prompt = input.prompt ?? undefined;
322
- }
323
- if (input.metadata !== undefined) {
324
- manifest.metadata = input.metadata ?? undefined;
325
- }
326
- this.writeSessionManifestFile(manifestPath, manifest);
327
- } catch {
328
- // Ignore malformed manifests and keep backend session state as source of truth.
473
+ const { path: manifestPath, manifest } = this.readSessionManifestFile(
474
+ input.sessionId,
475
+ );
476
+ if (manifest) {
477
+ if (input.prompt !== undefined) {
478
+ manifest.prompt = input.prompt ?? undefined;
479
+ }
480
+ const nextMetadata =
481
+ input.metadata !== undefined
482
+ ? { ...(normalizeMetadataForStorage(input.metadata) ?? {}) }
483
+ : { ...(normalizeMetadataForStorage(manifest.metadata) ?? {}) };
484
+ if (nextTitle) {
485
+ nextMetadata.title = nextTitle;
329
486
  }
487
+ manifest.metadata =
488
+ Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined;
489
+ this.writeSessionManifestFile(manifestPath, manifest);
330
490
  }
331
491
  return { updated: true };
332
492
  }
@@ -422,7 +582,9 @@ export class UnifiedSessionPersistenceService {
422
582
  conversation_id: input.conversationId,
423
583
  is_subagent: 1,
424
584
  prompt,
425
- metadata_json: null,
585
+ metadata_json: stringifyMetadataJson(
586
+ metadataWithResolvedTitle({ prompt }),
587
+ ),
426
588
  transcript_path: artifactPaths.transcriptPath,
427
589
  hook_path: artifactPaths.hookPath,
428
590
  messages_path: artifactPaths.messagesPath,
@@ -444,6 +606,30 @@ export class UnifiedSessionPersistenceService {
444
606
  agentId: input.agentId,
445
607
  conversationId: input.conversationId,
446
608
  prompt: existing.prompt ?? prompt ?? null,
609
+ metadataJson: stringifyMetadataJson(
610
+ metadataWithResolvedTitle({
611
+ metadata: (() => {
612
+ const raw = existing.metadata_json?.trim();
613
+ if (!raw) {
614
+ return undefined;
615
+ }
616
+ try {
617
+ const parsed = JSON.parse(raw) as unknown;
618
+ if (
619
+ parsed &&
620
+ typeof parsed === "object" &&
621
+ !Array.isArray(parsed)
622
+ ) {
623
+ return parsed as Record<string, unknown>;
624
+ }
625
+ } catch {
626
+ // Ignore malformed metadata payloads.
627
+ }
628
+ return undefined;
629
+ })(),
630
+ prompt: existing.prompt ?? prompt ?? null,
631
+ }),
632
+ ),
447
633
  expectedStatusLock: existing.status_lock,
448
634
  });
449
635
  return sessionId;
@@ -601,7 +787,9 @@ export class UnifiedSessionPersistenceService {
601
787
  conversation_id: null,
602
788
  is_subagent: 1,
603
789
  prompt: message || `Team task for ${agentId}`,
604
- metadata_json: null,
790
+ metadata_json: stringifyMetadataJson(
791
+ metadataWithResolvedTitle({ prompt: message }),
792
+ ),
605
793
  transcript_path: transcriptPath,
606
794
  hook_path: hookPath,
607
795
  messages_path: messagesPath,
@@ -750,7 +938,9 @@ export class UnifiedSessionPersistenceService {
750
938
  }
751
939
  rows = await this.adapter.listSessions({ limit: scanLimit });
752
940
  }
753
- return rows.slice(0, requestedLimit);
941
+ return rows
942
+ .slice(0, requestedLimit)
943
+ .map((row) => this.applyResolvedTitleToRow(row));
754
944
  }
755
945
 
756
946
  async deleteSession(sessionId: string): Promise<{ deleted: boolean }> {
@@ -15,7 +15,7 @@ import type {
15
15
  SessionPromptConfig,
16
16
  SessionWorkspaceConfig,
17
17
  } from "@clinebot/shared";
18
- import type { ToolRoutingRule } from "../default-tools/model-tool-routing.js";
18
+ import type { ToolRoutingRule } from "../tools/model-tool-routing.js";
19
19
 
20
20
  export type CoreAgentMode = AgentMode;
21
21
 
@@ -1 +0,0 @@
1
- export * from "./index";