@johpaz/hive-sdk 0.0.18 → 0.1.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 (41) hide show
  1. package/bun.lock +291 -1
  2. package/docs/HIVE-HARNESS.md +113 -0
  3. package/package.json +36 -2
  4. package/packages/cli/package.json +1 -1
  5. package/packages/core/package.json +13 -2
  6. package/packages/core/src/ace/Tracer.ts +1 -1
  7. package/packages/core/src/agent/AgentRunner.ts +12 -0
  8. package/packages/core/src/agent/ContextCompiler.ts +4 -4
  9. package/packages/core/src/agent/ConversationStore.ts +30 -20
  10. package/packages/core/src/agent/selectors/PlaybookSelector.ts +50 -76
  11. package/packages/core/src/agent/selectors/SkillSelector.ts +106 -262
  12. package/packages/core/src/agent/selectors/ToolSelector.ts +53 -89
  13. package/packages/core/src/auth/auth.ts +36 -23
  14. package/packages/core/src/harness/boot-id.ts +20 -0
  15. package/packages/core/src/harness/collections.ts +98 -0
  16. package/packages/core/src/harness/db-helpers.ts +87 -0
  17. package/packages/core/src/harness/durable-queue.ts +337 -0
  18. package/packages/core/src/harness/goal-verifier.ts +141 -0
  19. package/packages/core/src/harness/harness.test.ts +236 -0
  20. package/packages/core/src/harness/index.ts +34 -0
  21. package/packages/core/src/harness/job-store.ts +399 -0
  22. package/packages/core/src/harness/proof-packet.ts +69 -0
  23. package/packages/core/src/harness/reconcile.ts +149 -0
  24. package/packages/core/src/harness/run-epoch.ts +32 -0
  25. package/packages/core/src/harness/run-store.ts +334 -0
  26. package/packages/core/src/index.ts +6 -0
  27. package/packages/core/src/memory/Scratchpad.test.ts +23 -21
  28. package/packages/core/src/memory/Scratchpad.ts +41 -24
  29. package/packages/core/src/storage/HiveDBStorage.ts +64 -0
  30. package/packages/core/src/storage/SQLiteStorage.ts +7 -0
  31. package/packages/core/src/storage/hiveSeed.ts +308 -0
  32. package/packages/core/src/storage/hiveStorage.test.ts +38 -0
  33. package/packages/core/src/storage/index.ts +10 -0
  34. package/packages/core/src/storage/seed.ts +5 -1
  35. package/packages/core/src/storage/usage.ts +106 -167
  36. package/packages/core/src/tool-runtime/tool-runtime.test.ts +11 -3
  37. package/packages/core/src/tools/agents/get-available-models.ts +52 -56
  38. package/packages/core/src/tools/agents/index.ts +77 -60
  39. package/packages/core/src/tools/core/index.ts +106 -291
  40. package/packages/core/src/tools/meeting/index.ts +83 -93
  41. package/packages/core/src/utils/toon.ts +4 -4
@@ -2,20 +2,33 @@
2
2
  * Meeting Tools - 4 tools para transcripción de reuniones
3
3
  *
4
4
  * @category meeting
5
- *
6
- * - meeting_start — Inicia una sesión de transcripción
7
- * - meeting_add_segment — Transcribe un chunk de audio y lo agrega a la sesión
8
- * - meeting_stop — Detiene la sesión de transcripción
9
- * - meeting_report — Obtiene el transcript completo para que el agente genere el reporte
10
5
  */
11
6
 
12
7
  import type { Tool } from "../types.ts";
13
- import { getDb } from "../../storage/SQLiteStorage.ts";
8
+ import { getHiveDB } from "../../storage/HiveDBStorage.ts";
14
9
  import { voiceService, type AudioInput } from "../../voice/index.ts";
15
10
  import { logger } from "../../utils/logger.ts";
16
11
 
17
12
  const log = logger.child("meeting");
18
13
 
14
+ interface MeetingSession {
15
+ id: string;
16
+ title: string;
17
+ status: string;
18
+ sttModel: string;
19
+ startedAt: number;
20
+ stoppedAt?: number;
21
+ }
22
+
23
+ interface MeetingSegment {
24
+ sessionId: string;
25
+ seq: number;
26
+ speaker?: string;
27
+ text: string;
28
+ durationMs?: number;
29
+ createdAt: number;
30
+ }
31
+
19
32
  // ─── meeting_start ───────────────────────────────────────────────────────────
20
33
 
21
34
  export const meetingStartTool: Tool = {
@@ -43,30 +56,27 @@ export const meetingStartTool: Tool = {
43
56
  (params.stt_model as string) || "whisper-large-v3-turbo";
44
57
 
45
58
  try {
46
- const db = getDb();
47
- const result = db
48
- .query(
49
- `INSERT INTO meeting_sessions (title, stt_model)
50
- VALUES (?, ?)
51
- RETURNING id, title, status, stt_model, started_at`
52
- )
53
- .get(title, sttModel) as {
54
- id: string;
55
- title: string;
56
- status: string;
57
- stt_model: string;
58
- started_at: number;
59
+ const db = await getHiveDB();
60
+ const sessionsCol = db.collection<MeetingSession>("meeting_sessions");
61
+ const id = crypto.randomUUID();
62
+ const session: MeetingSession = {
63
+ id,
64
+ title,
65
+ status: "active",
66
+ sttModel,
67
+ startedAt: Math.floor(Date.now() / 1000),
59
68
  };
69
+ await sessionsCol.put(id, session);
60
70
 
61
- log.info(`Meeting session started: ${result.id} — "${title}"`);
71
+ log.info(`Meeting session started: ${id} — "${title}"`);
62
72
 
63
73
  return {
64
74
  ok: true,
65
- session_id: result.id,
66
- title: result.title,
67
- status: result.status,
68
- stt_model: result.stt_model,
69
- message: `✅ Sesión de reunión iniciada. ID: ${result.id}\nTítulo: "${title}"\nModelo STT: ${sttModel}`,
75
+ session_id: id,
76
+ title,
77
+ status: session.status,
78
+ stt_model: sttModel,
79
+ message: `✅ Sesión de reunión iniciada. ID: ${id}\nTítulo: "${title}"\nModelo STT: ${sttModel}`,
70
80
  };
71
81
  } catch (error) {
72
82
  log.error(`meeting_start error: ${(error as Error).message}`);
@@ -106,21 +116,19 @@ export const meetingAddSegmentTool: Tool = {
106
116
  execute: async (params) => {
107
117
  const sessionId = params.session_id as string;
108
118
  const audioBase64 = params.audio_base64 as string;
109
- const speaker = (params.speaker as string) || null;
119
+ const speaker = (params.speaker as string) || undefined;
110
120
  const mimeType = (params.mime_type as string) || "audio/webm";
111
121
 
112
122
  try {
113
- const db = getDb();
123
+ const db = await getHiveDB();
124
+ const sessionsCol = db.collection<MeetingSession>("meeting_sessions");
125
+ const segmentsCol = db.collection<MeetingSegment>("meeting_segments");
114
126
 
115
- const session = db
116
- .query(
117
- `SELECT id, stt_model, status FROM meeting_sessions WHERE id = ?`
118
- )
119
- .get(sessionId) as { id: string; stt_model: string; status: string } | undefined;
120
-
121
- if (!session) {
127
+ const sessionEntry = await sessionsCol.get(sessionId);
128
+ if (!sessionEntry) {
122
129
  return { ok: false, error: `Sesión ${sessionId} no encontrada.` };
123
130
  }
131
+ const session = sessionEntry.doc;
124
132
  if (session.status !== "active") {
125
133
  return {
126
134
  ok: false,
@@ -136,7 +144,7 @@ export const meetingAddSegmentTool: Tool = {
136
144
 
137
145
  let transcription: string;
138
146
  try {
139
- transcription = await voiceService.transcribe(audioInput, session.stt_model);
147
+ transcription = await voiceService.transcribe(audioInput, session.sttModel);
140
148
  } catch (transcribeError) {
141
149
  return {
142
150
  ok: false,
@@ -144,18 +152,19 @@ export const meetingAddSegmentTool: Tool = {
144
152
  };
145
153
  }
146
154
 
147
- const seqResult = db
148
- .query(
149
- `SELECT COALESCE(MAX(seq) + 1, 0) as next_seq FROM meeting_segments WHERE session_id = ?`
150
- )
151
- .get(sessionId) as { next_seq: number };
152
-
153
- const seq = seqResult.next_seq;
155
+ const entries = await segmentsCol.scan();
156
+ const seq = entries
157
+ .filter(e => e.doc.sessionId === sessionId)
158
+ .reduce((max, e) => Math.max(max, e.doc.seq), -1) + 1;
154
159
 
155
- db.query(
156
- `INSERT INTO meeting_segments (session_id, seq, speaker, text, duration_ms)
157
- VALUES (?, ?, ?, ?, NULL)`
158
- ).run(sessionId, seq, speaker, transcription);
160
+ const segmentId = `${sessionId}:${seq}`;
161
+ await segmentsCol.put(segmentId, {
162
+ sessionId,
163
+ seq,
164
+ speaker,
165
+ text: transcription,
166
+ createdAt: Math.floor(Date.now() / 1000),
167
+ });
159
168
 
160
169
  log.info(`Segment ${seq} added to session ${sessionId}: "${transcription.substring(0, 60)}..."`);
161
170
 
@@ -196,15 +205,15 @@ export const meetingStopTool: Tool = {
196
205
  const sessionId = params.session_id as string;
197
206
 
198
207
  try {
199
- const db = getDb();
200
-
201
- const session = db
202
- .query(`SELECT id, title, status FROM meeting_sessions WHERE id = ?`)
203
- .get(sessionId) as { id: string; title: string; status: string } | undefined;
208
+ const db = await getHiveDB();
209
+ const sessionsCol = db.collection<MeetingSession>("meeting_sessions");
210
+ const segmentsCol = db.collection<MeetingSegment>("meeting_segments");
204
211
 
205
- if (!session) {
212
+ const sessionEntry = await sessionsCol.get(sessionId);
213
+ if (!sessionEntry) {
206
214
  return { ok: false, error: `Sesión ${sessionId} no encontrada.` };
207
215
  }
216
+ const session = sessionEntry.doc;
208
217
  if (session.status === "stopped" || session.status === "report_ready") {
209
218
  return {
210
219
  ok: true,
@@ -213,22 +222,20 @@ export const meetingStopTool: Tool = {
213
222
  };
214
223
  }
215
224
 
216
- db.query(
217
- `UPDATE meeting_sessions SET status = 'stopped', stopped_at = unixepoch() WHERE id = ?`
218
- ).run(sessionId);
225
+ const stoppedAt = Math.floor(Date.now() / 1000);
226
+ await sessionsCol.put(sessionId, { ...session, status: "stopped", stoppedAt });
219
227
 
220
- const countResult = db
221
- .query(`SELECT COUNT(*) as count FROM meeting_segments WHERE session_id = ?`)
222
- .get(sessionId) as { count: number };
228
+ const entries = await segmentsCol.scan();
229
+ const count = entries.filter(e => e.doc.sessionId === sessionId).length;
223
230
 
224
- log.info(`Meeting session stopped: ${sessionId} — ${countResult.count} segments`);
231
+ log.info(`Meeting session stopped: ${sessionId} — ${count} segments`);
225
232
 
226
233
  return {
227
234
  ok: true,
228
235
  session_id: sessionId,
229
236
  title: session.title,
230
- segment_count: countResult.count,
231
- message: `⏹️ Sesión "${session.title}" detenida.\n${countResult.count} segmentos transcritos.\n\nPuedes pedir el reporte con: "Genera el reporte de la reunión ${sessionId}"`,
237
+ segment_count: count,
238
+ message: `⏹️ Sesión "${session.title}" detenida.\n${count} segmentos transcritos.\n\nPuedes pedir el reporte con: "Genera el reporte de la reunión ${sessionId}"`,
232
239
  };
233
240
  } catch (error) {
234
241
  log.error(`meeting_stop error: ${(error as Error).message}`);
@@ -257,38 +264,21 @@ export const meetingReportTool: Tool = {
257
264
  const sessionId = params.session_id as string;
258
265
 
259
266
  try {
260
- const db = getDb();
261
-
262
- const session = db
263
- .query(
264
- `SELECT id, title, status, stt_model, started_at, stopped_at
265
- FROM meeting_sessions WHERE id = ?`
266
- )
267
- .get(sessionId) as {
268
- id: string;
269
- title: string;
270
- status: string;
271
- stt_model: string;
272
- started_at: number;
273
- stopped_at: number | null;
274
- } | undefined;
275
-
276
- if (!session) {
267
+ const db = await getHiveDB();
268
+ const sessionsCol = db.collection<MeetingSession>("meeting_sessions");
269
+ const segmentsCol = db.collection<MeetingSegment>("meeting_segments");
270
+
271
+ const sessionEntry = await sessionsCol.get(sessionId);
272
+ if (!sessionEntry) {
277
273
  return { ok: false, error: `Sesión ${sessionId} no encontrada.` };
278
274
  }
275
+ const session = sessionEntry.doc;
279
276
 
280
- const segments = db
281
- .query(
282
- `SELECT seq, speaker, text, created_at
283
- FROM meeting_segments WHERE session_id = ?
284
- ORDER BY seq ASC`
285
- )
286
- .all(sessionId) as {
287
- seq: number;
288
- speaker: string | null;
289
- text: string;
290
- created_at: number;
291
- }[];
277
+ const entries = await segmentsCol.scan();
278
+ const segments = entries
279
+ .filter(e => e.doc.sessionId === sessionId)
280
+ .map(e => e.doc)
281
+ .sort((a, b) => a.seq - b.seq);
292
282
 
293
283
  if (segments.length === 0) {
294
284
  return {
@@ -301,9 +291,9 @@ export const meetingReportTool: Tool = {
301
291
  .map((s) => (s.speaker ? `[${s.speaker}]: ${s.text}` : s.text))
302
292
  .join("\n");
303
293
 
304
- const durationSec = session.stopped_at
305
- ? session.stopped_at - session.started_at
306
- : Math.floor(Date.now() / 1000) - session.started_at;
294
+ const durationSec = session.stoppedAt
295
+ ? session.stoppedAt - session.startedAt
296
+ : Math.floor(Date.now() / 1000) - session.startedAt;
307
297
  const durationMin = Math.floor(durationSec / 60);
308
298
  const durationSecRem = durationSec % 60;
309
299
 
@@ -113,7 +113,7 @@ export function formatToolResult(data: any, model?: string): string {
113
113
  const result = stringify(data, model)
114
114
 
115
115
  if (result.tokensSaved > 0 && model) {
116
- recordToonSavings({
116
+ void recordToonSavings({
117
117
  jsonBytes: result.jsonBytes,
118
118
  toonBytes: result.toonBytes,
119
119
  savedBytes: result.savedBytes,
@@ -135,7 +135,7 @@ export function formatMCPResponse(data: any, model?: string): string {
135
135
  const result = stringify(data, model)
136
136
 
137
137
  if (result.tokensSaved > 0 && model) {
138
- recordToonSavings({
138
+ void recordToonSavings({
139
139
  jsonBytes: result.jsonBytes,
140
140
  toonBytes: result.toonBytes,
141
141
  savedBytes: result.savedBytes,
@@ -157,7 +157,7 @@ export function formatSkillOutput(data: any, model?: string): string {
157
157
  const result = stringify(data, model)
158
158
 
159
159
  if (result.tokensSaved > 0 && model) {
160
- recordToonSavings({
160
+ void recordToonSavings({
161
161
  jsonBytes: result.jsonBytes,
162
162
  toonBytes: result.toonBytes,
163
163
  savedBytes: result.savedBytes,
@@ -179,7 +179,7 @@ export function formatContext(data: any, model?: string): string {
179
179
  const result = stringify(data, model)
180
180
 
181
181
  if (result.tokensSaved > 0 && model) {
182
- recordToonSavings({
182
+ void recordToonSavings({
183
183
  jsonBytes: result.jsonBytes,
184
184
  toonBytes: result.toonBytes,
185
185
  savedBytes: result.savedBytes,