@johpaz/hive-sdk 0.1.4 → 0.1.6
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 +129 -0
- package/README.md +78 -23
- package/bun.lock +55 -29
- package/bunfig.toml +4 -2
- package/docs/API-AGENTS.md +78 -27
- package/docs/API-CONTEXT-COMPILER.md +31 -34
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
- package/docs/HIVE-HARNESS.md +1 -1
- package/docs/INDEX.md +4 -4
- package/docs/TEMPLATE-HIVE-APP.md +10 -10
- package/package.json +17 -12
- package/packages/cli/package.json +2 -2
- package/packages/cli/src/commands/create-app.test.ts +36 -7
- package/packages/cli/src/commands/init.ts +3 -3
- package/packages/cli/src/commands/run.ts +1 -1
- package/packages/cli/src/commands/test.ts +37 -25
- package/packages/cli/src/commands/trace.ts +30 -28
- package/packages/cli/templates/hive-app/.env.example +10 -2
- package/packages/cli/templates/hive-app/README.md +103 -0
- package/packages/cli/templates/hive-app/hive.config.ts +9 -3
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
- package/packages/cli/templates/hive-app/src/main.ts +12 -19
- package/packages/core/package.json +13 -12
- package/packages/core/src/agent/acceptance-checks.ts +172 -0
- package/packages/core/src/agent/agent-catalog.ts +348 -0
- package/packages/core/src/agent/agent-loop.ts +1373 -0
- package/packages/core/src/agent/capability-search.ts +186 -0
- package/packages/core/src/agent/catalog-selector.ts +103 -0
- package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
- package/packages/core/src/agent/context-compiler.ts +689 -0
- package/packages/core/src/agent/conversation-store.ts +381 -0
- package/packages/core/src/agent/curator.ts +276 -0
- package/packages/core/src/agent/delegation-runtime.ts +241 -0
- package/packages/core/src/agent/goal-runner.ts +323 -0
- package/packages/core/src/agent/index.ts +17 -12
- package/packages/core/src/agent/llm-client.ts +266 -0
- package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
- package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
- package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
- package/packages/core/src/agent/llm-providers/groq.ts +5 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
- package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
- package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
- package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
- package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
- package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
- package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
- package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
- package/packages/core/src/agent/llm-providers/openai.ts +5 -0
- package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
- package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
- package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
- package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
- package/packages/core/src/agent/minimal-loadout.ts +47 -0
- package/packages/core/src/agent/playbook-selector.ts +119 -0
- package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
- package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
- package/packages/core/src/agent/providers/index.ts +35 -16
- package/packages/core/src/agent/reflector.ts +320 -0
- package/packages/core/src/agent/routing-intent.ts +22 -0
- package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
- package/packages/core/src/{harness → agent}/run-store.ts +142 -81
- package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
- package/packages/core/src/agent/skill-selector.ts +374 -0
- package/packages/core/src/agent/stuck-loop.ts +209 -0
- package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
- package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
- package/packages/core/src/api/createAgent.test.ts +139 -27
- package/packages/core/src/api/createAgent.ts +232 -44
- package/packages/core/src/artifacts/store.ts +162 -0
- package/packages/core/src/canvas/canvas-manager.ts +161 -0
- package/packages/core/src/canvas/canvas.test.ts +8 -4
- package/packages/core/src/canvas/emitter.ts +131 -80
- package/packages/core/src/canvas/index.ts +1 -3
- package/packages/core/src/channels/base.ts +9 -1
- package/packages/core/src/channels/discord.ts +5 -4
- package/packages/core/src/channels/manager.ts +122 -30
- package/packages/core/src/channels/slack.ts +5 -4
- package/packages/core/src/channels/telegram.ts +36 -6
- package/packages/core/src/channels/webchat.ts +11 -10
- package/packages/core/src/channels/whatsapp.ts +23 -7
- package/packages/core/src/config/index.ts +13 -2
- package/packages/core/src/config/loader.ts +76 -29
- package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
- package/packages/core/src/ethics/EthicsGuard.ts +51 -47
- package/packages/core/src/events/agent-bus.ts +44 -68
- package/packages/core/src/events/channel-narration.ts +150 -0
- package/packages/core/src/events/narration.ts +82 -0
- package/packages/core/src/events/tool-narration.ts +62 -0
- package/packages/core/src/gateway/delegation-groups.ts +258 -0
- package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
- package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
- package/packages/core/src/gateway/lane-queue.ts +173 -0
- package/packages/core/src/gateway/notification-inbox.ts +57 -0
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/harness/index.ts +46 -27
- package/packages/core/src/index.ts +33 -27
- package/packages/core/src/mcp/hot-reload.ts +32 -23
- package/packages/core/src/mcp/index.ts +6 -3
- package/packages/core/src/mcp/singleton.ts +1 -4
- package/packages/core/src/mcp/tool-sync.ts +138 -0
- package/packages/core/src/memory/Scratchpad.test.ts +39 -20
- package/packages/core/src/memory/Scratchpad.ts +27 -34
- package/packages/core/src/multimodal/vision-service.ts +44 -38
- package/packages/core/src/resilience/retry.ts +95 -0
- package/packages/core/src/scheduler/CronScheduler.ts +334 -287
- package/packages/core/src/scheduler/index.ts +9 -7
- package/packages/core/src/scheduler/integration.ts +46 -26
- package/packages/core/src/scheduler/scheduler.test.ts +9 -13
- package/packages/core/src/scheduler/types.ts +7 -2
- package/packages/core/src/security/Pairing.ts +1 -1
- package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
- package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
- package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
- package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
- package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
- package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
- package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
- package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
- package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
- package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
- package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
- package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
- package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
- package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
- package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
- package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
- package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
- package/packages/core/src/skills/skills.test.ts +52 -11
- package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
- package/packages/core/src/storage/bootstrap.ts +151 -0
- package/packages/core/src/storage/causal-events.ts +84 -0
- package/packages/core/src/storage/collections.ts +680 -0
- package/packages/core/src/storage/crypto.ts +205 -74
- package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
- package/packages/core/src/storage/hivedb.ts +61 -0
- package/packages/core/src/storage/index.ts +111 -18
- package/packages/core/src/storage/model-id.ts +53 -0
- package/packages/core/src/storage/onboarding.ts +540 -972
- package/packages/core/src/storage/reconcile.ts +238 -0
- package/packages/core/src/storage/seed.ts +572 -406
- package/packages/core/src/storage/usage.ts +285 -225
- package/packages/core/src/storage/user-email.ts +11 -0
- package/packages/core/src/swarm/AgentExecutor.ts +1 -1
- package/packages/core/src/swarm/EventBridge.ts +1 -1
- package/packages/core/src/swarm/index.ts +12 -9
- package/packages/core/src/tool-runtime/index.ts +146 -23
- package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
- package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
- package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
- package/packages/core/src/tools/agents/get-available-models.ts +36 -54
- package/packages/core/src/tools/agents/index.ts +784 -292
- package/packages/core/src/tools/api/api-request.test.ts +164 -0
- package/packages/core/src/tools/api/api-request.ts +174 -0
- package/packages/core/src/tools/api/index.ts +16 -0
- package/packages/core/src/tools/cli/index.ts +4 -0
- package/packages/core/src/tools/core/index.ts +281 -112
- package/packages/core/src/tools/cron/index.ts +121 -124
- package/packages/core/src/tools/index.ts +63 -78
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
- package/packages/core/src/tools/types.ts +3 -1
- package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
- package/packages/core/src/tools/web/browser-backend.ts +129 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +80 -35
- package/packages/core/src/tools/web/browser-type.ts +3 -8
- package/packages/core/src/tools/web/index.ts +4 -4
- package/packages/core/src/tools/web/webview-backend.ts +412 -0
- package/packages/core/src/voice/index.ts +89 -63
- package/packages/core/src/workers/agent.worker.ts +2 -2
- package/packages/core/src/workers/workers.test.ts +3 -10
- package/scripts/bump-version.ts +248 -0
- package/scripts/generate-skill-bundle.ts +108 -0
- package/test/acceptance-checks.test.ts +403 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/browser-backend.test.ts +308 -0
- package/test/catalog-agents-stay-enabled.test.ts +117 -0
- package/test/causal-events.test.ts +117 -0
- package/test/compaction.test.ts +105 -0
- package/test/context-compiler.test.ts +269 -0
- package/test/curator.test.ts +130 -0
- package/test/durable-queue.test.ts +114 -0
- package/test/harness-barrel.test.ts +64 -0
- package/test/hive-helpers.test.ts +130 -0
- package/test/hivedb-search.test.ts +189 -0
- package/test/internal-turns.test.ts +166 -0
- package/test/job-idempotency.test.ts +68 -0
- package/test/job-retry-backoff.test.ts +184 -0
- package/test/job-store.test.ts +381 -0
- package/test/llm-retry.test.ts +97 -0
- package/test/memory-perf.test.ts +774 -0
- package/test/minimal-loadout.test.ts +78 -0
- package/test/model-catalog.test.ts +105 -0
- package/test/preload.ts +12 -0
- package/test/reflector.test.ts +320 -0
- package/test/retention-cap.test.ts +91 -0
- package/test/retired-capabilities-pruned.test.ts +192 -0
- package/test/run-store.test.ts +355 -0
- package/test/scratchpad.test.ts +74 -0
- package/test/secrets-durability.test.ts +119 -0
- package/test/seed-model-reseed.test.ts +155 -0
- package/test/setup-agent-seed.test.ts +264 -0
- package/test/tool-inventory.test.ts +65 -0
- package/test/tool-runtime.test.ts +258 -0
- package/test/tool-selector-runtime-tools.test.ts +117 -0
- package/test/toon.test.ts +429 -0
- package/tsconfig.json +2 -0
- package/packages/core/src/ace/Curator.ts +0 -158
- package/packages/core/src/ace/Reflector.ts +0 -200
- package/packages/core/src/ace/index.ts +0 -4
- package/packages/core/src/agent/AgentRunner.ts +0 -711
- package/packages/core/src/agent/ContextCompiler.ts +0 -567
- package/packages/core/src/agent/ContextGuard.ts +0 -91
- package/packages/core/src/agent/ConversationStore.ts +0 -254
- package/packages/core/src/agent/Hooks.ts +0 -166
- package/packages/core/src/agent/StuckLoop.ts +0 -133
- package/packages/core/src/agent/providers/LLMClient.ts +0 -149
- package/packages/core/src/agent/providers/anthropic.ts +0 -212
- package/packages/core/src/agent/providers/openai-compat.ts +0 -231
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
- package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
- package/packages/core/src/agent/selectors/index.ts +0 -6
- package/packages/core/src/auth/auth.ts +0 -121
- package/packages/core/src/auth/index.ts +0 -1
- package/packages/core/src/canvas/CanvasManager.ts +0 -390
- package/packages/core/src/canvas/canvas-tools.ts +0 -448
- package/packages/core/src/harness/collections.ts +0 -98
- package/packages/core/src/harness/goal-verifier.ts +0 -141
- package/packages/core/src/harness/harness.test.ts +0 -236
- package/packages/core/src/harness/reconcile.ts +0 -149
- package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
- package/packages/core/src/multimodal/VisionService.ts +0 -293
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
- package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
- package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
- package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
- package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
- package/packages/core/src/scheduler/dag/errors.ts +0 -37
- package/packages/core/src/scheduler/dag/index.ts +0 -26
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
- package/packages/core/src/storage/HiveDBStorage.ts +0 -64
- package/packages/core/src/storage/SQLiteStorage.ts +0 -414
- package/packages/core/src/storage/hiveSeed.ts +0 -308
- package/packages/core/src/storage/hiveStorage.test.ts +0 -38
- package/packages/core/src/storage/schema.ts +0 -689
- package/packages/core/src/storage/storage.test.ts +0 -37
- package/packages/core/src/swarm/AgentBus.ts +0 -460
- package/packages/core/src/swarm/EventBus.ts +0 -169
- package/packages/core/src/swarm/WorkerPool.ts +0 -236
- package/packages/core/src/tools/bridge-events.ts +0 -26
- package/packages/core/src/tools/canvas/index.ts +0 -375
- package/packages/core/src/tools/codebridge/index.ts +0 -342
- package/packages/core/src/tools/meeting/index.ts +0 -353
- package/packages/core/src/tools/projects/index.ts +0 -37
- package/packages/core/src/tools/projects/project-create.ts +0 -94
- package/packages/core/src/tools/projects/project-done.ts +0 -66
- package/packages/core/src/tools/projects/project-fail.ts +0 -66
- package/packages/core/src/tools/projects/project-list.ts +0 -96
- package/packages/core/src/tools/projects/project-update.ts +0 -72
- package/packages/core/src/tools/projects/task-create.ts +0 -68
- package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
- package/packages/core/src/tools/projects/task-update.ts +0 -93
- package/packages/core/src/tools/voice/index.ts +0 -104
- package/packages/core/src/tools/web/api-request.test.ts +0 -170
- package/packages/core/src/tools/web/api-request.ts +0 -239
- package/test/setup-db.ts +0 -216
- /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { col, toIndexable, fromIndexable, updateDoc } from "../storage/hive";
|
|
2
|
+
import type {
|
|
3
|
+
AgentDoc,
|
|
4
|
+
McpServerDoc,
|
|
5
|
+
ModelDoc,
|
|
6
|
+
ProviderDoc,
|
|
7
|
+
SkillDoc,
|
|
8
|
+
AgentModelOverride,
|
|
9
|
+
} from "../storage/collections";
|
|
10
|
+
import { createAllTools } from "../tools";
|
|
11
|
+
import { loadConfig } from "../config/loader";
|
|
12
|
+
import type { MCPClientManager } from "../mcp/index.ts";
|
|
13
|
+
import { logger } from "../utils/logger";
|
|
14
|
+
|
|
15
|
+
const log = logger.child("delegation-runtime");
|
|
16
|
+
const MCP_IDLE_TTL_MS = 2 * 60_000;
|
|
17
|
+
|
|
18
|
+
export interface PrepareDelegationOptions {
|
|
19
|
+
workspace: string | null;
|
|
20
|
+
/** Fallback provider/model when the target row has none of its own (catalog agents are seeded without one — inherits the parent/coordinator's). */
|
|
21
|
+
parentProviderId?: string | null;
|
|
22
|
+
parentModelId?: string | null;
|
|
23
|
+
/** Model to avoid only during capability-based DB fallback resolution. Explicit agent/parent configuration always wins. */
|
|
24
|
+
executorModelId?: string | null;
|
|
25
|
+
mcpManager?: MCPClientManager | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PreparedDelegation {
|
|
29
|
+
agent: AgentDoc;
|
|
30
|
+
toolNames: string[];
|
|
31
|
+
skillIds: string[];
|
|
32
|
+
mcpServerIds: string[];
|
|
33
|
+
providerId: string;
|
|
34
|
+
modelId: string;
|
|
35
|
+
release(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface McpLease {
|
|
39
|
+
refs: number;
|
|
40
|
+
serverName: string;
|
|
41
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mcpLeases = new Map<string, McpLease>();
|
|
45
|
+
|
|
46
|
+
function matchesPattern(name: string, pattern: string): boolean {
|
|
47
|
+
if (!pattern.includes("*")) return name === pattern;
|
|
48
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
49
|
+
return new RegExp(`^${escaped}$`).test(name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function expandToolAllowlist(patterns: string[], availableNames?: string[]): string[] {
|
|
53
|
+
const names = availableNames ?? createAllTools(loadConfig()).map((tool) => tool.name);
|
|
54
|
+
const selected = names.filter((name) => patterns.some((pattern) => matchesPattern(name, pattern)));
|
|
55
|
+
return [...new Set(selected)].sort();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseCapabilities(model: ModelDoc): string[] {
|
|
59
|
+
try {
|
|
60
|
+
return model.capabilities ? JSON.parse(model.capabilities) : [];
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the effective provider/model for a delegation. Persisted database
|
|
68
|
+
* configuration is authoritative: the agent's complete provider/model pair
|
|
69
|
+
* wins, otherwise the parent's complete pair is inherited. Capability metadata
|
|
70
|
+
* is consulted only when neither row supplies a complete pair, and candidates
|
|
71
|
+
* are selected exclusively from active database rows.
|
|
72
|
+
*/
|
|
73
|
+
export async function resolveAgentModel(
|
|
74
|
+
modelOverride: AgentModelOverride | null,
|
|
75
|
+
rowProviderId: string | null,
|
|
76
|
+
rowModelId: string | null,
|
|
77
|
+
parentProviderId: string | null,
|
|
78
|
+
parentModelId: string | null,
|
|
79
|
+
executorModelId?: string | null,
|
|
80
|
+
): Promise<{ providerId: string; modelId: string }> {
|
|
81
|
+
if (rowProviderId && rowModelId) {
|
|
82
|
+
return { providerId: rowProviderId, modelId: rowModelId };
|
|
83
|
+
}
|
|
84
|
+
if (parentProviderId && parentModelId) {
|
|
85
|
+
return { providerId: parentProviderId, modelId: parentModelId };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const fallback = {
|
|
89
|
+
providerId: rowProviderId ?? parentProviderId ?? "",
|
|
90
|
+
modelId: rowModelId ?? parentModelId ?? "",
|
|
91
|
+
};
|
|
92
|
+
if (!modelOverride) return fallback;
|
|
93
|
+
|
|
94
|
+
const models = (await (await col<ModelDoc>("models")).scan({})).map((entry) => entry.doc);
|
|
95
|
+
const providers = new Map(
|
|
96
|
+
(await (await col<ProviderDoc>("providers")).scan({}))
|
|
97
|
+
.map((entry) => entry.doc)
|
|
98
|
+
.filter((provider) => provider.enabled && provider.active)
|
|
99
|
+
.map((provider) => [provider.id, provider]),
|
|
100
|
+
);
|
|
101
|
+
const compatible = (model: ModelDoc) => {
|
|
102
|
+
if (!model.enabled || !providers.has(model.provider_id)) return false;
|
|
103
|
+
const caps = parseCapabilities(model);
|
|
104
|
+
if (!modelOverride.required_capabilities.every((cap) => caps.includes(cap))) return false;
|
|
105
|
+
if (modelOverride.prefer_different_family && executorModelId) {
|
|
106
|
+
const currentFamily = executorModelId.split(/[/:]/)[0];
|
|
107
|
+
const candidateFamily = model.id.split(/[/:]/)[0];
|
|
108
|
+
if (currentFamily === candidateFamily) return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const candidate = models.find(compatible);
|
|
114
|
+
return candidate ? { providerId: candidate.provider_id, modelId: candidate.id } : fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function validateSkillIds(skillIds: string[]): Promise<string[]> {
|
|
118
|
+
const skills = await col<SkillDoc>("skills");
|
|
119
|
+
const valid: string[] = [];
|
|
120
|
+
for (const id of skillIds) {
|
|
121
|
+
const entry = await skills.get(id);
|
|
122
|
+
if (!entry?.doc.active) throw new Error(`Agent references missing or inactive skill: ${id}`);
|
|
123
|
+
valid.push(id);
|
|
124
|
+
}
|
|
125
|
+
return valid;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function acquireMcpLease(
|
|
129
|
+
serverId: string,
|
|
130
|
+
manager: MCPClientManager,
|
|
131
|
+
): Promise<{ serverId: string; serverName: string }> {
|
|
132
|
+
const entry = await (await col<McpServerDoc>("mcpServers")).get(serverId);
|
|
133
|
+
if (!entry?.doc.enabled) throw new Error(`MCP server is missing or disabled: ${serverId}`);
|
|
134
|
+
// Gateway initialization registers DB-backed MCP servers under their stable
|
|
135
|
+
// document id. The display name is only used in UI/tool labels.
|
|
136
|
+
const serverName = entry.doc.id;
|
|
137
|
+
const existing = mcpLeases.get(serverId);
|
|
138
|
+
if (existing) {
|
|
139
|
+
if (existing.timer) clearTimeout(existing.timer);
|
|
140
|
+
existing.timer = undefined;
|
|
141
|
+
existing.refs++;
|
|
142
|
+
return { serverId, serverName: existing.serverName };
|
|
143
|
+
}
|
|
144
|
+
await manager.connectServer(serverName);
|
|
145
|
+
mcpLeases.set(serverId, { refs: 1, serverName });
|
|
146
|
+
return { serverId, serverName };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function releaseMcpLease(serverId: string, manager: MCPClientManager): Promise<void> {
|
|
150
|
+
const lease = mcpLeases.get(serverId);
|
|
151
|
+
if (!lease) return;
|
|
152
|
+
lease.refs = Math.max(0, lease.refs - 1);
|
|
153
|
+
if (lease.refs > 0) return;
|
|
154
|
+
lease.timer = setTimeout(() => {
|
|
155
|
+
const current = mcpLeases.get(serverId);
|
|
156
|
+
if (!current || current.refs > 0) return;
|
|
157
|
+
void manager.disconnectServer(current.serverName)
|
|
158
|
+
.catch((err) => log.warn(`[delegation-runtime] MCP disconnect failed: ${(err as Error).message}`))
|
|
159
|
+
.finally(() => mcpLeases.delete(serverId));
|
|
160
|
+
}, MCP_IDLE_TTL_MS);
|
|
161
|
+
lease.timer.unref?.();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Prepares a delegation target for execution: expands its tool allowlist
|
|
166
|
+
* (when it has one — catalog agents do, plain agent_create workers use their
|
|
167
|
+
* stored tools_json as-is), validates skills, resolves the effective
|
|
168
|
+
* provider/model, and acquires any requested MCP leases.
|
|
169
|
+
*
|
|
170
|
+
* This never creates a new AgentDoc — the row already exists (seeded from
|
|
171
|
+
* the catalog, or created via agent_create).
|
|
172
|
+
* It does persist the resolved workspace/model back onto that row (same as
|
|
173
|
+
* the old materialization did) so agent-loop.ts picks them up when it loads
|
|
174
|
+
* the agent — which reintroduces a narrow, accepted race for the rare case
|
|
175
|
+
* of two concurrent delegations to the *same* catalog agent with different
|
|
176
|
+
* workspaces (catalog agents are global rows, not one-per-workspace anymore).
|
|
177
|
+
* `updateDoc`'s OCC retry keeps this safe (last-write-wins), never corrupt.
|
|
178
|
+
*/
|
|
179
|
+
export async function prepareDelegation(agentId: string, opts: PrepareDelegationOptions): Promise<PreparedDelegation> {
|
|
180
|
+
const agents = await col<AgentDoc>("agents");
|
|
181
|
+
const entry = await agents.get(agentId);
|
|
182
|
+
if (!entry?.doc.enabled) throw new Error(`Agent not found or disabled: ${agentId}`);
|
|
183
|
+
const agentDoc = entry.doc;
|
|
184
|
+
|
|
185
|
+
const toolNames = agentDoc.tool_allowlist_json
|
|
186
|
+
? expandToolAllowlist(JSON.parse(agentDoc.tool_allowlist_json))
|
|
187
|
+
: (agentDoc.tools_json ? JSON.parse(agentDoc.tools_json) : []);
|
|
188
|
+
|
|
189
|
+
const skillIds = agentDoc.skills_json ? await validateSkillIds(JSON.parse(agentDoc.skills_json)) : [];
|
|
190
|
+
|
|
191
|
+
const requestedMcp: string[] = agentDoc.mcp_server_ids_json ? JSON.parse(agentDoc.mcp_server_ids_json) : [];
|
|
192
|
+
if (requestedMcp.length > 0 && !opts.mcpManager) throw new Error("MCP servers requested but MCP manager is unavailable");
|
|
193
|
+
|
|
194
|
+
const acquired: string[] = [];
|
|
195
|
+
try {
|
|
196
|
+
for (const serverId of requestedMcp) {
|
|
197
|
+
await acquireMcpLease(serverId, opts.mcpManager!);
|
|
198
|
+
acquired.push(serverId);
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
await Promise.all(acquired.map((id) => releaseMcpLease(id, opts.mcpManager!)));
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const modelOverride: AgentModelOverride | null = agentDoc.model_override_json
|
|
206
|
+
? JSON.parse(agentDoc.model_override_json)
|
|
207
|
+
: null;
|
|
208
|
+
const resolved = await resolveAgentModel(
|
|
209
|
+
modelOverride,
|
|
210
|
+
fromIndexable(agentDoc.provider_id),
|
|
211
|
+
fromIndexable(agentDoc.model_id),
|
|
212
|
+
opts.parentProviderId ?? null,
|
|
213
|
+
opts.parentModelId ?? null,
|
|
214
|
+
opts.executorModelId ?? opts.parentModelId ?? null,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
await updateDoc<AgentDoc>("agents", agentId, {
|
|
218
|
+
workspace: opts.workspace,
|
|
219
|
+
provider_id: toIndexable(resolved.providerId || null),
|
|
220
|
+
model_id: toIndexable(resolved.modelId || null),
|
|
221
|
+
active_mcp_json: JSON.stringify(requestedMcp),
|
|
222
|
+
updated_at: Date.now(),
|
|
223
|
+
}).catch((err) => log.warn(`[prepareDelegation] Failed to persist delegation context for ${agentId}: ${(err as Error).message}`));
|
|
224
|
+
|
|
225
|
+
let released = false;
|
|
226
|
+
return {
|
|
227
|
+
agent: agentDoc,
|
|
228
|
+
toolNames,
|
|
229
|
+
skillIds,
|
|
230
|
+
mcpServerIds: requestedMcp,
|
|
231
|
+
providerId: resolved.providerId,
|
|
232
|
+
modelId: resolved.modelId,
|
|
233
|
+
release: async () => {
|
|
234
|
+
if (released) return;
|
|
235
|
+
released = true;
|
|
236
|
+
if (opts.mcpManager) {
|
|
237
|
+
await Promise.all(requestedMcp.map((id) => releaseMcpLease(id, opts.mcpManager!)));
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* goal-runner — orchestrates a multi-turn agent run toward a verifiable goal.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Create an AgentRun (kind="goal") with goal + budget
|
|
6
|
+
* 2. Run agent turns until:
|
|
7
|
+
* - Goal is met (verified by goal_check_tool or LLM verifier)
|
|
8
|
+
* - Budget exhausted (iterations/tokens/turns)
|
|
9
|
+
* - Max goal attempts reached
|
|
10
|
+
* 3. Between turns: compact context, inject goal/reason/budget reminder
|
|
11
|
+
* 4. On completion: persist success/failure + notify channel
|
|
12
|
+
*
|
|
13
|
+
* The budget is HARD: iterations, tokens, and turns all count across the
|
|
14
|
+
* entire run (not per-turn). This prevents endless loops.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { logger } from "../utils/logger";
|
|
18
|
+
import { callLLM, type LLMMessage } from "./llm-client";
|
|
19
|
+
import { createRun, type AcceptanceCriterion } from "./run-store";
|
|
20
|
+
import { clearOldToolResults } from "./compaction";
|
|
21
|
+
import { loadConfig } from "../config/loader";
|
|
22
|
+
import { getDurableQueue } from "../gateway/durable-queue.ts";
|
|
23
|
+
import { recordLLMUsage } from "./tracer";
|
|
24
|
+
|
|
25
|
+
export type { AcceptanceCriterion } from "./run-store";
|
|
26
|
+
|
|
27
|
+
export interface AcceptanceResult {
|
|
28
|
+
id: string;
|
|
29
|
+
description: string;
|
|
30
|
+
met: boolean;
|
|
31
|
+
evidence: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const log = logger.child("goal-runner");
|
|
35
|
+
|
|
36
|
+
const MAX_GOAL_ATTEMPTS = 5;
|
|
37
|
+
|
|
38
|
+
export interface GoalRunOptions {
|
|
39
|
+
agentId: string;
|
|
40
|
+
threadId: string;
|
|
41
|
+
userId: string;
|
|
42
|
+
channel: string | null;
|
|
43
|
+
goal: string;
|
|
44
|
+
goalCheckTool?: string | null;
|
|
45
|
+
maxIterationsPerTurn?: number;
|
|
46
|
+
maxTurns?: number;
|
|
47
|
+
maxTokens?: number;
|
|
48
|
+
maxAttempts?: number;
|
|
49
|
+
/** Whole-job acceptance criteria — when set, the goal is only "met" once every criterion is. */
|
|
50
|
+
acceptance?: AcceptanceCriterion[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface GoalRunResult {
|
|
54
|
+
met: boolean;
|
|
55
|
+
reason: string;
|
|
56
|
+
turnsUsed: number;
|
|
57
|
+
iterationsUsed: number;
|
|
58
|
+
tokensUsed: number;
|
|
59
|
+
attempts: number;
|
|
60
|
+
finalContent: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run a goal-based agent loop with verification between turns.
|
|
65
|
+
*/
|
|
66
|
+
export async function runGoal(opts: GoalRunOptions): Promise<GoalRunResult> {
|
|
67
|
+
const maxAttempts = opts.maxAttempts ?? MAX_GOAL_ATTEMPTS;
|
|
68
|
+
const maxIterationsPerTurn = opts.maxIterationsPerTurn ?? 20;
|
|
69
|
+
const maxTurns = opts.maxTurns ?? 10;
|
|
70
|
+
const maxTokens = opts.maxTokens ?? 200_000;
|
|
71
|
+
|
|
72
|
+
log.info(`[runGoal] Starting goal="${opts.goal}" agent=${opts.agentId} maxTurns=${maxTurns} maxAttempts=${maxAttempts}`);
|
|
73
|
+
|
|
74
|
+
// Create the durable AgentRun
|
|
75
|
+
const run = await createRun({
|
|
76
|
+
thread_id: opts.threadId,
|
|
77
|
+
agent_id: opts.agentId,
|
|
78
|
+
user_id: opts.userId,
|
|
79
|
+
channel: opts.channel,
|
|
80
|
+
kind: "goal",
|
|
81
|
+
max_iterations: maxIterationsPerTurn * maxTurns,
|
|
82
|
+
max_turns: maxTurns,
|
|
83
|
+
max_tokens: maxTokens,
|
|
84
|
+
goal: opts.goal,
|
|
85
|
+
goal_check_tool: opts.goalCheckTool ?? null,
|
|
86
|
+
resume_policy: "resume",
|
|
87
|
+
acceptance: opts.acceptance,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Enqueue a goal_run job in the durable queue
|
|
91
|
+
const queue = getDurableQueue();
|
|
92
|
+
const job = await queue.enqueue({
|
|
93
|
+
lane: `goal:${run.id}`,
|
|
94
|
+
type: "goal_run",
|
|
95
|
+
run_id: run.id,
|
|
96
|
+
payload: {
|
|
97
|
+
agentId: opts.agentId,
|
|
98
|
+
threadId: opts.threadId,
|
|
99
|
+
goal: opts.goal,
|
|
100
|
+
goal_check_tool: opts.goalCheckTool,
|
|
101
|
+
maxAttempts,
|
|
102
|
+
budget: {
|
|
103
|
+
maxIterations: maxIterationsPerTurn,
|
|
104
|
+
maxTurns,
|
|
105
|
+
maxTokens,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
log.info(`[runGoal] Enqueued goal_run job ${job.id} for run ${run.id}`);
|
|
111
|
+
|
|
112
|
+
// Note: The actual execution happens asynchronously via the durable queue's
|
|
113
|
+
// goal_run executor. This function returns the initial state — the caller
|
|
114
|
+
// can poll the run status or subscribe to the channel for notifications.
|
|
115
|
+
return {
|
|
116
|
+
met: false,
|
|
117
|
+
reason: "Goal run enqueued — execution is asynchronous. Poll task_status or watch the channel for completion.",
|
|
118
|
+
turnsUsed: 0,
|
|
119
|
+
iterationsUsed: 0,
|
|
120
|
+
tokensUsed: 0,
|
|
121
|
+
attempts: 0,
|
|
122
|
+
finalContent: "",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Runs a deterministic goal_check_tool, no LLM involved. */
|
|
127
|
+
async function runDeterministicCheck(checkTool: string, goal: string): Promise<{ met: boolean; reason: string } | null> {
|
|
128
|
+
try {
|
|
129
|
+
const { executeToolBatch } = await import("../tool-runtime");
|
|
130
|
+
const { createAllTools } = await import("../tools/index");
|
|
131
|
+
const allTools = createAllTools(loadConfig());
|
|
132
|
+
const toolDef = allTools.find((t) => t.name === checkTool);
|
|
133
|
+
if (!toolDef) {
|
|
134
|
+
log.warn(`[verifyGoal] Check tool "${checkTool}" not found in the tool registry — falling back to LLM verifier`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const toolResults = await executeToolBatch({
|
|
138
|
+
toolCalls: [{
|
|
139
|
+
id: "goal-check",
|
|
140
|
+
function: { name: checkTool, arguments: JSON.stringify({ goal }) },
|
|
141
|
+
}],
|
|
142
|
+
allTools,
|
|
143
|
+
toolConfig: {},
|
|
144
|
+
});
|
|
145
|
+
const result = toolResults[0];
|
|
146
|
+
if (result?.ok) return interpretCheckResult(result.result);
|
|
147
|
+
return { met: false, reason: `Check tool failed: ${result?.error?.message ?? "unknown"}` };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log.warn(`[verifyGoal] Check tool "${checkTool}" failed: ${(err as Error).message}`);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Judges every criterion that has no deterministic checkTool with a SINGLE
|
|
156
|
+
* LLM call (not one call per criterion) — the model returns a verdict per
|
|
157
|
+
* criterion id in one structured response.
|
|
158
|
+
*/
|
|
159
|
+
async function judgeCriteriaWithLLM(
|
|
160
|
+
criteria: AcceptanceCriterion[],
|
|
161
|
+
messages: LLMMessage[],
|
|
162
|
+
providerCfg: any,
|
|
163
|
+
): Promise<AcceptanceResult[]> {
|
|
164
|
+
try {
|
|
165
|
+
const verificationMessages: LLMMessage[] = [
|
|
166
|
+
...clearOldToolResults(messages),
|
|
167
|
+
{
|
|
168
|
+
role: "user",
|
|
169
|
+
content: `Evaluá si cada uno de los siguientes criterios de aceptación se cumplió, basándote en la conversación anterior.\n\nCriterios:\n${criteria.map((c) => `- ${c.id}: ${c.description}`).join("\n")}\n\nRespondé en JSON estricto, un resultado por criterio:\n{"results":[{"id":"...","met":true/false,"reason":"explicación breve"}]}`,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const response = await callLLM({ ...providerCfg, messages: verificationMessages, tools: undefined });
|
|
174
|
+
if (providerCfg.provider && providerCfg.model && response.usage) {
|
|
175
|
+
recordLLMUsage({
|
|
176
|
+
provider: providerCfg.provider,
|
|
177
|
+
model: providerCfg.model,
|
|
178
|
+
inputTokens: response.usage.input_tokens ?? 0,
|
|
179
|
+
outputTokens: response.usage.output_tokens ?? 0,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Without this the error text falls through to JSON.parse and the catch below
|
|
184
|
+
// reports a bogus "Unexpected token" instead of the actual provider failure.
|
|
185
|
+
if (response.stop_reason === "error") {
|
|
186
|
+
throw new Error(response.error?.message ?? response.content);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const content = response.content?.trim() || "";
|
|
190
|
+
const candidate = content.slice(content.indexOf("{"), content.lastIndexOf("}") + 1);
|
|
191
|
+
const parsed = JSON.parse(candidate) as { results?: Array<{ id: string; met: boolean; reason?: string }> };
|
|
192
|
+
const byId = new Map((parsed.results ?? []).map((r) => [r.id, r]));
|
|
193
|
+
return criteria.map((c) => {
|
|
194
|
+
const r = byId.get(c.id);
|
|
195
|
+
return { id: c.id, description: c.description, met: r?.met === true, evidence: r?.reason || "El modelo no evaluó este criterio" };
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
log.warn(`[verifyGoal] LLM verification failed: ${(err as Error).message}`);
|
|
199
|
+
return criteria.map((c) => ({ id: c.id, description: c.description, met: false, evidence: `Verification error: ${(err as Error).message}` }));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Verify whether a goal has been met using either:
|
|
205
|
+
* - A deterministic tool (goal_check_tool) — executes the tool and checks the result
|
|
206
|
+
* - An LLM verifier — asks the model to return JSON {met, reason}
|
|
207
|
+
*
|
|
208
|
+
* When `acceptance` criteria are supplied, each with its own checkTool is
|
|
209
|
+
* checked deterministically (no LLM), and every remaining criterion is
|
|
210
|
+
* judged together in a single LLM call — never one call per criterion. The
|
|
211
|
+
* overall verdict is the conjunction of all of them; the top-level
|
|
212
|
+
* `goal`/`checkTool` are ignored in that case.
|
|
213
|
+
*/
|
|
214
|
+
export async function verifyGoal(
|
|
215
|
+
goal: string,
|
|
216
|
+
checkTool: string | null | undefined,
|
|
217
|
+
messages: LLMMessage[],
|
|
218
|
+
providerCfg: any,
|
|
219
|
+
acceptance?: AcceptanceCriterion[] | null,
|
|
220
|
+
): Promise<{ met: boolean; reason: string; acceptanceResults?: AcceptanceResult[] }> {
|
|
221
|
+
if (acceptance && acceptance.length > 0) {
|
|
222
|
+
const results: AcceptanceResult[] = [];
|
|
223
|
+
const needsLLMJudgment: AcceptanceCriterion[] = [];
|
|
224
|
+
|
|
225
|
+
for (const criterion of acceptance) {
|
|
226
|
+
const deterministic = criterion.checkTool ? await runDeterministicCheck(criterion.checkTool, criterion.description) : null;
|
|
227
|
+
if (deterministic) {
|
|
228
|
+
results.push({ id: criterion.id, description: criterion.description, met: deterministic.met, evidence: deterministic.reason });
|
|
229
|
+
} else {
|
|
230
|
+
needsLLMJudgment.push(criterion);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (needsLLMJudgment.length > 0) {
|
|
235
|
+
results.push(...(await judgeCriteriaWithLLM(needsLLMJudgment, messages, providerCfg)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const met = results.every((r) => r.met);
|
|
239
|
+
const reason = results.map((r) => `${r.met ? "✅" : "❌"} ${r.description}: ${r.evidence}`).join("\n");
|
|
240
|
+
return { met, reason, acceptanceResults: results };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If we have a deterministic check tool, execute it
|
|
244
|
+
if (checkTool) {
|
|
245
|
+
const deterministic = await runDeterministicCheck(checkTool, goal);
|
|
246
|
+
if (deterministic) return deterministic;
|
|
247
|
+
// Falls through to the LLM verifier when the tool is missing or errored.
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// LLM verifier: ask the model to evaluate whether the goal is met
|
|
251
|
+
try {
|
|
252
|
+
const verificationMessages: LLMMessage[] = [
|
|
253
|
+
...clearOldToolResults(messages),
|
|
254
|
+
{
|
|
255
|
+
role: "user",
|
|
256
|
+
content: `Evaluá si el siguiente objetivo ha sido cumplido basándote en la conversación anterior.\n\nObjetivo: "${goal}"\n\nRespondé en JSON:\n{"met": true/false, "reason": "explicación breve"}`,
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const response = await callLLM({
|
|
261
|
+
...providerCfg,
|
|
262
|
+
messages: verificationMessages,
|
|
263
|
+
tools: undefined,
|
|
264
|
+
});
|
|
265
|
+
if (providerCfg.provider && providerCfg.model && response.usage) {
|
|
266
|
+
recordLLMUsage({
|
|
267
|
+
provider: providerCfg.provider,
|
|
268
|
+
model: providerCfg.model,
|
|
269
|
+
inputTokens: response.usage.input_tokens ?? 0,
|
|
270
|
+
outputTokens: response.usage.output_tokens ?? 0,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (response.stop_reason === "error") {
|
|
275
|
+
throw new Error(response.error?.message ?? response.content);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const content = response.content?.trim() || "";
|
|
279
|
+
// Extract JSON from the response
|
|
280
|
+
const jsonMatch = content.match(/\{[^}]*\}/);
|
|
281
|
+
if (jsonMatch) {
|
|
282
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
283
|
+
return {
|
|
284
|
+
met: !!parsed.met,
|
|
285
|
+
reason: parsed.reason || "No reason provided",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return { met: false, reason: "Could not parse verification response" };
|
|
289
|
+
} catch (err) {
|
|
290
|
+
log.warn(`[verifyGoal] LLM verification failed: ${(err as Error).message}`);
|
|
291
|
+
return { met: false, reason: `Verification error: ${(err as Error).message}` };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Interpret a check tool's result strictly: an object with a boolean `met`,
|
|
297
|
+
* a bare boolean, or a JSON string with `met` — anything else is not met.
|
|
298
|
+
*/
|
|
299
|
+
export function interpretCheckResult(raw: unknown): { met: boolean; reason: string } {
|
|
300
|
+
if (typeof raw === "boolean") {
|
|
301
|
+
return { met: raw, reason: raw ? "Check tool returned true" : "Check tool returned false" };
|
|
302
|
+
}
|
|
303
|
+
if (raw && typeof raw === "object" && "met" in (raw as Record<string, unknown>)) {
|
|
304
|
+
const obj = raw as { met: unknown; reason?: unknown };
|
|
305
|
+
return { met: obj.met === true, reason: typeof obj.reason === "string" ? obj.reason : `Check tool met=${obj.met === true}` };
|
|
306
|
+
}
|
|
307
|
+
if (typeof raw === "string") {
|
|
308
|
+
const trimmed = raw.trim();
|
|
309
|
+
if (trimmed === "true" || trimmed === "false") {
|
|
310
|
+
return { met: trimmed === "true", reason: `Check tool returned "${trimmed}"` };
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(trimmed);
|
|
314
|
+
if (parsed && typeof parsed === "object" && "met" in parsed) {
|
|
315
|
+
return { met: parsed.met === true, reason: typeof parsed.reason === "string" ? parsed.reason : `Check tool met=${parsed.met === true}` };
|
|
316
|
+
}
|
|
317
|
+
if (typeof parsed === "boolean") {
|
|
318
|
+
return { met: parsed, reason: `Check tool returned ${parsed}` };
|
|
319
|
+
}
|
|
320
|
+
} catch { /* not JSON */ }
|
|
321
|
+
}
|
|
322
|
+
return { met: false, reason: "Check tool result had no interpretable met/true signal" };
|
|
323
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
12
|
-
export * from "./
|
|
1
|
+
export * from "./acceptance-checks.ts";
|
|
2
|
+
export * from "./agent-catalog.ts";
|
|
3
|
+
export * from "./agent-loop.ts";
|
|
4
|
+
export * from "./capability-search.ts";
|
|
5
|
+
export * from "./catalog-selector.ts";
|
|
6
|
+
export * from "./context-compiler.ts";
|
|
7
|
+
export * from "./conversation-store.ts";
|
|
8
|
+
export * from "./delegation-runtime.ts";
|
|
9
|
+
export * from "./llm-client.ts";
|
|
10
|
+
export * from "./minimal-loadout.ts";
|
|
11
|
+
export * from "./playbook-selector.ts";
|
|
12
|
+
export * from "./prompt-builder.ts";
|
|
13
|
+
export * from "./proof-packet.ts";
|
|
14
|
+
export * from "./run-store.ts";
|
|
15
|
+
export * from "./service.ts";
|
|
16
|
+
export * from "./skill-selector.ts";
|
|
17
|
+
export * from "./tool-selector.ts";
|