@johpaz/hive-sdk 0.1.6 → 0.2.0
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.
- package/CHANGELOG.md +135 -0
- package/README.md +11 -1
- package/package.json +18 -2
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +3 -3
- package/packages/core/src/agent/agent-loop.ts +115 -26
- package/packages/core/src/agent/capability-search.ts +2 -2
- package/packages/core/src/agent/catalog-selector.ts +4 -4
- package/packages/core/src/agent/compaction.ts +10 -9
- package/packages/core/src/agent/context-compiler.ts +58 -34
- package/packages/core/src/agent/conversation-store.ts +31 -7
- package/packages/core/src/agent/curator.ts +4 -4
- package/packages/core/src/agent/delegation-runtime.ts +5 -5
- package/packages/core/src/agent/goal-runner.ts +9 -9
- package/packages/core/src/agent/index.ts +1 -0
- package/packages/core/src/agent/llm-client.ts +98 -37
- package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
- package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
- package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
- package/packages/core/src/agent/llm-providers/groq.ts +1 -1
- package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
- package/packages/core/src/agent/llm-providers/interface.ts +2 -2
- package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
- package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
- package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
- package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
- package/packages/core/src/agent/llm-providers/nvidia.ts +1 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +9 -5
- package/packages/core/src/agent/llm-providers/openai.ts +1 -1
- package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
- package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
- package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
- package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
- package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
- package/packages/core/src/agent/playbook-selector.ts +4 -4
- package/packages/core/src/agent/prompt-builder.ts +5 -5
- package/packages/core/src/agent/proof-packet.ts +5 -5
- package/packages/core/src/agent/providers/index.ts +4 -4
- package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
- package/packages/core/src/agent/realtime-providers/index.ts +29 -0
- package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
- package/packages/core/src/agent/reflector.ts +6 -6
- package/packages/core/src/agent/run-store.ts +8 -8
- package/packages/core/src/agent/service.ts +10 -10
- package/packages/core/src/agent/skill-selector.ts +6 -6
- package/packages/core/src/agent/thread-id.ts +71 -0
- package/packages/core/src/agent/thread-store.ts +250 -0
- package/packages/core/src/agent/tool-selector.ts +7 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +1 -1
- package/packages/core/src/artifacts/store.ts +84 -3
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/channels/telegram.ts +1 -1
- package/packages/core/src/channels/webchat.ts +1 -1
- package/packages/core/src/config/loader.ts +14 -5
- package/packages/core/src/events/agent-bus.ts +3 -3
- package/packages/core/src/events/channel-narration.ts +3 -3
- package/packages/core/src/events/event-bus.ts +1 -1
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +5 -5
- package/packages/core/src/gateway/job-store.ts +5 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/mcp/MCPClient.ts +3 -3
- package/packages/core/src/mcp/hot-reload.ts +5 -5
- package/packages/core/src/mcp/tool-sync.ts +5 -5
- package/packages/core/src/mcp/transports/index.ts +2 -2
- package/packages/core/src/mcp/transports/sse.ts +1 -1
- package/packages/core/src/models/index.ts +36 -0
- package/packages/core/src/multimodal/index.ts +2 -2
- package/packages/core/src/multimodal/vision-service.ts +6 -6
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +6 -6
- package/packages/core/src/scheduler/integration.ts +9 -9
- package/packages/core/src/sessions/index.ts +266 -0
- package/packages/core/src/storage/bootstrap.ts +34 -8
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +32 -1
- package/packages/core/src/storage/crypto.ts +16 -2
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/onboarding.ts +6 -6
- package/packages/core/src/storage/reconcile.ts +5 -5
- package/packages/core/src/storage/seed.ts +103 -13
- package/packages/core/src/storage/usage.ts +3 -3
- package/packages/core/src/swarm/AgentExecutor.ts +2 -2
- package/packages/core/src/swarm/Coordinator.ts +8 -8
- package/packages/core/src/swarm/EventBridge.ts +2 -2
- package/packages/core/src/swarm/RoleSwarm.ts +234 -0
- package/packages/core/src/swarm/TaskGraph.ts +2 -2
- package/packages/core/src/swarm/index.ts +7 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
- package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/core/index.ts +2 -2
- package/packages/core/src/tools/cron/index.ts +4 -4
- package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
- package/packages/core/src/tools/web/artifact-read.ts +162 -0
- package/packages/core/src/tools/web/browser-backend.ts +141 -44
- package/packages/core/src/tools/web/browser-click.ts +2 -2
- package/packages/core/src/tools/web/browser-extract.ts +2 -2
- package/packages/core/src/tools/web/browser-navigate.ts +2 -2
- package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
- package/packages/core/src/tools/web/browser-script.ts +2 -2
- package/packages/core/src/tools/web/browser-service.ts +63 -384
- package/packages/core/src/tools/web/browser-session.ts +125 -0
- package/packages/core/src/tools/web/browser-type.ts +2 -2
- package/packages/core/src/tools/web/browser-wait.ts +2 -2
- package/packages/core/src/tools/web/computer-use.ts +553 -0
- package/packages/core/src/tools/web/index.ts +8 -1
- package/packages/core/src/tools/web/webview-backend.ts +460 -21
- package/packages/core/src/utils/index.ts +1 -0
- package/packages/core/src/utils/logger.ts +12 -4
- package/packages/core/src/utils/redact-binary.ts +17 -0
- package/packages/core/src/utils/toon.ts +1 -1
- package/packages/core/src/voice/index.ts +6 -6
- package/bun.lock +0 -859
- package/bunfig.toml +0 -9
- package/docs/API-AGENTS.md +0 -367
- package/docs/API-CONTEXT-COMPILER.md +0 -249
- package/docs/API-DAG-SCHEDULER.md +0 -273
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
- package/docs/API-WORKERS-EVENTS.md +0 -299
- package/docs/HIVE-HARNESS.md +0 -113
- package/docs/INDEX.md +0 -190
- package/docs/TEMPLATE-HIVE-APP.md +0 -360
- package/packages/cli/package.json +0 -17
- package/packages/cli/src/commands/create-app.test.ts +0 -180
- package/packages/core/package.json +0 -70
- package/packages/core/src/api/createAgent.test.ts +0 -160
- package/packages/core/src/canvas/canvas.test.ts +0 -36
- package/packages/core/src/channels/channels.test.ts +0 -18
- package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
- package/packages/core/src/gateway/gateway.test.ts +0 -38
- package/packages/core/src/memory/Scratchpad.test.ts +0 -68
- package/packages/core/src/scheduler/scheduler.test.ts +0 -15
- package/packages/core/src/skills/skills.test.ts +0 -62
- package/packages/core/src/swarm/swarm.test.ts +0 -24
- package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
- package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
- package/packages/core/src/tools/api/api-request.test.ts +0 -164
- package/packages/core/src/tools/web/browser-service.test.ts +0 -83
- package/packages/core/src/workers/workers.test.ts +0 -41
- package/scripts/bump-version.ts +0 -248
- package/scripts/generate-skill-bundle.ts +0 -108
- package/test/acceptance-checks.test.ts +0 -403
- package/test/agent-loop-terminal-synthesis.test.ts +0 -32
- package/test/browser-backend.test.ts +0 -308
- package/test/catalog-agents-stay-enabled.test.ts +0 -117
- package/test/causal-events.test.ts +0 -117
- package/test/compaction.test.ts +0 -105
- package/test/context-compiler.test.ts +0 -269
- package/test/curator.test.ts +0 -130
- package/test/durable-queue.test.ts +0 -114
- package/test/harness-barrel.test.ts +0 -64
- package/test/hive-helpers.test.ts +0 -130
- package/test/hivedb-search.test.ts +0 -189
- package/test/internal-turns.test.ts +0 -166
- package/test/job-idempotency.test.ts +0 -68
- package/test/job-retry-backoff.test.ts +0 -184
- package/test/job-store.test.ts +0 -381
- package/test/llm-retry.test.ts +0 -97
- package/test/memory-perf.test.ts +0 -774
- package/test/minimal-loadout.test.ts +0 -78
- package/test/model-catalog.test.ts +0 -105
- package/test/preload.ts +0 -12
- package/test/reflector.test.ts +0 -320
- package/test/retention-cap.test.ts +0 -91
- package/test/retired-capabilities-pruned.test.ts +0 -192
- package/test/run-store.test.ts +0 -355
- package/test/scratchpad.test.ts +0 -74
- package/test/secrets-durability.test.ts +0 -119
- package/test/seed-model-reseed.test.ts +0 -155
- package/test/setup-agent-seed.test.ts +0 -264
- package/test/tool-inventory.test.ts +0 -65
- package/test/tool-runtime.test.ts +0 -258
- package/test/tool-selector-runtime-tools.test.ts +0 -117
- package/test/toon.test.ts +0 -429
- package/tsconfig.json +0 -42
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions — la conversación de un usuario, como una sola cosa.
|
|
3
|
+
*
|
|
4
|
+
* Hasta acá "sesión" estaba repartida en cuatro capas que nadie unía:
|
|
5
|
+
*
|
|
6
|
+
* - `agent/thread-store.ts` — identidad y catálogo (`conversationThreads`)
|
|
7
|
+
* - `agent/conversation-store.ts`— los mensajes (`conversations`, por prefijo)
|
|
8
|
+
* - `agent/run-store.ts` — la ejecución: checkpoint, lease, resume
|
|
9
|
+
* - `state/store.ts` — un `Map` en memoria que muere con el proceso
|
|
10
|
+
*
|
|
11
|
+
* El resultado eran dos identificadores para lo mismo —`thread_id` para la
|
|
12
|
+
* conversación y `run_id` para la ejecución— y ninguna forma de preguntar "qué
|
|
13
|
+
* sesiones tiene este usuario" sin escanear mensajes.
|
|
14
|
+
*
|
|
15
|
+
* Este módulo **no agrega una tercera persistencia**: `Session` es una vista
|
|
16
|
+
* compuesta sobre las colecciones que ya existen. `Session.id` ES el `threadId`.
|
|
17
|
+
* Agregar una colección propia habría recreado exactamente la duplicación que
|
|
18
|
+
* este módulo viene a cerrar.
|
|
19
|
+
*
|
|
20
|
+
* `state/store.ts` queda para métricas efímeras; no es la sesión.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { ContentPart } from "../multimodal/types.ts"
|
|
24
|
+
import type { AgentRunDoc, ConversationThreadDoc } from "../storage/collections.ts"
|
|
25
|
+
import { updateDoc } from "../storage/hive.ts"
|
|
26
|
+
import {
|
|
27
|
+
addMessage,
|
|
28
|
+
getHistory,
|
|
29
|
+
type StoredMessage,
|
|
30
|
+
} from "../agent/conversation-store.ts"
|
|
31
|
+
import {
|
|
32
|
+
createWebConversation,
|
|
33
|
+
deleteThread,
|
|
34
|
+
ensureThread,
|
|
35
|
+
getThread,
|
|
36
|
+
listThreads,
|
|
37
|
+
mostRecentWebThread,
|
|
38
|
+
renameThread,
|
|
39
|
+
threadForChannel,
|
|
40
|
+
} from "../agent/thread-store.ts"
|
|
41
|
+
import {
|
|
42
|
+
deserializeCheckpoint,
|
|
43
|
+
findRunsByThread,
|
|
44
|
+
type RunCheckpointState,
|
|
45
|
+
} from "../agent/run-store.ts"
|
|
46
|
+
|
|
47
|
+
export * from "../agent/thread-id.ts"
|
|
48
|
+
|
|
49
|
+
/** El estado de ejecución más reciente del hilo, si alguna vez corrió. */
|
|
50
|
+
export interface SessionRun {
|
|
51
|
+
runId: string
|
|
52
|
+
agentId: string
|
|
53
|
+
status: AgentRunDoc["status"]
|
|
54
|
+
kind: AgentRunDoc["kind"]
|
|
55
|
+
updatedAt: number
|
|
56
|
+
/** true cuando quedó a medias y `resumeSession` puede retomarla. */
|
|
57
|
+
resumable: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Una sesión: la identidad del hilo más, opcionalmente, su última ejecución.
|
|
62
|
+
* Es una vista, no una fila: se compone de `conversationThreads` + `agentRuns`.
|
|
63
|
+
*/
|
|
64
|
+
export interface Session {
|
|
65
|
+
/** ES el threadId (`${user}/${canal}/${peer}`). */
|
|
66
|
+
id: string
|
|
67
|
+
userId: string
|
|
68
|
+
channel: string
|
|
69
|
+
peerId: string
|
|
70
|
+
peerKind: "direct" | "group"
|
|
71
|
+
title: string | null
|
|
72
|
+
archived: boolean
|
|
73
|
+
createdAt: number
|
|
74
|
+
lastMessageAt: number
|
|
75
|
+
messageCount: number
|
|
76
|
+
lastRun?: SessionRun
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Una ejecución interrumpida sigue siendo retomable; una terminada no. */
|
|
80
|
+
const RESUMABLE_STATUSES: ReadonlySet<AgentRunDoc["status"]> = new Set([
|
|
81
|
+
"interrupted",
|
|
82
|
+
"running",
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
function toSessionRun(run: AgentRunDoc): SessionRun {
|
|
86
|
+
return {
|
|
87
|
+
runId: run.id,
|
|
88
|
+
agentId: run.agent_id,
|
|
89
|
+
status: run.status,
|
|
90
|
+
kind: run.kind,
|
|
91
|
+
updatedAt: run.updated_at ?? run.created_at,
|
|
92
|
+
resumable: RESUMABLE_STATUSES.has(run.status),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toSession(doc: ConversationThreadDoc, lastRun?: SessionRun): Session {
|
|
97
|
+
return {
|
|
98
|
+
id: doc.id,
|
|
99
|
+
userId: doc.user_id,
|
|
100
|
+
channel: doc.channel,
|
|
101
|
+
peerId: doc.peer_id,
|
|
102
|
+
peerKind: doc.peer_kind,
|
|
103
|
+
title: doc.title,
|
|
104
|
+
archived: doc.archived,
|
|
105
|
+
createdAt: doc.created_at,
|
|
106
|
+
lastMessageAt: doc.last_message_at,
|
|
107
|
+
messageCount: doc.message_count,
|
|
108
|
+
...(lastRun ? { lastRun } : {}),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** La ejecución más reciente del hilo, o undefined si nunca corrió. */
|
|
113
|
+
async function latestRun(threadId: string): Promise<SessionRun | undefined> {
|
|
114
|
+
const runs = await findRunsByThread(threadId)
|
|
115
|
+
if (runs.length === 0) return undefined
|
|
116
|
+
const newest = runs.reduce((a, b) =>
|
|
117
|
+
(b.updated_at ?? b.created_at) > (a.updated_at ?? a.created_at) ? b : a,
|
|
118
|
+
)
|
|
119
|
+
return toSessionRun(newest)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface CreateSessionInput {
|
|
123
|
+
userId: string
|
|
124
|
+
channel: string
|
|
125
|
+
peerId: string
|
|
126
|
+
peerKind?: "direct" | "group"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Abre la sesión de un usuario en un canal, o devuelve la que ya existía.
|
|
131
|
+
* Idempotente: se puede llamar en cada turno.
|
|
132
|
+
*/
|
|
133
|
+
export async function createSession(input: CreateSessionInput): Promise<Session> {
|
|
134
|
+
const threadId = await ensureThread(input)
|
|
135
|
+
const doc = await getThread(threadId)
|
|
136
|
+
if (!doc) throw new Error(`No se pudo abrir la sesión ${threadId}`)
|
|
137
|
+
return toSession(doc)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Una conversación nueva de la web, con su propio id (no reusa la anterior). */
|
|
141
|
+
export async function createWebSession(userId: string, title?: string): Promise<Session> {
|
|
142
|
+
return toSession(await createWebConversation(userId, title))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function getSession(sessionId: string): Promise<Session | null> {
|
|
146
|
+
const doc = await getThread(sessionId)
|
|
147
|
+
if (!doc) return null
|
|
148
|
+
return toSession(doc, await latestRun(sessionId))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ListSessionsOptions {
|
|
152
|
+
channel?: string
|
|
153
|
+
includeArchived?: boolean
|
|
154
|
+
/**
|
|
155
|
+
* Adjunta la última ejecución de cada sesión. Cuesta una consulta por sesión,
|
|
156
|
+
* así que está apagado por defecto: la lista de conversaciones no lo necesita.
|
|
157
|
+
*/
|
|
158
|
+
withRuns?: boolean
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Las sesiones de un usuario, de la más reciente a la más vieja.
|
|
163
|
+
*
|
|
164
|
+
* Es la consulta que antes no existía: el estado en memoria se perdía en cada
|
|
165
|
+
* reinicio y los mensajes sólo se podían leer por prefijo de un hilo conocido.
|
|
166
|
+
*/
|
|
167
|
+
export async function listSessions(
|
|
168
|
+
userId: string,
|
|
169
|
+
opts?: ListSessionsOptions,
|
|
170
|
+
): Promise<Session[]> {
|
|
171
|
+
const docs = await listThreads(userId, {
|
|
172
|
+
channel: opts?.channel,
|
|
173
|
+
includeArchived: opts?.includeArchived,
|
|
174
|
+
})
|
|
175
|
+
if (!opts?.withRuns) return docs.map((d) => toSession(d))
|
|
176
|
+
return Promise.all(docs.map(async (d) => toSession(d, await latestRun(d.id))))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** La sesión de webchat en la que el usuario seguiría escribiendo. */
|
|
180
|
+
export async function mostRecentWebSession(userId: string): Promise<Session | null> {
|
|
181
|
+
const doc = await mostRecentWebThread(userId)
|
|
182
|
+
return doc ? toSession(doc) : null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* La sesión por la que hablarle a alguien en un canal cuando no venimos de un
|
|
187
|
+
* mensaje suyo (un aviso de tarea programada, por ejemplo). null si no hay.
|
|
188
|
+
*/
|
|
189
|
+
export async function sessionForChannel(
|
|
190
|
+
userId: string,
|
|
191
|
+
channel: string,
|
|
192
|
+
): Promise<string | null> {
|
|
193
|
+
return threadForChannel(userId, channel)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Agrega un mensaje y mantiene al día el catálogo (título, orden, contador):
|
|
198
|
+
* `addMessage` ya llama a `touchThread`, así que no hay que hacerlo acá.
|
|
199
|
+
*
|
|
200
|
+
* Ojo: esa actualización del catálogo es deliberadamente *fire-and-forget* —
|
|
201
|
+
* persistir el mensaje nunca se bloquea por el contador. En la práctica eso
|
|
202
|
+
* significa que `messageCount` y `title` son de consistencia eventual: leer la
|
|
203
|
+
* sesión inmediatamente después de escribir puede devolver el valor anterior.
|
|
204
|
+
* El historial (`getSessionHistory`) sí es consistente al instante.
|
|
205
|
+
*/
|
|
206
|
+
export async function appendMessage(
|
|
207
|
+
sessionId: string,
|
|
208
|
+
role: StoredMessage["role"],
|
|
209
|
+
content: string | ContentPart[],
|
|
210
|
+
opts?: Parameters<typeof addMessage>[3],
|
|
211
|
+
): Promise<number> {
|
|
212
|
+
return addMessage(sessionId, role, content, opts)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function getSessionHistory(
|
|
216
|
+
sessionId: string,
|
|
217
|
+
limit?: number,
|
|
218
|
+
): Promise<StoredMessage[]> {
|
|
219
|
+
return getHistory(sessionId, limit)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface ResumableSession {
|
|
223
|
+
run: SessionRun
|
|
224
|
+
checkpoint: RunCheckpointState
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* El trabajo a medias de una sesión, listo para retomar.
|
|
229
|
+
*
|
|
230
|
+
* Devuelve null cuando no hay nada que retomar — que es el caso normal. Un run
|
|
231
|
+
* `running` sin checkpoint tampoco sirve: murió antes de guardar estado.
|
|
232
|
+
*/
|
|
233
|
+
export async function resumeSession(sessionId: string): Promise<ResumableSession | null> {
|
|
234
|
+
const runs = await findRunsByThread(sessionId)
|
|
235
|
+
const candidates = runs
|
|
236
|
+
.filter((r) => RESUMABLE_STATUSES.has(r.status))
|
|
237
|
+
.sort((a, b) => (b.updated_at ?? b.created_at) - (a.updated_at ?? a.created_at))
|
|
238
|
+
|
|
239
|
+
for (const run of candidates) {
|
|
240
|
+
const checkpoint = deserializeCheckpoint(run)
|
|
241
|
+
if (checkpoint) return { run: toSessionRun(run), checkpoint }
|
|
242
|
+
}
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function renameSession(sessionId: string, title: string): Promise<void> {
|
|
247
|
+
return renameThread(sessionId, title)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Cierra la sesión sin perder nada: sale de la lista pero el historial queda.
|
|
252
|
+
* Para borrarla de verdad, `deleteSession`.
|
|
253
|
+
*/
|
|
254
|
+
export async function closeSession(sessionId: string): Promise<void> {
|
|
255
|
+
await updateDoc<ConversationThreadDoc>("conversationThreads", sessionId, { archived: true })
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Reabre una sesión archivada. */
|
|
259
|
+
export async function reopenSession(sessionId: string): Promise<void> {
|
|
260
|
+
await updateDoc<ConversationThreadDoc>("conversationThreads", sessionId, { archived: false })
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Borra la sesión entera: mensajes, resumen, notas y su fila del catálogo. */
|
|
264
|
+
export async function deleteSession(sessionId: string): Promise<void> {
|
|
265
|
+
return deleteThread(sessionId)
|
|
266
|
+
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* instead of `DELETE`+`INSERT`.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { getHiveDb } from "./hivedb";
|
|
15
|
-
import { col } from "./hive";
|
|
16
|
-
import { seedAllData } from "./seed";
|
|
17
|
-
import { ensureSecretsBackend } from "./crypto";
|
|
14
|
+
import { getHiveDb, getOpenHiveDb } from "./hivedb.ts";
|
|
15
|
+
import { col } from "./hive.ts";
|
|
16
|
+
import { seedAllData } from "./seed.ts";
|
|
17
|
+
import { ensureSecretsBackend } from "./crypto.ts";
|
|
18
|
+
import { ensureLegacyThread } from "../agent/thread-store.ts";
|
|
18
19
|
|
|
19
20
|
interface IndexSpec {
|
|
20
21
|
collection: string;
|
|
@@ -102,6 +103,9 @@ const INDEXES: IndexSpec[] = [
|
|
|
102
103
|
// Stage 7 — meeting
|
|
103
104
|
{ collection: "meetingSessions", field: "user_id" },
|
|
104
105
|
{ collection: "meetingSessions", field: "status" },
|
|
106
|
+
{ collection: "conversationThreads", field: "user_id" },
|
|
107
|
+
{ collection: "conversationThreads", field: "channel" },
|
|
108
|
+
{ collection: "conversationThreads", field: "archived" },
|
|
105
109
|
];
|
|
106
110
|
|
|
107
111
|
async function ensureIndexes(): Promise<void> {
|
|
@@ -123,7 +127,27 @@ async function ensureSeedData(): Promise<void> {
|
|
|
123
127
|
await seedAllData();
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
// Bootstrap state belongs to a specific database instance. A boolean that
|
|
131
|
+
// survives closeHiveDb() can report a closed/replaced database as ready when
|
|
132
|
+
// tests or long-running processes open a fresh instance in the same runtime.
|
|
133
|
+
let bootstrappedDb: Awaited<ReturnType<typeof getHiveDb>> | null = null;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Antes de la separación por canal todos los canales compartían un solo hilo cuyo
|
|
137
|
+
* `thread_id` era el `userId`. Esa conversación se registra —sin mover un solo
|
|
138
|
+
* mensaje— como una conversación más de la web, para que siga siendo legible desde
|
|
139
|
+
* la lista. Idempotente: no hace nada si ya está registrada o si no hay historial.
|
|
140
|
+
*/
|
|
141
|
+
async function ensureLegacyThreads(): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
const users = await col<{ id: string }>("users");
|
|
144
|
+
for (const user of await users.scan({})) {
|
|
145
|
+
await ensureLegacyThread(user.id);
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// Base sin usuarios todavía (onboarding pendiente) — nada que registrar.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
127
151
|
|
|
128
152
|
/**
|
|
129
153
|
* Idempotent entry point: opens the database, ensures indexes, reseeds the
|
|
@@ -131,7 +155,7 @@ let bootstrapped = false;
|
|
|
131
155
|
* gateway boot.
|
|
132
156
|
*/
|
|
133
157
|
export async function ensureHiveDb(): Promise<void> {
|
|
134
|
-
await getHiveDb();
|
|
158
|
+
const db = await getHiveDb();
|
|
135
159
|
await ensureIndexes();
|
|
136
160
|
// Mint the master key before anything can save an API key, so a fresh
|
|
137
161
|
// install is durable from the first keystroke rather than after a restart
|
|
@@ -139,13 +163,15 @@ export async function ensureHiveDb(): Promise<void> {
|
|
|
139
163
|
ensureSecretsBackend();
|
|
140
164
|
await ensureSeedData();
|
|
141
165
|
|
|
166
|
+
await ensureLegacyThreads();
|
|
167
|
+
|
|
142
168
|
const meta = await col<{ value: number }>("meta");
|
|
143
169
|
const existing = await meta.get("schemaVersion");
|
|
144
170
|
if (!existing) await meta.put("schemaVersion", { value: 1 }, { expectedVersion: 0 });
|
|
145
171
|
|
|
146
|
-
|
|
172
|
+
bootstrappedDb = db;
|
|
147
173
|
}
|
|
148
174
|
|
|
149
175
|
export function isBootstrapped(): boolean {
|
|
150
|
-
return
|
|
176
|
+
return bootstrappedDb !== null && bootstrappedDb === getOpenHiveDb();
|
|
151
177
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* co-located with the DB singleton accessor.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { getHiveDb } from "./hivedb"
|
|
9
|
+
import { getHiveDb } from "./hivedb.ts"
|
|
10
10
|
import type { Event, EventPattern } from "@johpaz/hive-db"
|
|
11
11
|
|
|
12
12
|
export type { Event as CausalEvent, EventPattern as CausalEventPattern }
|
|
@@ -40,7 +40,8 @@ export interface ModelDoc {
|
|
|
40
40
|
id: string
|
|
41
41
|
provider_id: string
|
|
42
42
|
name: string
|
|
43
|
-
|
|
43
|
+
/** "realtime": voz full-duplex (Live API), no confundir con stt+tts encadenados. */
|
|
44
|
+
model_type: "llm" | "stt" | "tts" | "vision" | "embedding" | "realtime"
|
|
44
45
|
context_window: number
|
|
45
46
|
capabilities: string | null
|
|
46
47
|
enabled: boolean
|
|
@@ -269,6 +270,14 @@ export interface RefreshTokenDoc {
|
|
|
269
270
|
export type TurnSource =
|
|
270
271
|
| "message" | "audio" | "a2ui" | "canvas" | "api"
|
|
271
272
|
| "task_complete" | "delegation_summary"
|
|
273
|
+
/** Turno hablado en una sesión de voz en tiempo real. A diferencia de "audio"
|
|
274
|
+
* (nota de voz: STT → turno → TTS) la voz la pone el modelo realtime, así que
|
|
275
|
+
* el turno nunca sintetiza audio al final. */
|
|
276
|
+
| "realtime"
|
|
277
|
+
/** Charla hablada que la voz YA resolvió sin tocar la colmena. Se guarda como
|
|
278
|
+
* contexto del hilo, nunca como pedido pendiente: tratarla como accionable
|
|
279
|
+
* hacía que el coordinador re-delegara todo lo que se había dicho en voz. */
|
|
280
|
+
| "realtime_chat"
|
|
272
281
|
|
|
273
282
|
/** What gets persisted — adds the synthetic marker for rows written before `source` existed. */
|
|
274
283
|
export type MessageSource = TurnSource | "legacy_internal"
|
|
@@ -298,6 +307,28 @@ export interface SummaryDoc {
|
|
|
298
307
|
last_message_id: string | null
|
|
299
308
|
}
|
|
300
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Una conversación: el registro de un `conversations.thread_id`, con lo necesario
|
|
312
|
+
* para listarla en la web sin escanear los mensajes. `id` ES el threadId
|
|
313
|
+
* (`${user}/${canal}/${peer}` — ver agent/thread-id.ts), salvo la fila del hilo
|
|
314
|
+
* legacy anterior a la separación por canal, cuyo id es el userId pelado.
|
|
315
|
+
*/
|
|
316
|
+
export interface ConversationThreadDoc {
|
|
317
|
+
id: string
|
|
318
|
+
user_id: string
|
|
319
|
+
channel: string
|
|
320
|
+
/** Contacto/grupo del canal, o el id de conversación en la web. */
|
|
321
|
+
peer_id: string
|
|
322
|
+
peer_kind: "direct" | "group"
|
|
323
|
+
/** null hasta el primer mensaje del usuario, de donde se deriva. */
|
|
324
|
+
title: string | null
|
|
325
|
+
/** Borrado suave: sigue en la base pero no se lista ni se reabre. */
|
|
326
|
+
archived: boolean
|
|
327
|
+
created_at: number
|
|
328
|
+
last_message_at: number
|
|
329
|
+
message_count: number
|
|
330
|
+
}
|
|
331
|
+
|
|
301
332
|
export interface TraceDoc {
|
|
302
333
|
id: string
|
|
303
334
|
thread_id: string
|
|
@@ -2,8 +2,8 @@ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto"
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
3
|
import * as path from "node:path"
|
|
4
4
|
import { getHiveDir } from "../config/loader.ts"
|
|
5
|
-
import { logger } from "../utils/logger"
|
|
6
|
-
import { col } from "./hive"
|
|
5
|
+
import { logger } from "../utils/logger.ts"
|
|
6
|
+
import { col } from "./hive.ts"
|
|
7
7
|
|
|
8
8
|
const log = logger.child("crypto")
|
|
9
9
|
const SERVICE = "hive"
|
|
@@ -79,6 +79,20 @@ async function _readCollectionSecret(name: string): Promise<string | null> {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Olvida si el keychain del SO respondió o no.
|
|
84
|
+
*
|
|
85
|
+
* `_keychainOk` se cachea a nivel de módulo a propósito: en un servidor sin
|
|
86
|
+
* libsecret cada lectura tiraría y no tiene sentido reintentarlo. El costo es
|
|
87
|
+
* que el resultado del primer sondeo vale para todo el proceso, y un test que
|
|
88
|
+
* sustituya `Bun.secrets` por un doble queda cortocircuitado si algo ya sondeó
|
|
89
|
+
* antes y falló. Resetear acá lo vuelve a dejar sin probar.
|
|
90
|
+
*/
|
|
91
|
+
export function resetKeychainProbe(): void {
|
|
92
|
+
_keychainOk = null
|
|
93
|
+
_mem.clear()
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
async function _keychainGet(name: string): Promise<string | null> {
|
|
83
97
|
if (_keychainOk === false) return null
|
|
84
98
|
try {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { HiveDB } from "@johpaz/hive-db";
|
|
17
17
|
import { getHiveDir } from "../config/loader.ts";
|
|
18
|
-
import { logger } from "../utils/logger";
|
|
18
|
+
import { logger } from "../utils/logger.ts";
|
|
19
19
|
|
|
20
20
|
const log = logger.child("hivedb");
|
|
21
21
|
|
|
@@ -48,6 +48,15 @@ export async function getHiveDb(): Promise<HiveDB> {
|
|
|
48
48
|
return opening;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Return the currently open database without opening one.
|
|
53
|
+
* Used by lifecycle-aware consumers that need to invalidate state after a
|
|
54
|
+
* close/reopen cycle.
|
|
55
|
+
*/
|
|
56
|
+
export function getOpenHiveDb(): HiveDB | null {
|
|
57
|
+
return db;
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
export function closeHiveDb(): void {
|
|
52
61
|
if (db) {
|
|
53
62
|
try {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.ts";
|
|
2
|
-
import { ensureHiveDb } from "./bootstrap";
|
|
3
|
-
import { col, toIndexable, fromIndexable } from "./hive";
|
|
2
|
+
import { ensureHiveDb } from "./bootstrap.ts";
|
|
3
|
+
import { col, toIndexable, fromIndexable } from "./hive.ts";
|
|
4
4
|
import {
|
|
5
5
|
storeProviderApiKey,
|
|
6
6
|
storeChannelConfig,
|
|
7
7
|
storeMcpEnv,
|
|
8
8
|
loadProviderApiKey,
|
|
9
9
|
loadChannelConfig,
|
|
10
|
-
} from "./crypto";
|
|
10
|
+
} from "./crypto.ts";
|
|
11
11
|
import { SkillLoader } from "../skills/index.ts";
|
|
12
12
|
import type {
|
|
13
13
|
UserDoc, ProviderDoc, ModelDoc, AgentDoc, ChannelDoc, McpServerDoc,
|
|
14
14
|
UserIdentityDoc, OnboardingProgressDoc, EthicsDoc, SkillDoc, ToolDoc,
|
|
15
|
-
} from "./collections";
|
|
16
|
-
import { normalizeUserEmail } from "./user-email";
|
|
15
|
+
} from "./collections.ts";
|
|
16
|
+
import { normalizeUserEmail } from "./user-email.ts";
|
|
17
17
|
|
|
18
18
|
export interface OnboardingSection {
|
|
19
19
|
step: "user" | "skills" | "ethics" | "tools" | "provider" | "model" | "channel" | "mcp" | "agent" | "complete";
|
|
@@ -423,7 +423,7 @@ export async function propagateCoordinatorModel(
|
|
|
423
423
|
providerId: string,
|
|
424
424
|
modelId: string,
|
|
425
425
|
): Promise<number> {
|
|
426
|
-
const { applyCoordinatorModel } = await import("../agent/agent-catalog");
|
|
426
|
+
const { applyCoordinatorModel } = await import("../agent/agent-catalog.ts");
|
|
427
427
|
const updated = await applyCoordinatorModel({ userId, providerId, modelId, overwrite: true });
|
|
428
428
|
if (updated > 0) {
|
|
429
429
|
log.info(`✅ ${updated} agente(s) sincronizados con el modelo del coordinador`, { providerId, modelId });
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* durable queue are activated.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { col, updateDoc } from "./hive";
|
|
16
|
-
import type { TaskRunDoc, MeetingSessionDoc, AgentRunDoc, JobDoc, TaskDoc } from "./collections";
|
|
17
|
-
import { logger } from "../utils/logger";
|
|
15
|
+
import { col, updateDoc } from "./hive.ts";
|
|
16
|
+
import type { TaskRunDoc, MeetingSessionDoc, AgentRunDoc, JobDoc, TaskDoc } from "./collections.ts";
|
|
17
|
+
import { logger } from "../utils/logger.ts";
|
|
18
18
|
import { reclaimOrInterrupt } from "../gateway/job-store.ts";
|
|
19
|
-
import { interruptRun } from "../agent/run-store";
|
|
20
|
-
import { sendToUserChannel } from "../gateway/channel-notify";
|
|
19
|
+
import { interruptRun } from "../agent/run-store.ts";
|
|
20
|
+
import { sendToUserChannel } from "../gateway/channel-notify.ts";
|
|
21
21
|
|
|
22
22
|
const log = logger.child("reconcile");
|
|
23
23
|
|