@clinebot/core 0.0.11 → 0.0.13

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-config-loader.d.ts +1 -1
  3. package/dist/agents/agent-config-parser.d.ts +5 -2
  4. package/dist/agents/index.d.ts +1 -1
  5. package/dist/agents/plugin-config-loader.d.ts +4 -0
  6. package/dist/agents/plugin-loader.d.ts +1 -0
  7. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  8. package/dist/agents/plugin-sandbox.d.ts +4 -0
  9. package/dist/index.node.d.ts +5 -0
  10. package/dist/index.node.js +685 -413
  11. package/dist/runtime/commands.d.ts +11 -0
  12. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  13. package/dist/runtime/skills.d.ts +13 -0
  14. package/dist/session/default-session-manager.d.ts +5 -0
  15. package/dist/session/session-config-builder.d.ts +4 -1
  16. package/dist/session/session-manager.d.ts +1 -0
  17. package/dist/session/session-service.d.ts +22 -22
  18. package/dist/session/unified-session-persistence-service.d.ts +12 -6
  19. package/dist/session/utils/helpers.d.ts +2 -2
  20. package/dist/session/utils/types.d.ts +9 -0
  21. package/dist/tools/definitions.d.ts +2 -2
  22. package/dist/tools/presets.d.ts +3 -3
  23. package/dist/tools/schemas.d.ts +15 -14
  24. package/dist/types/config.d.ts +5 -0
  25. package/dist/types/events.d.ts +22 -0
  26. package/package.json +5 -4
  27. package/src/agents/agent-config-loader.test.ts +2 -0
  28. package/src/agents/agent-config-loader.ts +1 -0
  29. package/src/agents/agent-config-parser.ts +12 -5
  30. package/src/agents/index.ts +1 -0
  31. package/src/agents/plugin-config-loader.test.ts +49 -0
  32. package/src/agents/plugin-config-loader.ts +10 -73
  33. package/src/agents/plugin-loader.test.ts +127 -1
  34. package/src/agents/plugin-loader.ts +72 -5
  35. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  36. package/src/agents/plugin-sandbox.test.ts +198 -1
  37. package/src/agents/plugin-sandbox.ts +223 -353
  38. package/src/index.node.ts +14 -0
  39. package/src/runtime/commands.test.ts +98 -0
  40. package/src/runtime/commands.ts +83 -0
  41. package/src/runtime/hook-file-hooks.test.ts +1 -1
  42. package/src/runtime/hook-file-hooks.ts +16 -6
  43. package/src/runtime/index.ts +10 -0
  44. package/src/runtime/runtime-builder.test.ts +67 -0
  45. package/src/runtime/runtime-builder.ts +70 -16
  46. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  47. package/src/runtime/skills.ts +44 -0
  48. package/src/runtime/workflows.ts +20 -29
  49. package/src/session/default-session-manager.e2e.test.ts +52 -33
  50. package/src/session/default-session-manager.test.ts +453 -1
  51. package/src/session/default-session-manager.ts +210 -12
  52. package/src/session/rpc-session-service.ts +14 -96
  53. package/src/session/session-config-builder.ts +2 -0
  54. package/src/session/session-manager.ts +1 -0
  55. package/src/session/session-service.ts +127 -64
  56. package/src/session/session-team-coordination.ts +30 -0
  57. package/src/session/unified-session-persistence-service.test.ts +3 -3
  58. package/src/session/unified-session-persistence-service.ts +159 -141
  59. package/src/session/utils/helpers.ts +22 -41
  60. package/src/session/utils/types.ts +10 -0
  61. package/src/storage/sqlite-team-store.ts +16 -5
  62. package/src/tools/definitions.test.ts +137 -8
  63. package/src/tools/definitions.ts +115 -70
  64. package/src/tools/presets.test.ts +2 -3
  65. package/src/tools/presets.ts +3 -3
  66. package/src/tools/schemas.ts +28 -28
  67. package/src/types/config.ts +5 -0
  68. package/src/types/events.ts +23 -0
@@ -24,35 +24,35 @@ import type {
24
24
  } from "./unified-session-persistence-service";
25
25
  import { UnifiedSessionPersistenceService } from "./unified-session-persistence-service";
26
26
 
27
- export interface SessionRowShape {
28
- session_id: string;
27
+ export interface SessionRow {
28
+ sessionId: string;
29
29
  source: string;
30
30
  pid: number;
31
- started_at: string;
32
- ended_at?: string | null;
33
- exit_code?: number | null;
31
+ startedAt: string;
32
+ endedAt?: string | null;
33
+ exitCode?: number | null;
34
34
  status: SessionStatus;
35
- status_lock?: number;
36
- interactive: number;
35
+ statusLock: number;
36
+ interactive: boolean;
37
37
  provider: string;
38
38
  model: string;
39
39
  cwd: string;
40
- workspace_root: string;
41
- team_name?: string | null;
42
- enable_tools: number;
43
- enable_spawn: number;
44
- enable_teams: number;
45
- parent_session_id?: string | null;
46
- parent_agent_id?: string | null;
47
- agent_id?: string | null;
48
- conversation_id?: string | null;
49
- is_subagent: number;
40
+ workspaceRoot: string;
41
+ teamName?: string | null;
42
+ enableTools: boolean;
43
+ enableSpawn: boolean;
44
+ enableTeams: boolean;
45
+ parentSessionId?: string | null;
46
+ parentAgentId?: string | null;
47
+ agentId?: string | null;
48
+ conversationId?: string | null;
49
+ isSubagent: boolean;
50
50
  prompt?: string | null;
51
- metadata_json?: string | null;
52
- transcript_path: string;
53
- hook_path: string;
54
- messages_path?: string | null;
55
- updated_at?: string;
51
+ metadata?: Record<string, unknown> | null;
52
+ transcriptPath: string;
53
+ hookPath: string;
54
+ messagesPath?: string | null;
55
+ updatedAt: string;
56
56
  }
57
57
 
58
58
  export interface CreateRootSessionInput {
@@ -110,6 +110,74 @@ export interface UpsertSubagentInput {
110
110
  rootSessionId?: string;
111
111
  }
112
112
 
113
+ // ── SQLite helpers ───────────────────────────────────────────────────
114
+
115
+ /** SELECT clause that aliases snake_case columns to camelCase SessionRow keys. */
116
+ const SESSION_SELECT_COLUMNS = `
117
+ session_id AS sessionId,
118
+ source,
119
+ pid,
120
+ started_at AS startedAt,
121
+ ended_at AS endedAt,
122
+ exit_code AS exitCode,
123
+ status,
124
+ status_lock AS statusLock,
125
+ interactive,
126
+ provider,
127
+ model,
128
+ cwd,
129
+ workspace_root AS workspaceRoot,
130
+ team_name AS teamName,
131
+ enable_tools AS enableTools,
132
+ enable_spawn AS enableSpawn,
133
+ enable_teams AS enableTeams,
134
+ parent_session_id AS parentSessionId,
135
+ parent_agent_id AS parentAgentId,
136
+ agent_id AS agentId,
137
+ conversation_id AS conversationId,
138
+ is_subagent AS isSubagent,
139
+ prompt,
140
+ metadata_json AS metadata,
141
+ transcript_path AS transcriptPath,
142
+ hook_path AS hookPath,
143
+ messages_path AS messagesPath,
144
+ updated_at AS updatedAt`;
145
+
146
+ /**
147
+ * Patch a raw SQLite result into a proper SessionRow.
148
+ * SQLite returns 0/1 for booleans and a JSON string for metadata —
149
+ * this converts them in-place to avoid allocating a second object.
150
+ */
151
+ function patchSqliteRow(raw: Record<string, unknown>): SessionRow {
152
+ raw.interactive = raw.interactive === 1;
153
+ raw.enableTools = raw.enableTools === 1;
154
+ raw.enableSpawn = raw.enableSpawn === 1;
155
+ raw.enableTeams = raw.enableTeams === 1;
156
+ raw.isSubagent = raw.isSubagent === 1;
157
+ const meta = raw.metadata;
158
+ if (typeof meta === "string" && meta.trim()) {
159
+ try {
160
+ const parsed = JSON.parse(meta) as unknown;
161
+ raw.metadata =
162
+ parsed && typeof parsed === "object" && !Array.isArray(parsed)
163
+ ? parsed
164
+ : null;
165
+ } catch {
166
+ raw.metadata = null;
167
+ }
168
+ } else {
169
+ raw.metadata = null;
170
+ }
171
+ return raw as unknown as SessionRow;
172
+ }
173
+
174
+ function stringifyMetadata(
175
+ metadata: Record<string, unknown> | null | undefined,
176
+ ): string | null {
177
+ if (!metadata || Object.keys(metadata).length === 0) return null;
178
+ return JSON.stringify(metadata);
179
+ }
180
+
113
181
  function reviveTeamStateDates(state: TeamRuntimeState): TeamRuntimeState {
114
182
  return {
115
183
  ...state,
@@ -327,7 +395,7 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
327
395
  return this.store.ensureSessionsDir();
328
396
  }
329
397
 
330
- async upsertSession(row: SessionRowShape): Promise<void> {
398
+ async upsertSession(row: SessionRow): Promise<void> {
331
399
  this.store.run(
332
400
  `INSERT OR REPLACE INTO sessions (
333
401
  session_id, source, pid, started_at, ended_at, exit_code, status, status_lock, interactive,
@@ -336,55 +404,51 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
336
404
  metadata_json, transcript_path, hook_path, messages_path, updated_at
337
405
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
338
406
  [
339
- row.session_id,
407
+ row.sessionId,
340
408
  row.source,
341
409
  row.pid,
342
- row.started_at,
343
- row.ended_at ?? null,
344
- row.exit_code ?? null,
410
+ row.startedAt,
411
+ row.endedAt ?? null,
412
+ row.exitCode ?? null,
345
413
  row.status,
346
- typeof row.status_lock === "number" ? row.status_lock : 0,
347
- row.interactive,
414
+ row.statusLock,
415
+ row.interactive ? 1 : 0,
348
416
  row.provider,
349
417
  row.model,
350
418
  row.cwd,
351
- row.workspace_root,
352
- row.team_name ?? null,
353
- row.enable_tools,
354
- row.enable_spawn,
355
- row.enable_teams,
356
- row.parent_session_id ?? null,
357
- row.parent_agent_id ?? null,
358
- row.agent_id ?? null,
359
- row.conversation_id ?? null,
360
- row.is_subagent,
419
+ row.workspaceRoot,
420
+ row.teamName ?? null,
421
+ row.enableTools ? 1 : 0,
422
+ row.enableSpawn ? 1 : 0,
423
+ row.enableTeams ? 1 : 0,
424
+ row.parentSessionId ?? null,
425
+ row.parentAgentId ?? null,
426
+ row.agentId ?? null,
427
+ row.conversationId ?? null,
428
+ row.isSubagent ? 1 : 0,
361
429
  row.prompt ?? null,
362
- row.metadata_json ?? null,
363
- row.transcript_path,
364
- row.hook_path,
365
- row.messages_path ?? null,
366
- row.updated_at ?? nowIso(),
430
+ stringifyMetadata(row.metadata),
431
+ row.transcriptPath,
432
+ row.hookPath,
433
+ row.messagesPath ?? null,
434
+ row.updatedAt,
367
435
  ],
368
436
  );
369
437
  }
370
438
 
371
- async getSession(sessionId: string): Promise<SessionRowShape | undefined> {
372
- const row = this.store.queryOne<SessionRowShape>(
373
- `SELECT session_id, source, pid, started_at, ended_at, exit_code, status, status_lock, interactive,
374
- provider, model, cwd, workspace_root, team_name, enable_tools, enable_spawn, enable_teams,
375
- parent_session_id, parent_agent_id, agent_id, conversation_id, is_subagent, prompt,
376
- metadata_json, transcript_path, hook_path, messages_path, updated_at
377
- FROM sessions WHERE session_id = ?`,
439
+ async getSession(sessionId: string): Promise<SessionRow | undefined> {
440
+ const row = this.store.queryOne<Record<string, unknown>>(
441
+ `SELECT ${SESSION_SELECT_COLUMNS} FROM sessions WHERE session_id = ?`,
378
442
  [sessionId],
379
443
  );
380
- return row ?? undefined;
444
+ return row ? patchSqliteRow(row) : undefined;
381
445
  }
382
446
 
383
447
  async listSessions(options: {
384
448
  limit: number;
385
449
  parentSessionId?: string;
386
450
  status?: string;
387
- }): Promise<SessionRowShape[]> {
451
+ }): Promise<SessionRow[]> {
388
452
  const whereClauses: string[] = [];
389
453
  const params: unknown[] = [];
390
454
  if (options.parentSessionId) {
@@ -397,17 +461,16 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
397
461
  }
398
462
  const where =
399
463
  whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
400
- return this.store.queryAll<SessionRowShape>(
401
- `SELECT session_id, source, pid, started_at, ended_at, exit_code, status, status_lock, interactive,
402
- provider, model, cwd, workspace_root, team_name, enable_tools, enable_spawn, enable_teams,
403
- parent_session_id, parent_agent_id, agent_id, conversation_id, is_subagent, prompt,
404
- metadata_json, transcript_path, hook_path, messages_path, updated_at
464
+ return this.store
465
+ .queryAll<Record<string, unknown>>(
466
+ `SELECT ${SESSION_SELECT_COLUMNS}
405
467
  FROM sessions
406
468
  ${where}
407
469
  ORDER BY started_at DESC
408
470
  LIMIT ?`,
409
- [...params, options.limit],
410
- );
471
+ [...params, options.limit],
472
+ )
473
+ .map(patchSqliteRow);
411
474
  }
412
475
 
413
476
  async updateSession(
@@ -459,9 +522,9 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
459
522
  fields.push("prompt = ?");
460
523
  params.push(input.prompt ?? null);
461
524
  }
462
- if (input.metadataJson !== undefined) {
525
+ if (input.metadata !== undefined) {
463
526
  fields.push("metadata_json = ?");
464
- params.push(input.metadataJson ?? null);
527
+ params.push(stringifyMetadata(input.metadata));
465
528
  }
466
529
  if (input.parentSessionId !== undefined) {
467
530
  fields.push("parent_session_id = ?");
@@ -481,7 +544,7 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
481
544
  }
482
545
  if (fields.length === 0) {
483
546
  const row = await this.getSession(input.sessionId);
484
- return { updated: !!row, statusLock: row?.status_lock ?? 0 };
547
+ return { updated: !!row, statusLock: row?.statusLock ?? 0 };
485
548
  }
486
549
 
487
550
  let statusLock = 0;
@@ -505,7 +568,7 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
505
568
  }
506
569
  if (input.expectedStatusLock === undefined) {
507
570
  const row = await this.getSession(input.sessionId);
508
- statusLock = row?.status_lock ?? 0;
571
+ statusLock = row?.statusLock ?? 0;
509
572
  }
510
573
  return { updated: true, statusLock };
511
574
  }
@@ -52,6 +52,36 @@ export async function dispatchTeamEventToBackend(
52
52
  invokeOptional: (method: string, ...args: unknown[]) => Promise<void>,
53
53
  ): Promise<void> {
54
54
  switch (event.type) {
55
+ case "run_progress":
56
+ await invokeOptional(
57
+ "onTeamTaskProgress",
58
+ rootSessionId,
59
+ event.run.agentId,
60
+ event.message,
61
+ { kind: event.message === "heartbeat" ? "heartbeat" : "progress" },
62
+ );
63
+ break;
64
+ case "agent_event":
65
+ if (
66
+ event.event.type === "content_start" &&
67
+ event.event.contentType === "text" &&
68
+ typeof event.event.text === "string"
69
+ ) {
70
+ const snippet = event.event.text
71
+ .replace(/\s+/g, " ")
72
+ .trim()
73
+ .slice(0, 120);
74
+ if (snippet) {
75
+ await invokeOptional(
76
+ "onTeamTaskProgress",
77
+ rootSessionId,
78
+ event.agentId,
79
+ snippet,
80
+ { kind: "text" },
81
+ );
82
+ }
83
+ }
84
+ break;
55
85
  case "task_start":
56
86
  await invokeOptional(
57
87
  "onTeamTaskStart",
@@ -45,11 +45,11 @@ describe("UnifiedSessionPersistenceService", () => {
45
45
  const rows = await service.listSessions(10);
46
46
  expect(rows).toHaveLength(1);
47
47
  expect(rows[0]).toMatchObject({
48
- session_id: sessionId,
48
+ sessionId,
49
49
  status: "failed",
50
- exit_code: 1,
50
+ exitCode: 1,
51
51
  });
52
- expect(rows[0]?.ended_at).toBeTruthy();
52
+ expect(rows[0]?.endedAt).toBeTruthy();
53
53
 
54
54
  const manifest = JSON.parse(
55
55
  readFileSync(artifacts.manifestPath, "utf8"),