@johpaz/hive-sdk 0.1.6 → 0.3.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 +441 -0
- package/README.md +21 -3
- package/package.json +27 -5
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +82 -25
- 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 +30 -10
- package/packages/core/src/agent/context-compiler.ts +63 -36
- package/packages/core/src/agent/conversation-store.ts +167 -9
- package/packages/core/src/agent/curator.ts +16 -7
- 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 +40 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
- 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 +22 -7
- 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 +39 -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 +38 -15
- 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 +293 -0
- package/packages/core/src/agent/tool-selector.ts +9 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +67 -2
- package/packages/core/src/artifacts/index.ts +15 -0
- package/packages/core/src/artifacts/store.ts +161 -5
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/canvas/index.ts +9 -0
- 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/ethics/EthicsGuard.ts +7 -1
- 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/index.ts +18 -0
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/events/tool-narration.ts +4 -0
- package/packages/core/src/gateway/channel-notify.ts +103 -6
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +18 -6
- package/packages/core/src/gateway/index.ts +3 -0
- package/packages/core/src/gateway/job-store.ts +11 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/harness/executors.ts +493 -0
- package/packages/core/src/harness/index.ts +12 -2
- package/packages/core/src/hooks/index.ts +203 -0
- package/packages/core/src/images/index.ts +161 -0
- package/packages/core/src/index.ts +1 -0
- 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 +51 -19
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/index.ts +13 -0
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +54 -27
- package/packages/core/src/scheduler/cron/expression.ts +165 -0
- package/packages/core/src/scheduler/cron/index.ts +10 -0
- package/packages/core/src/scheduler/cron/job.ts +339 -0
- package/packages/core/src/scheduler/cron/next-run.ts +121 -0
- package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
- package/packages/core/src/scheduler/index.ts +21 -3
- package/packages/core/src/scheduler/integration.ts +25 -14
- package/packages/core/src/scheduler/types.ts +3 -18
- package/packages/core/src/services/agents.ts +268 -0
- package/packages/core/src/services/cron.ts +257 -0
- package/packages/core/src/services/endpoints.ts +289 -0
- package/packages/core/src/services/ethics.ts +107 -0
- package/packages/core/src/services/images.ts +212 -0
- package/packages/core/src/services/index.ts +112 -0
- package/packages/core/src/services/mcp.ts +201 -0
- package/packages/core/src/services/memory.ts +133 -0
- package/packages/core/src/services/models.ts +179 -0
- package/packages/core/src/services/providers.ts +152 -0
- package/packages/core/src/services/setup.ts +222 -0
- package/packages/core/src/services/skills.ts +241 -0
- package/packages/core/src/services/swarms.ts +307 -0
- package/packages/core/src/services/tools.ts +106 -0
- package/packages/core/src/sessions/index.ts +268 -0
- package/packages/core/src/sessions/resolve.ts +108 -0
- package/packages/core/src/skills/SkillLoader.ts +8 -1
- package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
- package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
- package/packages/core/src/skills/bundled-data.generated.ts +110 -12
- package/packages/core/src/storage/bootstrap.ts +107 -12
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +138 -2
- package/packages/core/src/storage/crypto.ts +35 -4
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/index.ts +2 -1
- package/packages/core/src/storage/onboarding.ts +61 -45
- package/packages/core/src/storage/reconcile.ts +11 -6
- package/packages/core/src/storage/seed.ts +191 -23
- 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/swarm/types.ts +3 -18
- package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
- package/packages/core/src/tool-runtime/index.ts +129 -14
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/agents/index.ts +18 -60
- package/packages/core/src/tools/cli/index.ts +55 -0
- package/packages/core/src/tools/core/index.ts +52 -4
- package/packages/core/src/tools/cron/index.ts +8 -8
- package/packages/core/src/tools/images/index.ts +130 -0
- package/packages/core/src/tools/index.ts +14 -1
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
- package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
- package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
- 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
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
writeFileSync,
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { extname, join } from "node:path";
|
|
11
|
-
import { getHiveDir } from "../config/loader";
|
|
12
|
-
import { col, updateDoc } from "../storage/hive";
|
|
13
|
-
import type { ArtifactDoc } from "../storage/collections";
|
|
11
|
+
import { getHiveDir } from "../config/loader.ts";
|
|
12
|
+
import { col, updateDoc } from "../storage/hive.ts";
|
|
13
|
+
import type { ArtifactDoc } from "../storage/collections.ts";
|
|
14
14
|
|
|
15
15
|
const ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
16
|
|
|
@@ -52,6 +52,11 @@ export async function createArtifact(input: {
|
|
|
52
52
|
width?: number | null;
|
|
53
53
|
height?: number | null;
|
|
54
54
|
now?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Cuándo caduca. `null` = nunca, para lo que es del usuario y no basura
|
|
57
|
+
* transitoria. Por omisión, los 7 días de siempre.
|
|
58
|
+
*/
|
|
59
|
+
expiresAt?: number | null;
|
|
55
60
|
}): Promise<ArtifactDoc> {
|
|
56
61
|
const now = input.now ?? Date.now();
|
|
57
62
|
const id = randomUUID();
|
|
@@ -77,7 +82,7 @@ export async function createArtifact(input: {
|
|
|
77
82
|
height: input.height ?? null,
|
|
78
83
|
status: "active",
|
|
79
84
|
created_at: now,
|
|
80
|
-
expires_at: now + ARTIFACT_RETENTION_MS,
|
|
85
|
+
expires_at: input.expiresAt !== undefined ? input.expiresAt : now + ARTIFACT_RETENTION_MS,
|
|
81
86
|
expired_at: null,
|
|
82
87
|
};
|
|
83
88
|
|
|
@@ -91,6 +96,87 @@ export async function createArtifact(input: {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Reads an artifact's bytes straight off disk — for server-side consumers
|
|
101
|
+
* that don't need an HTTP round trip (routes/artifacts.ts is for the
|
|
102
|
+
* browser; channels/*.ts send()s use this directly to build a Telegram/
|
|
103
|
+
* Discord/Slack/WhatsApp photo attachment from the same file).
|
|
104
|
+
*/
|
|
105
|
+
export async function readArtifactBytes(
|
|
106
|
+
artifactId: string,
|
|
107
|
+
): Promise<{ bytes: Buffer; mimeType: string } | null> {
|
|
108
|
+
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
109
|
+
const entry = await artifacts.get(artifactId);
|
|
110
|
+
if (!entry || entry.doc.status !== "active") return null;
|
|
111
|
+
if (!existsSync(entry.doc.path)) return null;
|
|
112
|
+
return { bytes: readFileSync(entry.doc.path), mimeType: entry.doc.mime_type };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Beyond this an artifact stops being something an agent can read into a context window. */
|
|
116
|
+
const MAX_TEXT_ARTIFACT_BYTES = 25 * 1024 * 1024;
|
|
117
|
+
|
|
118
|
+
function looksBinary(mimeType: string, bytes: Buffer): boolean {
|
|
119
|
+
if (/^(image|audio|video)\//.test(mimeType)) return true;
|
|
120
|
+
if (mimeType === "application/octet-stream") return true;
|
|
121
|
+
// A NUL byte in the first KB is the classic "this is not text" tell — a
|
|
122
|
+
// mislabelled text/plain artifact would otherwise decode to garbage.
|
|
123
|
+
return bytes.subarray(0, 1024).includes(0x00);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type ArtifactTextResult = {
|
|
127
|
+
ok: boolean;
|
|
128
|
+
text?: string;
|
|
129
|
+
artifact?: ArtifactDoc;
|
|
130
|
+
error?: string;
|
|
131
|
+
status?: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Decodes a text artifact for in-process consumers that need its *content*,
|
|
136
|
+
* not just its metadata (inspectArtifact) or its raw bytes (readArtifactBytes).
|
|
137
|
+
*
|
|
138
|
+
* This exists because mcp-result-normalizer.ts moves oversized MCP text results
|
|
139
|
+
* out of the context window and hands the model an `artifact_ref` instead. Until
|
|
140
|
+
* this, nothing could read that reference back: the agent held a receipt it
|
|
141
|
+
* could not cash, burned its iterations guessing, and the turn died on an empty
|
|
142
|
+
* synthesis. Ownership and status checks mirror inspectArtifact's.
|
|
143
|
+
*/
|
|
144
|
+
export async function readArtifactText(
|
|
145
|
+
artifactId: string,
|
|
146
|
+
options: { userId?: string } = {},
|
|
147
|
+
): Promise<ArtifactTextResult> {
|
|
148
|
+
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
149
|
+
const entry = await artifacts.get(artifactId);
|
|
150
|
+
if (!entry) return { ok: false, error: "Artifact not found" };
|
|
151
|
+
const artifact = entry.doc;
|
|
152
|
+
|
|
153
|
+
if (options.userId && artifact.user_id && artifact.user_id !== options.userId) {
|
|
154
|
+
return { ok: false, error: "Artifact not accessible" };
|
|
155
|
+
}
|
|
156
|
+
if (artifact.status !== "active") {
|
|
157
|
+
return { ok: false, error: "Artifact binary has expired", status: artifact.status };
|
|
158
|
+
}
|
|
159
|
+
if (!existsSync(artifact.path)) {
|
|
160
|
+
return { ok: false, error: "Artifact binary is missing", status: "missing" };
|
|
161
|
+
}
|
|
162
|
+
if (artifact.size > MAX_TEXT_ARTIFACT_BYTES) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
error: `Artifact is too large to read as text (${artifact.size} bytes, limit ${MAX_TEXT_ARTIFACT_BYTES})`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const bytes = readFileSync(artifact.path);
|
|
170
|
+
if (looksBinary(detectedMime(bytes, artifact.mime_type), bytes)) {
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
error: `Artifact is binary (${detectedMime(bytes, artifact.mime_type)}) — use artifact_inspect for its metadata`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { ok: true, text: bytes.toString("utf-8"), artifact };
|
|
178
|
+
}
|
|
179
|
+
|
|
94
180
|
export async function inspectArtifact(
|
|
95
181
|
artifactId: string,
|
|
96
182
|
options: { userId?: string } = {},
|
|
@@ -141,12 +227,82 @@ export async function inspectArtifact(
|
|
|
141
227
|
};
|
|
142
228
|
}
|
|
143
229
|
|
|
230
|
+
export interface ListArtifactsOptions {
|
|
231
|
+
/** Filtra por tipo: `image`, `document`… */
|
|
232
|
+
kind?: string;
|
|
233
|
+
includeExpired?: boolean;
|
|
234
|
+
limit?: number;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Los artefactos de un usuario, del más reciente al más viejo.
|
|
239
|
+
*
|
|
240
|
+
* No existía, y sin esto una interfaz no puede mostrarle a alguien lo que tiene
|
|
241
|
+
* guardado — ni una galería de imágenes ni la lista de adjuntos de una
|
|
242
|
+
* conversación.
|
|
243
|
+
*/
|
|
244
|
+
export async function listArtifacts(
|
|
245
|
+
userId: string,
|
|
246
|
+
opts: ListArtifactsOptions = {},
|
|
247
|
+
): Promise<ArtifactDoc[]> {
|
|
248
|
+
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
249
|
+
const rows = await artifacts.findBy("user_id", userId);
|
|
250
|
+
return rows
|
|
251
|
+
.map((e) => e.doc)
|
|
252
|
+
.filter((d) => (opts.includeExpired ? true : d.status === "active"))
|
|
253
|
+
.filter((d) => (opts.kind ? d.kind === opts.kind : true))
|
|
254
|
+
.sort((a, b) => b.created_at - a.created_at)
|
|
255
|
+
.slice(0, opts.limit ?? Number.MAX_SAFE_INTEGER);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Cambia cuándo caduca un artefacto. `null` = conservarlo indefinidamente.
|
|
260
|
+
*
|
|
261
|
+
* Es lo que le da al usuario el control: puede marcar como permanente algo que
|
|
262
|
+
* nació temporal, o ponerle fecha a algo que ya no necesita.
|
|
263
|
+
*/
|
|
264
|
+
export async function setArtifactRetention(
|
|
265
|
+
artifactId: string,
|
|
266
|
+
expiresAt: number | null,
|
|
267
|
+
): Promise<ArtifactDoc | null> {
|
|
268
|
+
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
269
|
+
const entry = await artifacts.get(artifactId);
|
|
270
|
+
if (!entry) return null;
|
|
271
|
+
|
|
272
|
+
const doc: ArtifactDoc = { ...entry.doc, expires_at: expiresAt };
|
|
273
|
+
await artifacts.put(artifactId, doc, { expectedVersion: entry.version });
|
|
274
|
+
return doc;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Borra el artefacto y su archivo, sin esperar a que caduque.
|
|
279
|
+
*
|
|
280
|
+
* A diferencia de `expireArtifacts`, que marca `status: "expired"` y conserva la
|
|
281
|
+
* fila como registro, esto la elimina: es un borrado pedido por el usuario, y
|
|
282
|
+
* dejar el rastro de algo que pidió borrar sería lo contrario de lo que pidió.
|
|
283
|
+
*/
|
|
284
|
+
export async function deleteArtifact(artifactId: string): Promise<boolean> {
|
|
285
|
+
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
286
|
+
const entry = await artifacts.get(artifactId);
|
|
287
|
+
if (!entry) return false;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
if (existsSync(entry.doc.path)) unlinkSync(entry.doc.path);
|
|
291
|
+
} catch {
|
|
292
|
+
// El archivo puede haber desaparecido; la fila igual se va.
|
|
293
|
+
}
|
|
294
|
+
await artifacts.delete(artifactId);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
144
298
|
export async function expireArtifacts(now = Date.now()): Promise<{ expired: number }> {
|
|
145
299
|
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
146
300
|
const rows = await artifacts.scan({});
|
|
147
301
|
let expired = 0;
|
|
148
302
|
for (const row of rows) {
|
|
149
|
-
|
|
303
|
+
// `null` es explícito: el usuario pidió conservarlo. Saltarlo ANTES de
|
|
304
|
+
// comparar fechas evita que un `null > now` (que es false) lo borre.
|
|
305
|
+
if (row.doc.status !== "active" || row.doc.expires_at === null || row.doc.expires_at > now) continue;
|
|
150
306
|
try {
|
|
151
307
|
if (existsSync(row.doc.path)) unlinkSync(row.doc.path);
|
|
152
308
|
} catch {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { col, fromIndexable } from "../storage/hive"
|
|
2
|
-
import type { AgentDoc, McpServerDoc } from "../storage/collections"
|
|
1
|
+
import { col, fromIndexable } from "../storage/hive.ts"
|
|
2
|
+
import type { AgentDoc, McpServerDoc } from "../storage/collections.ts"
|
|
3
3
|
|
|
4
4
|
export interface CanvasEvent {
|
|
5
5
|
type: CanvasEventType
|
|
@@ -1 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas — el estado visual de un enjambre corriendo.
|
|
3
|
+
*
|
|
4
|
+
* `canvas-manager.ts` guarda y sirve el snapshot; `emitter.ts` es por donde el
|
|
5
|
+
* runtime publica los cambios (un nodo que empieza a pensar, una delegación que
|
|
6
|
+
* arranca o termina). Quien construya una UI sobre el SDK consume ambos.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
export * from "./canvas-manager.ts";
|
|
10
|
+
export * from "./emitter.ts";
|
|
@@ -3,7 +3,7 @@ import { BaseChannel, type ChannelConfig, type IncomingMessage, type OutboundMes
|
|
|
3
3
|
import { logger } from "../utils/logger.ts";
|
|
4
4
|
import { col, updateDoc } from "../storage/hive.ts";
|
|
5
5
|
import type { ChannelDoc, UserIdentityDoc } from "../storage/collections.ts";
|
|
6
|
-
import { resolveUserId } from "../storage/onboarding";
|
|
6
|
+
import { resolveUserId } from "../storage/onboarding.ts";
|
|
7
7
|
|
|
8
8
|
export interface TelegramConfig extends ChannelConfig {
|
|
9
9
|
botToken: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ServerWebSocket } from "bun";
|
|
2
2
|
import { BaseChannel, type ChannelConfig, type IncomingMessage, type OutboundMessage } from "./base.ts";
|
|
3
3
|
import { logger } from "../utils/logger.ts";
|
|
4
|
-
import { resolveUserId } from "../storage/onboarding";
|
|
4
|
+
import { resolveUserId } from "../storage/onboarding.ts";
|
|
5
5
|
|
|
6
6
|
export interface WebChatConfig extends ChannelConfig {
|
|
7
7
|
accountId?: string;
|
|
@@ -20,7 +20,14 @@ export function loadEnv(hiveDir: string): void {
|
|
|
20
20
|
const [key, ...valueParts] = trimmed.split("=");
|
|
21
21
|
if (key && valueParts.length > 0) {
|
|
22
22
|
const value = valueParts.join("=").trim().replace(/^['"]|['"]$/g, "");
|
|
23
|
-
|
|
23
|
+
const normalizedKey = key.trim();
|
|
24
|
+
// Explicit process environment values (for example the random
|
|
25
|
+
// port assigned by the desktop shell) must take precedence over the
|
|
26
|
+
// persisted .env file. This follows dotenv conventions and prevents
|
|
27
|
+
// a previous CLI port from breaking desktop startup.
|
|
28
|
+
if (process.env[normalizedKey] === undefined) {
|
|
29
|
+
process.env[normalizedKey] = value;
|
|
30
|
+
}
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
} catch (e) {
|
|
@@ -120,11 +127,13 @@ const BrowserConfigSchema = z.object({
|
|
|
120
127
|
headless: z.boolean().optional(),
|
|
121
128
|
timeoutMs: z.number().optional(),
|
|
122
129
|
sessionName: z.string().optional(),
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// sólo si hay motor. Lo pisa HIVE_BROWSER_BACKEND.
|
|
130
|
+
// Queda un solo backend: Bun.WebView in-process. La clave sobrevive para no
|
|
131
|
+
// romper configs viejas —"agent-browser" se acepta, avisa y usa el WebView—
|
|
132
|
+
// y se puede quitar sin más. Lo pisa HIVE_BROWSER_BACKEND.
|
|
127
133
|
backend: z.enum(["agent-browser", "webview", "auto"]).optional(),
|
|
134
|
+
// Guarda las cookies para que los logins sobrevivan a un reinicio. Default
|
|
135
|
+
// activo; apagarlo hace que cada arranque empiece sin historia.
|
|
136
|
+
persistSession: z.boolean().optional(),
|
|
128
137
|
});
|
|
129
138
|
|
|
130
139
|
const CanvasConfigSchema = z.object({
|
|
@@ -37,8 +37,12 @@ export class EthicsGuard {
|
|
|
37
37
|
* Con `agentRole` filtra por las que lo declaran en `applicable_to`; si
|
|
38
38
|
* ninguna coincide devuelve todas, para no dejar al agente sin capa por un
|
|
39
39
|
* `applicable_to` mal cargado.
|
|
40
|
+
*
|
|
41
|
+
* `userId` acota lo aprendido a quien corresponde: entran las globales
|
|
42
|
+
* (`user_id === ""`, sembradas con el producto) y las que salieron de las
|
|
43
|
+
* trazas de ese mismo usuario. Omitirlo deja sólo las globales.
|
|
40
44
|
*/
|
|
41
|
-
async getRules(agentRole?: string): Promise<EthicsRule[]> {
|
|
45
|
+
async getRules(agentRole?: string, userId?: string): Promise<EthicsRule[]> {
|
|
42
46
|
const playbookCol = await col<PlaybookDoc>("playbook");
|
|
43
47
|
const all = (await playbookCol.scan({}))
|
|
44
48
|
.map((e) => ({
|
|
@@ -48,8 +52,10 @@ export class EthicsGuard {
|
|
|
48
52
|
applicable_to: e.doc.applicable_to,
|
|
49
53
|
helpful_count: e.doc.helpful_count ?? 0,
|
|
50
54
|
active: e.doc.active,
|
|
55
|
+
user_id: e.doc.user_id ?? "",
|
|
51
56
|
}))
|
|
52
57
|
.filter((r) => r.active && r.category === RESPONSE_QUALITY)
|
|
58
|
+
.filter((r) => r.user_id === "" || r.user_id === (userId ?? ""))
|
|
53
59
|
.sort(byUsefulness);
|
|
54
60
|
|
|
55
61
|
if (!agentRole) return all;
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { EventEmitter } from "events";
|
|
13
|
-
import { logger } from "../utils/logger";
|
|
14
|
-
import { col, nextId, toIndexable, fromIndexable, BROADCAST } from "../storage/hive";
|
|
15
|
-
import type { AgentBusMessageDoc, TaskDoc } from "../storage/collections";
|
|
13
|
+
import { logger } from "../utils/logger.ts";
|
|
14
|
+
import { col, nextId, toIndexable, fromIndexable, BROADCAST } from "../storage/hive.ts";
|
|
15
|
+
import type { AgentBusMessageDoc, TaskDoc } from "../storage/collections.ts";
|
|
16
16
|
|
|
17
17
|
const log = logger.child("agent-bus");
|
|
18
18
|
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// messages before the actual answer — noise for the user and a rate-limit /
|
|
7
7
|
// ban risk on WhatsApp. This module decides what reaches those channels.
|
|
8
8
|
|
|
9
|
-
import { col } from "../storage/hive";
|
|
10
|
-
import type { ChannelDoc, NarrationEventDoc } from "../storage/collections";
|
|
11
|
-
import { logger } from "../utils/logger";
|
|
9
|
+
import { col } from "../storage/hive.ts";
|
|
10
|
+
import type { ChannelDoc, NarrationEventDoc } from "../storage/collections.ts";
|
|
11
|
+
import { logger } from "../utils/logger.ts";
|
|
12
12
|
|
|
13
13
|
const log = logger.child("narration:channel");
|
|
14
14
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events — el bus de eventos del runtime y la narración de lo que hace un agente.
|
|
3
|
+
*
|
|
4
|
+
* Dos buses con propósitos distintos:
|
|
5
|
+
* - `eventBus`: eventos del proceso, tipados (`event-bus.ts`).
|
|
6
|
+
* - `agentBus`: mensajería entre workers de un enjambre, con respaldo
|
|
7
|
+
* persistente en HiveDB para que un worker lea lo que le dejaron mientras
|
|
8
|
+
* no estaba (`agent-bus.ts`).
|
|
9
|
+
*
|
|
10
|
+
* La narración traduce una tool call a una frase que se le puede mostrar a
|
|
11
|
+
* alguien ("Buscando en la web...") en vez del nombre crudo de la tool.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export * from "./event-bus.ts";
|
|
15
|
+
export * from "./agent-bus.ts";
|
|
16
|
+
export * from "./narration.ts";
|
|
17
|
+
export * from "./tool-narration.ts";
|
|
18
|
+
export * from "./channel-narration.ts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { col } from "../storage/hive";
|
|
3
|
-
import type { NarrationEventDoc } from "../storage/collections";
|
|
4
|
-
import { logger } from "../utils/logger";
|
|
2
|
+
import { col } from "../storage/hive.ts";
|
|
3
|
+
import type { NarrationEventDoc } from "../storage/collections.ts";
|
|
4
|
+
import { logger } from "../utils/logger.ts";
|
|
5
5
|
|
|
6
6
|
const log = logger.child("narration");
|
|
7
7
|
|
|
@@ -43,7 +43,11 @@ const TOOL_NARRATIONS: Record<string, string> = {
|
|
|
43
43
|
browser_click: "Haciendo clic...",
|
|
44
44
|
browser_type: "Escribiendo en la página...",
|
|
45
45
|
browser_screenshot: "Tomando captura de pantalla...",
|
|
46
|
+
computer_use_task: "Operando el navegador...",
|
|
46
47
|
browser_extract: "Extrayendo información de la página...",
|
|
48
|
+
// Artefactos
|
|
49
|
+
artifact_inspect: "Verificando el archivo generado...",
|
|
50
|
+
artifact_read: "Leyendo el resultado completo...",
|
|
47
51
|
// Canvas
|
|
48
52
|
canvas_add_node: "Actualizando canvas...",
|
|
49
53
|
canvas_update: "Actualizando canvas...",
|
|
@@ -1,16 +1,110 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Channel Notify —
|
|
3
|
-
*
|
|
2
|
+
* Channel Notify — el camino de salida hacia el usuario.
|
|
3
|
+
*
|
|
4
|
+
* Esto era un stub que sólo hacía `console.log`, y estaba en el camino real: la
|
|
5
|
+
* tool `notify`, los reportes de progreso, el aviso de que una tarea programada
|
|
6
|
+
* terminó, el de un turno interrumpido por un crash y el de compactación pasan
|
|
7
|
+
* todos por acá. Es decir, un agente sobre el SDK **no podía hablarle al
|
|
8
|
+
* usuario por ningún canal** — mientras `channels/manager.ts` tenía adaptadores
|
|
9
|
+
* funcionales de Slack, Discord, Telegram y WhatsApp, sin nada que los conectara.
|
|
10
|
+
*
|
|
11
|
+
* El cableado es explícito y opcional: la app registra su `ChannelManager` con
|
|
12
|
+
* `setChannelManager()`. Sin registro se conserva el comportamiento anterior
|
|
13
|
+
* —un log— porque un proceso que no maneja canales (un script, un test) no
|
|
14
|
+
* debería fallar por intentar notificar.
|
|
15
|
+
*
|
|
16
|
+
* Resolver a quién enviar es la otra mitad. `ChannelManager.send` necesita un
|
|
17
|
+
* `sessionId`, que es el contacto o grupo dentro del canal. Se obtiene del
|
|
18
|
+
* `threadId` (`${userId}/${canal}/${peer}`) cuando viene, y si no, buscando la
|
|
19
|
+
* conversación de ese usuario en ese canal. Sin eso el mensaje no sabe a qué
|
|
20
|
+
* chat volver.
|
|
4
21
|
*/
|
|
5
22
|
|
|
23
|
+
import { logger } from "../utils/logger.ts";
|
|
24
|
+
import { parseThreadId } from "../agent/thread-id.ts";
|
|
25
|
+
import { threadForChannel, listThreads } from "../agent/thread-store.ts";
|
|
26
|
+
|
|
27
|
+
const log = logger.child("channel-notify");
|
|
28
|
+
|
|
29
|
+
/** Lo mínimo que se necesita de un ChannelManager, para no atarse a su clase. */
|
|
30
|
+
export interface ChannelSender {
|
|
31
|
+
send(channelName: string, sessionId: string, message: unknown, accountId?: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let _sender: ChannelSender | null = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Conecta el manager de canales. Llamalo una vez al arrancar, después de
|
|
38
|
+
* `channelManager.initialize()`.
|
|
39
|
+
*/
|
|
40
|
+
export function setChannelManager(sender: ChannelSender | null): void {
|
|
41
|
+
_sender = sender;
|
|
42
|
+
log.info(sender ? "canales conectados: las notificaciones salen de verdad" : "canales desconectados");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getChannelManager(): ChannelSender | null {
|
|
46
|
+
return _sender;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A qué conversación del canal enviar.
|
|
51
|
+
*
|
|
52
|
+
* El `threadId` ya lleva el peer adentro, así que si viene se usa. Si no, se
|
|
53
|
+
* busca el hilo del usuario en ese canal; y como último recurso se usa el
|
|
54
|
+
* `userId`, que es lo que hacían las instalaciones anteriores a la separación
|
|
55
|
+
* por canal.
|
|
56
|
+
*/
|
|
57
|
+
async function resolveSessionId(
|
|
58
|
+
channel: string,
|
|
59
|
+
userId: string,
|
|
60
|
+
threadId?: string,
|
|
61
|
+
): Promise<string | null> {
|
|
62
|
+
if (threadId) {
|
|
63
|
+
const parts = parseThreadId(threadId);
|
|
64
|
+
if (parts?.peerId) return parts.peerId;
|
|
65
|
+
}
|
|
66
|
+
// `threadForChannel` mira `userIdentities`, que es el registro canónico de
|
|
67
|
+
// "por dónde se alcanza a este usuario".
|
|
68
|
+
const delCanal = await threadForChannel(userId, channel).catch(() => null);
|
|
69
|
+
if (delCanal) {
|
|
70
|
+
const parts = parseThreadId(delCanal);
|
|
71
|
+
if (parts?.peerId) return parts.peerId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Si no hay identidad registrada pero sí una conversación abierta en ese
|
|
75
|
+
// canal, ahí es donde responder: es evidencia igual de válida de dónde está
|
|
76
|
+
// el usuario, y evita perder el aviso por un registro que nadie llenó.
|
|
77
|
+
const hilos = await listThreads(userId, { channel }).catch(() => []);
|
|
78
|
+
const reciente = hilos[0];
|
|
79
|
+
if (reciente) {
|
|
80
|
+
const parts = parseThreadId(reciente.id);
|
|
81
|
+
if (parts?.peerId) return parts.peerId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return userId || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
6
87
|
export async function notifyChannel(
|
|
7
88
|
channel: string,
|
|
8
89
|
userId: string,
|
|
9
90
|
message: string,
|
|
10
91
|
opts?: { threadId?: string; metadata?: Record<string, unknown> }
|
|
11
92
|
): Promise<void> {
|
|
12
|
-
|
|
13
|
-
|
|
93
|
+
if (!_sender) {
|
|
94
|
+
// Sin canales conectados no es un error: hay procesos que legítimamente no
|
|
95
|
+
// los tienen. Pero conviene que se note, porque un `notify` que no llega es
|
|
96
|
+
// silencioso por naturaleza.
|
|
97
|
+
log.warn(`sin ChannelManager conectado — el mensaje para ${channel} no sale: ${message.slice(0, 80)}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const sessionId = await resolveSessionId(channel, userId, opts?.threadId);
|
|
102
|
+
if (!sessionId) {
|
|
103
|
+
log.warn(`no pude resolver a qué conversación de ${channel} enviarle a ${userId}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await _sender.send(channel, sessionId, message);
|
|
14
108
|
}
|
|
15
109
|
|
|
16
110
|
export async function sendToUserChannel(
|
|
@@ -23,15 +117,18 @@ export async function sendToUserChannel(
|
|
|
23
117
|
await notifyChannel(channel, userId, message, opts);
|
|
24
118
|
return { ok: true };
|
|
25
119
|
} catch (err) {
|
|
120
|
+
// Un canal caído no debe tumbar el turno que estaba notificando.
|
|
121
|
+
log.warn(`falló el envío a ${channel}: ${(err as Error).message}`);
|
|
26
122
|
return { ok: false, error: (err as Error).message };
|
|
27
123
|
}
|
|
28
124
|
}
|
|
29
125
|
|
|
30
126
|
export async function broadcastNotification(
|
|
31
127
|
channels: string[],
|
|
32
|
-
message: string
|
|
128
|
+
message: string,
|
|
129
|
+
userId = "",
|
|
33
130
|
): Promise<void> {
|
|
34
131
|
for (const channel of channels) {
|
|
35
|
-
await notifyChannel(channel,
|
|
132
|
+
await notifyChannel(channel, userId, message).catch(() => {});
|
|
36
133
|
}
|
|
37
134
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { col } from "../storage/hive";
|
|
1
|
+
import { col } from "../storage/hive.ts";
|
|
2
2
|
import type {
|
|
3
3
|
DelegationGroupDoc,
|
|
4
4
|
DelegationGroupOutcome,
|
|
5
|
-
} from "../storage/collections";
|
|
6
|
-
import { logger } from "../utils/logger";
|
|
7
|
-
import { publishNarration } from "../events/narration";
|
|
5
|
+
} from "../storage/collections.ts";
|
|
6
|
+
import { logger } from "../utils/logger.ts";
|
|
7
|
+
import { publishNarration } from "../events/narration.ts";
|
|
8
8
|
|
|
9
9
|
const log = logger.child("delegation-groups");
|
|
10
10
|
const MAX_RETRIES = 8;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* is preserved, while all queue state is mirrored to the DB.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { logger } from "../utils/logger";
|
|
17
|
+
import { logger } from "../utils/logger.ts";
|
|
18
18
|
import {
|
|
19
19
|
createJob,
|
|
20
20
|
claimJob,
|
|
@@ -30,9 +30,9 @@ import {
|
|
|
30
30
|
loadJobRetryPolicy,
|
|
31
31
|
DEFAULT_JOB_RETRY_POLICY,
|
|
32
32
|
type JobRetryPolicy,
|
|
33
|
-
} from "./job-store";
|
|
34
|
-
import type { JobDoc } from "../storage/collections";
|
|
35
|
-
import { getBootId } from "../storage/boot-id";
|
|
33
|
+
} from "./job-store.ts";
|
|
34
|
+
import type { JobDoc } from "../storage/collections.ts";
|
|
35
|
+
import { getBootId } from "../storage/boot-id.ts";
|
|
36
36
|
|
|
37
37
|
const log = logger.child("durable-queue");
|
|
38
38
|
|
|
@@ -76,6 +76,17 @@ export function registerExecutor(type: JobType, executor: JobExecutor): void {
|
|
|
76
76
|
log.info(`[registerExecutor] Registered executor for type=${type}`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Los tipos de job que este proceso sabe ejecutar.
|
|
81
|
+
*
|
|
82
|
+
* El registro era privado, así que no había forma de comprobar desde fuera si
|
|
83
|
+
* un tipo quedó cableado — y un job encolado sin ejecutor no falla al encolarse
|
|
84
|
+
* sino al tomarse, que es tarde y lejos de donde está el error.
|
|
85
|
+
*/
|
|
86
|
+
export function getRegisteredExecutorTypes(): JobType[] {
|
|
87
|
+
return [...executors.keys()];
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
export interface JobTerminalOutcome {
|
|
80
91
|
ok: boolean;
|
|
81
92
|
result?: unknown;
|
|
@@ -92,7 +103,8 @@ export function registerTerminalHook(type: JobType, hook: JobTerminalHook): void
|
|
|
92
103
|
terminalHooks.set(type, hook);
|
|
93
104
|
}
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
/** Exported so job-store.ts's reclaimOrInterrupt can fire it too (dynamic import there — see its call site). */
|
|
107
|
+
export async function runTerminalHook(job: JobDoc, outcome: JobTerminalOutcome): Promise<void> {
|
|
96
108
|
const hook = terminalHooks.get(job.type);
|
|
97
109
|
if (!hook) return;
|
|
98
110
|
try {
|
|
@@ -287,7 +299,7 @@ export class DurableLaneQueue {
|
|
|
287
299
|
let leasedHere = true;
|
|
288
300
|
const leaseRenewer = setInterval(async () => {
|
|
289
301
|
if (leasedHere) {
|
|
290
|
-
const { renewLease } = await import("./job-store");
|
|
302
|
+
const { renewLease } = await import("./job-store.ts");
|
|
291
303
|
await renewLease(job.id, this.bootId).catch(() => {});
|
|
292
304
|
}
|
|
293
305
|
}, 30_000);
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { startGateway } from "./server.ts";
|
|
2
2
|
export type { GatewayConfig } from "./server.ts";
|
|
3
|
+
|
|
4
|
+
// Salida hacia el usuario: la app conecta su ChannelManager con setChannelManager().
|
|
5
|
+
export { setChannelManager, getChannelManager, notifyChannel, sendToUserChannel, broadcastNotification, type ChannelSender } from "./channel-notify.ts";
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* path guarantees only one process wins the race for the same job.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { col, nextId, updateDoc, toIndexable } from "../storage/hive";
|
|
9
|
-
import type { JobDoc } from "../storage/collections";
|
|
10
|
-
import { getBootId } from "../storage/boot-id";
|
|
11
|
-
import { logger } from "../utils/logger";
|
|
12
|
-
import { loadConfig } from "../config/loader";
|
|
8
|
+
import { col, nextId, updateDoc, toIndexable } from "../storage/hive.ts";
|
|
9
|
+
import type { JobDoc } from "../storage/collections.ts";
|
|
10
|
+
import { getBootId } from "../storage/boot-id.ts";
|
|
11
|
+
import { logger } from "../utils/logger.ts";
|
|
12
|
+
import { loadConfig } from "../config/loader.ts";
|
|
13
13
|
|
|
14
14
|
const log = logger.child("job-store");
|
|
15
15
|
|
|
@@ -337,6 +337,12 @@ export async function reclaimOrInterrupt(jobId: string, opts?: { force?: boolean
|
|
|
337
337
|
try {
|
|
338
338
|
await c.put(jobId, updated, { expectedVersion: entry.version });
|
|
339
339
|
log.warn(`[reclaimOrInterrupt] Job ${jobId} interrupted (attempts exhausted)`);
|
|
340
|
+
// Terminal via lease-expiry, not executeJob's normal fail path — that
|
|
341
|
+
// path already fires the hook, this one didn't before. Dynamic import
|
|
342
|
+
// avoids a static circular import (durable-queue.ts imports
|
|
343
|
+
// reclaimOrInterrupt from this module).
|
|
344
|
+
const { runTerminalHook } = await import("./durable-queue.ts");
|
|
345
|
+
await runTerminalHook(updated, { ok: false, error: updated.error ?? "Job interrupted after lease expiry" }).catch(() => {});
|
|
340
346
|
return updated;
|
|
341
347
|
} catch {
|
|
342
348
|
await occRetryDelay(attempt);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import type { NotificationDoc } from "../storage/collections";
|
|
3
|
-
import { col, updateDoc } from "../storage/hive";
|
|
2
|
+
import type { NotificationDoc } from "../storage/collections.ts";
|
|
3
|
+
import { col, updateDoc } from "../storage/hive.ts";
|
|
4
4
|
|
|
5
5
|
export async function createNotification(input: {
|
|
6
6
|
userId: string;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* - WebSocket /ws — real-time streaming
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { logger } from "../utils/logger";
|
|
10
|
+
import { logger } from "../utils/logger.ts";
|
|
11
11
|
import { runAgent } from "../agent/agent-loop.ts";
|
|
12
|
-
import type { MCPClientManager } from "../mcp/index";
|
|
12
|
+
import type { MCPClientManager } from "../mcp/index.ts";
|
|
13
13
|
|
|
14
14
|
const log = logger.child("gateway");
|
|
15
15
|
|