@johpaz/hive-sdk 0.1.4 → 0.1.5
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 +97 -0
- package/README.md +78 -23
- 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 +9 -4
- 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 +5 -4
- package/packages/core/src/agent/acceptance-checks.ts +166 -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 +71 -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-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +5 -0
- 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/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/agent-loop-terminal-synthesis.test.ts +32 -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/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
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agents Tools -
|
|
3
|
-
*
|
|
2
|
+
* Agents Tools - 15 tools
|
|
3
|
+
*
|
|
4
4
|
* @category agents
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Tool } from "../types.ts";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
8
|
+
import { col, toIndexable, fromIndexable, BROADCAST } from "../../storage/hive.ts";
|
|
9
|
+
import type { MemoryDoc, AgentDoc, ProviderDoc, ModelDoc, McpServerDoc, TaskDoc, AgentBusMessageDoc, AgentAcceptanceCriterion } from "../../storage/collections.ts";
|
|
10
|
+
import type { AcceptanceCriterion } from "../../agent/run-store.ts";
|
|
11
|
+
import type { PreparedDelegation } from "../../agent/delegation-runtime.ts";
|
|
11
12
|
import { logger } from "../../utils/logger.ts";
|
|
12
|
-
import { agentBus } from "../../
|
|
13
|
-
import {
|
|
13
|
+
import { agentBus } from "../../events/agent-bus.ts";
|
|
14
|
+
import {
|
|
15
|
+
emitDelegationStarted,
|
|
16
|
+
emitDelegationFinished,
|
|
17
|
+
emitWorkEvent,
|
|
18
|
+
} from "../../canvas/emitter.ts";
|
|
14
19
|
|
|
15
20
|
const log = logger.child("agents");
|
|
16
21
|
|
|
@@ -28,15 +33,20 @@ export const memoryWriteTool: Tool = {
|
|
|
28
33
|
required: ["title", "content"],
|
|
29
34
|
},
|
|
30
35
|
execute: async (params: Record<string, unknown>) => {
|
|
31
|
-
const db = await getHiveDB();
|
|
32
36
|
const title = params.title as string;
|
|
33
37
|
const content = params.content as string;
|
|
34
38
|
|
|
35
39
|
try {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
40
|
+
const memoryCol = await col<MemoryDoc>("memory");
|
|
41
|
+
const existing = await memoryCol.get(title);
|
|
38
42
|
const now = Date.now();
|
|
39
|
-
await
|
|
43
|
+
await memoryCol.put(title, {
|
|
44
|
+
id: title,
|
|
45
|
+
title,
|
|
46
|
+
content,
|
|
47
|
+
created_at: existing?.doc.created_at ?? now,
|
|
48
|
+
updated_at: now,
|
|
49
|
+
}, existing ? { expectedVersion: existing.version } : { expectedVersion: 0 });
|
|
40
50
|
|
|
41
51
|
return { ok: true, title, message: "Memory saved." };
|
|
42
52
|
} catch (error) {
|
|
@@ -58,24 +68,22 @@ export const memoryReadTool: Tool = {
|
|
|
58
68
|
required: ["title"],
|
|
59
69
|
},
|
|
60
70
|
execute: async (params: Record<string, unknown>) => {
|
|
61
|
-
const db = await getHiveDB();
|
|
62
71
|
const title = params.title as string;
|
|
63
72
|
|
|
64
73
|
try {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const note = entries.find(e => e.doc.title === title)?.doc;
|
|
74
|
+
const memoryCol = await col<MemoryDoc>("memory");
|
|
75
|
+
const entry = await memoryCol.get(title);
|
|
68
76
|
|
|
69
|
-
if (!
|
|
77
|
+
if (!entry) {
|
|
70
78
|
return { ok: false, error: `Memory not found: ${title}` };
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
return {
|
|
74
82
|
ok: true,
|
|
75
|
-
title:
|
|
76
|
-
content:
|
|
77
|
-
createdAt: new Date(
|
|
78
|
-
updatedAt: new Date(
|
|
83
|
+
title: entry.doc.title,
|
|
84
|
+
content: entry.doc.content,
|
|
85
|
+
createdAt: new Date(entry.doc.created_at).toISOString(),
|
|
86
|
+
updatedAt: new Date(entry.doc.updated_at).toISOString(),
|
|
79
87
|
};
|
|
80
88
|
} catch (error) {
|
|
81
89
|
return { ok: false, error: `Failed to read memory: ${(error as Error).message}` };
|
|
@@ -93,19 +101,16 @@ export const memoryListTool: Tool = {
|
|
|
93
101
|
properties: {},
|
|
94
102
|
},
|
|
95
103
|
execute: async () => {
|
|
96
|
-
const db = await getHiveDB();
|
|
97
|
-
|
|
98
104
|
try {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const notes = entries
|
|
105
|
+
const memoryCol = await col<MemoryDoc>("memory");
|
|
106
|
+
const notes = (await memoryCol.scan({}))
|
|
102
107
|
.map(e => e.doc)
|
|
103
|
-
.sort((a, b) => b.
|
|
108
|
+
.sort((a, b) => b.updated_at - a.updated_at);
|
|
104
109
|
|
|
105
110
|
return {
|
|
106
111
|
ok: true,
|
|
107
112
|
count: notes.length,
|
|
108
|
-
entries: notes.map((n) => ({ title: n.title, createdAt: new Date(n.
|
|
113
|
+
entries: notes.map((n) => ({ title: n.title, createdAt: new Date(n.created_at).toISOString() })),
|
|
109
114
|
};
|
|
110
115
|
} catch (error) {
|
|
111
116
|
return { ok: false, error: `Failed to list memories: ${(error as Error).message}` };
|
|
@@ -126,15 +131,14 @@ export const memorySearchTool: Tool = {
|
|
|
126
131
|
required: ["query"],
|
|
127
132
|
},
|
|
128
133
|
execute: async (params: Record<string, unknown>) => {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
134
|
+
const query = params.query as string;
|
|
135
|
+
const needle = query.toLowerCase();
|
|
131
136
|
|
|
132
137
|
try {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const notes = entries
|
|
138
|
+
const memoryCol = await col<MemoryDoc>("memory");
|
|
139
|
+
const notes = (await memoryCol.scan({}))
|
|
136
140
|
.map(e => e.doc)
|
|
137
|
-
.filter(n => n.
|
|
141
|
+
.filter(n => n.content.toLowerCase().includes(needle) || n.title.toLowerCase().includes(needle));
|
|
138
142
|
|
|
139
143
|
return {
|
|
140
144
|
ok: true,
|
|
@@ -164,19 +168,18 @@ export const memoryDeleteTool: Tool = {
|
|
|
164
168
|
required: ["title"],
|
|
165
169
|
},
|
|
166
170
|
execute: async (params: Record<string, unknown>) => {
|
|
167
|
-
const db = await getHiveDB();
|
|
168
171
|
const title = params.title as string;
|
|
169
172
|
|
|
170
173
|
try {
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const target = entries.find(e => e.doc.title === title);
|
|
174
|
+
const memoryCol = await col<MemoryDoc>("memory");
|
|
175
|
+
const existing = await memoryCol.get(title);
|
|
174
176
|
|
|
175
|
-
if (!
|
|
177
|
+
if (!existing) {
|
|
176
178
|
return { ok: false, error: `Memory not found: ${title}` };
|
|
177
179
|
}
|
|
178
180
|
|
|
179
|
-
await
|
|
181
|
+
await memoryCol.delete(title);
|
|
182
|
+
|
|
180
183
|
return { ok: true, title, message: "Memory deleted." };
|
|
181
184
|
} catch (error) {
|
|
182
185
|
return { ok: false, error: `Failed to delete memory: ${(error as Error).message}` };
|
|
@@ -188,7 +191,7 @@ export const memoryDeleteTool: Tool = {
|
|
|
188
191
|
|
|
189
192
|
export const agentCreateTool: Tool = {
|
|
190
193
|
name: "agent_create",
|
|
191
|
-
description: "Crear un nuevo agente worker especializado. Requiere consultar get_available_models
|
|
194
|
+
description: "Crear un nuevo agente worker especializado. Requiere consultar get_available_models; para un especialista MCP confirmado por el usuario, acepta mcp_server_id. Sinónimos: crear agente, nuevo worker, nuevo trabajador",
|
|
192
195
|
parameters: {
|
|
193
196
|
type: "object",
|
|
194
197
|
properties: {
|
|
@@ -198,100 +201,157 @@ export const agentCreateTool: Tool = {
|
|
|
198
201
|
tools_json: { type: "array", description: "Lista de IDs de herramientas", items: { type: "string" } },
|
|
199
202
|
providerId: { type: "string", description: "ID del provider (openai, anthropic, ollama, etc.) - Obtener de get_available_models" },
|
|
200
203
|
modelId: { type: "string", description: "ID del modelo (gpt-4o, claude-sonnet, etc.) - Obtener de get_available_models" },
|
|
204
|
+
mcp_server_id: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Servidor MCP persistente para un especialista. Requiere confirmación previa del usuario y asigna todas las tools actuales y futuras de ese servidor.",
|
|
207
|
+
},
|
|
201
208
|
tone: { type: "string", description: "Tono del agente (friendly, professional, direct, etc.)" },
|
|
202
209
|
max_iterations: { type: "number", description: "Límite de iteraciones del agente (default: 10)" },
|
|
203
210
|
},
|
|
204
211
|
required: ["name", "providerId", "modelId"],
|
|
205
212
|
},
|
|
206
213
|
execute: async (params: Record<string, unknown>, config?: any) => {
|
|
207
|
-
const db = await getHiveDB();
|
|
208
214
|
const userId = config?.configurable?.user_id;
|
|
209
|
-
const parentId = config?.configurable?.agent_id ??
|
|
215
|
+
const parentId = config?.configurable?.agent_id ?? null;
|
|
210
216
|
const name = params.name as string;
|
|
211
217
|
const description = (params.description as string) ?? "";
|
|
212
218
|
const systemPrompt = (params.system_prompt as string) ?? "";
|
|
213
|
-
const toolsJson = params.tools_json ? JSON.stringify(params.tools_json) :
|
|
219
|
+
const toolsJson = params.tools_json ? JSON.stringify(params.tools_json) : null;
|
|
214
220
|
const providerId = params.providerId as string;
|
|
215
221
|
const modelId = params.modelId as string;
|
|
222
|
+
const mcpServerId = params.mcp_server_id as string | undefined;
|
|
216
223
|
const tone = (params.tone as string) ?? "friendly";
|
|
217
224
|
const maxIterations = (params.max_iterations as number) ?? 10;
|
|
218
|
-
const parentWorkspace = config?.configurable?.workspace ??
|
|
225
|
+
const parentWorkspace = config?.configurable?.workspace ?? null;
|
|
219
226
|
|
|
227
|
+
// Validar que providerId y modelId sean obligatorios
|
|
220
228
|
if (!providerId || !modelId) {
|
|
221
|
-
return {
|
|
222
|
-
ok: false,
|
|
223
|
-
error: "providerId y modelId son obligatorios. Usá get_available_models para consultar los modelos disponibles antes de crear el agente."
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
error: "providerId y modelId son obligatorios. Usá get_available_models para consultar los modelos disponibles antes de crear el agente."
|
|
224
232
|
};
|
|
225
233
|
}
|
|
226
234
|
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
const [providerEntry, modelEntry] = await Promise.all([
|
|
231
|
-
providersCol.get(providerId),
|
|
232
|
-
modelsCol.get(modelId),
|
|
233
|
-
]);
|
|
235
|
+
// Validar que el provider existe y está activo
|
|
236
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
237
|
+
const providerEntry = await providersCol.get(providerId);
|
|
234
238
|
|
|
235
239
|
if (!providerEntry) {
|
|
236
|
-
return {
|
|
237
|
-
ok: false,
|
|
238
|
-
error: `Provider '${providerId}' no existe. Usá get_available_models para ver providers disponibles.`
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
error: `Provider '${providerId}' no existe. Usá get_available_models para ver providers disponibles.`
|
|
239
243
|
};
|
|
240
244
|
}
|
|
245
|
+
|
|
241
246
|
if (!providerEntry.doc.enabled || !providerEntry.doc.active) {
|
|
242
|
-
return {
|
|
243
|
-
ok: false,
|
|
244
|
-
error: `Provider '${providerId}' no está activo. Usá get_available_models para ver providers activos.`
|
|
247
|
+
return {
|
|
248
|
+
ok: false,
|
|
249
|
+
error: `Provider '${providerId}' no está activo. Usá get_available_models para ver providers activos.`
|
|
245
250
|
};
|
|
246
251
|
}
|
|
247
252
|
|
|
253
|
+
// Validar que el modelo existe y pertenece al provider ya validado. `active` en un
|
|
254
|
+
// ModelDoc solo marca el modelo por defecto del usuario (elegido en onboarding), no
|
|
255
|
+
// si el modelo es utilizable — cualquier modelo del provider configurado sirve.
|
|
256
|
+
const modelsCol = await col<ModelDoc>("models");
|
|
257
|
+
const modelEntry = await modelsCol.get(modelId);
|
|
258
|
+
|
|
248
259
|
if (!modelEntry) {
|
|
249
|
-
return {
|
|
250
|
-
ok: false,
|
|
251
|
-
error: `Modelo '${modelId}' no existe. Usá get_available_models para ver modelos disponibles.`
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
error: `Modelo '${modelId}' no existe. Usá get_available_models para ver modelos disponibles.`
|
|
252
263
|
};
|
|
253
264
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
|
|
266
|
+
if (!modelEntry.doc.enabled) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
error: `Modelo '${modelId}' no está habilitado. Usá get_available_models para ver modelos disponibles.`
|
|
258
270
|
};
|
|
259
271
|
}
|
|
260
272
|
|
|
273
|
+
if (modelEntry.doc.provider_id !== providerId) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
error: `Modelo '${modelId}' pertenece al provider '${modelEntry.doc.provider_id}', no a '${providerId}'.`
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (mcpServerId) {
|
|
281
|
+
if (!userId) {
|
|
282
|
+
return { ok: false, error: "No se puede asignar un servidor MCP sin contexto de usuario." };
|
|
283
|
+
}
|
|
284
|
+
const serverEntry = await (await col<McpServerDoc>("mcpServers")).get(mcpServerId);
|
|
285
|
+
if (!serverEntry?.doc.enabled) {
|
|
286
|
+
return { ok: false, error: `El servidor MCP '${mcpServerId}' no existe o está deshabilitado.` };
|
|
287
|
+
}
|
|
288
|
+
if (serverEntry.doc.user_id && serverEntry.doc.user_id !== userId) {
|
|
289
|
+
return { ok: false, error: `El servidor MCP '${mcpServerId}' pertenece a otro usuario.` };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const existingSpecialist = (await (await col<AgentDoc>("agents")).scan({}))
|
|
293
|
+
.map((entry) => entry.doc)
|
|
294
|
+
.find((agent) => {
|
|
295
|
+
if (agent.role !== "worker" || agent.user_id !== userId) return false;
|
|
296
|
+
try {
|
|
297
|
+
return agent.mcp_server_ids_json
|
|
298
|
+
? (JSON.parse(agent.mcp_server_ids_json) as string[]).includes(mcpServerId)
|
|
299
|
+
: false;
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (existingSpecialist) {
|
|
305
|
+
const action = existingSpecialist.enabled
|
|
306
|
+
? "Reutilizalo con task_delegate."
|
|
307
|
+
: "Está deshabilitado; el usuario debe reactivarlo desde Agentes.";
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
existingAgentId: existingSpecialist.id,
|
|
311
|
+
error: `Ya existe el especialista '${existingSpecialist.name}' para ese servidor. ${action}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
261
316
|
try {
|
|
262
317
|
const agentId = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
263
|
-
const agentsCol = db.collection<HiveAgentDoc>("agents");
|
|
264
318
|
const now = Date.now();
|
|
265
319
|
|
|
320
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
266
321
|
await agentsCol.put(agentId, {
|
|
267
322
|
id: agentId,
|
|
268
|
-
userId,
|
|
323
|
+
user_id: userId ?? "",
|
|
269
324
|
name,
|
|
270
325
|
description,
|
|
271
|
-
systemPrompt,
|
|
272
|
-
|
|
326
|
+
system_prompt: systemPrompt,
|
|
327
|
+
tone,
|
|
273
328
|
role: "worker",
|
|
274
329
|
status: "idle",
|
|
275
|
-
parentId,
|
|
276
|
-
providerId,
|
|
277
|
-
modelId,
|
|
278
|
-
tone,
|
|
279
|
-
maxIterations,
|
|
280
|
-
workspace: parentWorkspace,
|
|
281
330
|
enabled: true,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
331
|
+
provider_id: toIndexable(providerId),
|
|
332
|
+
model_id: toIndexable(modelId),
|
|
333
|
+
tools_json: toolsJson,
|
|
334
|
+
skills_json: null,
|
|
335
|
+
active_mcp_json: null,
|
|
336
|
+
parent_id: toIndexable(parentId),
|
|
337
|
+
max_iterations: maxIterations,
|
|
338
|
+
workspace: parentWorkspace,
|
|
339
|
+
lastTraceAt: null,
|
|
340
|
+
created_at: now,
|
|
341
|
+
updated_at: now,
|
|
342
|
+
source: "user",
|
|
343
|
+
mcp_server_ids_json: mcpServerId ? JSON.stringify([mcpServerId]) : null,
|
|
344
|
+
}, { expectedVersion: 0 });
|
|
286
345
|
|
|
287
|
-
return {
|
|
288
|
-
ok: true,
|
|
289
|
-
agentId,
|
|
290
|
-
name,
|
|
291
|
-
providerId,
|
|
346
|
+
return {
|
|
347
|
+
ok: true,
|
|
348
|
+
agentId,
|
|
349
|
+
name,
|
|
350
|
+
providerId,
|
|
292
351
|
modelId,
|
|
352
|
+
mcpServerId: mcpServerId ?? null,
|
|
293
353
|
workspace: parentWorkspace,
|
|
294
|
-
message: "Agente creado exitosamente."
|
|
354
|
+
message: "Agente creado exitosamente."
|
|
295
355
|
};
|
|
296
356
|
} catch (error) {
|
|
297
357
|
return { ok: false, error: `Failed to create agent: ${(error as Error).message}` };
|
|
@@ -303,49 +363,81 @@ export const agentCreateTool: Tool = {
|
|
|
303
363
|
|
|
304
364
|
export const agentFindTool: Tool = {
|
|
305
365
|
name: "agent_find",
|
|
306
|
-
description: "
|
|
366
|
+
description: "Discover available worker agents. Includes global system catalog agents plus private workers owned by the current user. This tool does not report task execution; use task_list/task_status for that. Spanish: buscar agente, encontrar worker, localizar agente",
|
|
307
367
|
parameters: {
|
|
308
368
|
type: "object",
|
|
309
369
|
properties: {
|
|
310
370
|
search: { type: "string", description: "Search term for agent name or description" },
|
|
311
|
-
|
|
371
|
+
availability: { type: "string", enum: ["enabled", "disabled", "any"], description: "Filter by whether the worker can accept tasks" },
|
|
372
|
+
status: { type: "string", enum: ["idle", "active", "any"], description: "Deprecated compatibility alias. It does not represent task execution; use task_list." },
|
|
312
373
|
},
|
|
313
374
|
},
|
|
314
375
|
execute: async (params: Record<string, unknown>, config?: any) => {
|
|
315
|
-
const db = await getHiveDB();
|
|
316
376
|
const userId = config?.configurable?.user_id;
|
|
317
377
|
const search = params.search as string | undefined;
|
|
318
|
-
const
|
|
378
|
+
const legacyStatus = params.status as string | undefined;
|
|
379
|
+
const availability = (params.availability as string | undefined)
|
|
380
|
+
?? (legacyStatus === "any" ? "any" : legacyStatus ? "enabled" : "any");
|
|
319
381
|
|
|
320
382
|
try {
|
|
321
|
-
const agentsCol =
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
383
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
384
|
+
const mcpServersCol = await col<McpServerDoc>("mcpServers");
|
|
385
|
+
const serverNames = new Map(
|
|
386
|
+
(await mcpServersCol.scan({})).map((entry) => [entry.doc.id, entry.doc.name]),
|
|
387
|
+
);
|
|
388
|
+
let agents = (await agentsCol.scan({}))
|
|
325
389
|
.map(e => e.doc)
|
|
326
|
-
.filter(a =>
|
|
390
|
+
.filter(a =>
|
|
391
|
+
a.role === "worker"
|
|
392
|
+
&& (a.source === "catalog" || (!!userId && a.user_id === userId))
|
|
393
|
+
);
|
|
327
394
|
|
|
328
395
|
if (search) {
|
|
329
|
-
const
|
|
330
|
-
agents = agents.filter(a =>
|
|
331
|
-
|
|
332
|
-
|
|
396
|
+
const needle = search.toLowerCase();
|
|
397
|
+
agents = agents.filter((a) => {
|
|
398
|
+
let assignedServerIds: string[] = [];
|
|
399
|
+
try {
|
|
400
|
+
assignedServerIds = a.mcp_server_ids_json ? JSON.parse(a.mcp_server_ids_json) : [];
|
|
401
|
+
} catch {
|
|
402
|
+
assignedServerIds = [];
|
|
403
|
+
}
|
|
404
|
+
return a.name.toLowerCase().includes(needle)
|
|
405
|
+
|| (a.description ?? "").toLowerCase().includes(needle)
|
|
406
|
+
|| assignedServerIds.some((id) =>
|
|
407
|
+
id.toLowerCase().includes(needle)
|
|
408
|
+
|| (serverNames.get(id) ?? "").toLowerCase().includes(needle),
|
|
409
|
+
);
|
|
410
|
+
});
|
|
333
411
|
}
|
|
334
412
|
|
|
335
|
-
if (
|
|
336
|
-
agents = agents.filter(a => a.
|
|
413
|
+
if (availability !== "any") {
|
|
414
|
+
agents = agents.filter(a => availability === "enabled" ? a.enabled : !a.enabled);
|
|
337
415
|
}
|
|
338
416
|
|
|
339
417
|
return {
|
|
340
418
|
ok: true,
|
|
341
419
|
count: agents.length,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
420
|
+
execution_source: "Use task_list or task_status for real execution state.",
|
|
421
|
+
...(legacyStatus ? { warning: "The status filter is deprecated and cannot prove whether a task is running." } : {}),
|
|
422
|
+
agents: agents.map((a) => {
|
|
423
|
+
let mcpServerIds: string[] = [];
|
|
424
|
+
try {
|
|
425
|
+
mcpServerIds = a.mcp_server_ids_json ? JSON.parse(a.mcp_server_ids_json) : [];
|
|
426
|
+
} catch {
|
|
427
|
+
mcpServerIds = [];
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
id: a.id,
|
|
431
|
+
name: a.name,
|
|
432
|
+
description: a.description,
|
|
433
|
+
role: a.role,
|
|
434
|
+
source: a.source ?? "user",
|
|
435
|
+
enabled: a.enabled,
|
|
436
|
+
availability: a.enabled ? "enabled" : "disabled",
|
|
437
|
+
mcpServerIds,
|
|
438
|
+
mcpServers: mcpServerIds.map((id) => ({ id, name: serverNames.get(id) ?? id })),
|
|
439
|
+
};
|
|
440
|
+
}),
|
|
349
441
|
};
|
|
350
442
|
} catch (error) {
|
|
351
443
|
return { ok: false, error: `Failed to find agents: ${(error as Error).message}` };
|
|
@@ -357,27 +449,36 @@ export const agentFindTool: Tool = {
|
|
|
357
449
|
|
|
358
450
|
export const agentArchiveTool: Tool = {
|
|
359
451
|
name: "agent_archive",
|
|
360
|
-
description: "Archive or terminate a worker
|
|
452
|
+
description: "Archive or terminate a worker you created. Catalog agents cannot be archived — only the user can disable those from the UI. Spanish: archivar agente, terminar worker",
|
|
361
453
|
parameters: {
|
|
362
454
|
type: "object",
|
|
363
455
|
properties: {
|
|
364
|
-
agentId: { type: "string", description: "ID of the
|
|
456
|
+
agentId: { type: "string", description: "ID of the worker to archive (not a catalog agent)" },
|
|
365
457
|
},
|
|
366
458
|
required: ["agentId"],
|
|
367
459
|
},
|
|
368
460
|
execute: async (params: Record<string, unknown>) => {
|
|
369
|
-
const db = await getHiveDB();
|
|
370
461
|
const agentId = params.agentId as string;
|
|
371
462
|
|
|
372
463
|
try {
|
|
373
|
-
const agentsCol =
|
|
374
|
-
const
|
|
464
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
465
|
+
const existing = await agentsCol.get(agentId);
|
|
375
466
|
|
|
376
|
-
if (!
|
|
467
|
+
if (!existing) {
|
|
377
468
|
return { ok: false, error: `Agent not found: ${agentId}` };
|
|
378
469
|
}
|
|
379
470
|
|
|
380
|
-
|
|
471
|
+
// Catalog personas are shared capabilities of the whole hive — turning
|
|
472
|
+
// one off is the user's call, from the UI, never an agent's.
|
|
473
|
+
if (existing.doc.source === "catalog") {
|
|
474
|
+
return {
|
|
475
|
+
ok: false,
|
|
476
|
+
error: `'${existing.doc.name}' is a catalog agent and stays available. Only the user can disable it from the Agents UI.`,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await agentsCol.put(agentId, { ...existing.doc, enabled: false, status: "archived", updated_at: Date.now() }, { expectedVersion: existing.version });
|
|
481
|
+
|
|
381
482
|
return { ok: true, agentId, message: "Agent archived." };
|
|
382
483
|
} catch (error) {
|
|
383
484
|
return { ok: false, error: `Failed to archive agent: ${(error as Error).message}` };
|
|
@@ -389,144 +490,563 @@ export const agentArchiveTool: Tool = {
|
|
|
389
490
|
|
|
390
491
|
export const taskDelegateTool: Tool = {
|
|
391
492
|
name: "task_delegate",
|
|
392
|
-
description: "Delegate a task to
|
|
493
|
+
description: "Delegate a bounded task to an existing worker_id (any `agents` row: catalog-seeded or agent_create-made). The delivery goes through deterministic acceptance checks (no LLM); you judge anything they don't cover in your closing turn, and use task_revise to send it back with feedback if it doesn't meet its criteria. mode=sync blocks the conversation until done; mode=async enqueues and frees the conversation immediately — the user is notified automatically in this same chat when the worker finishes. Prefer async unless you expect the result in a few seconds.",
|
|
393
494
|
parameters: {
|
|
394
495
|
type: "object",
|
|
395
496
|
properties: {
|
|
396
|
-
worker_id: { type: "string", description: "ID
|
|
497
|
+
worker_id: { type: "string", description: "Target agent ID — from agent_find or the catalog agent list in the system prompt." },
|
|
397
498
|
task_description: { type: "string", description: "Clear, detailed instructions for the worker" },
|
|
398
|
-
|
|
399
|
-
|
|
499
|
+
acceptance: {
|
|
500
|
+
type: "array",
|
|
501
|
+
description: "Verifiable acceptance criteria. The worker's default criteria are used when omitted.",
|
|
502
|
+
items: {
|
|
503
|
+
type: "object",
|
|
504
|
+
properties: {
|
|
505
|
+
id: { type: "string" },
|
|
506
|
+
description: { type: "string" },
|
|
507
|
+
checkTool: { type: "string" },
|
|
508
|
+
},
|
|
509
|
+
required: ["id", "description"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
mode: { type: "string", enum: ["sync", "async"], description: "sync (default, blocking, 2min timeout — only for very short delegations) or async (enqueued, frees the conversation instantly; outcome is relayed back to the user automatically). Prefer async for anything non-trivial." },
|
|
400
513
|
},
|
|
401
|
-
required: ["
|
|
514
|
+
required: ["task_description"],
|
|
402
515
|
},
|
|
403
516
|
execute: async (params: Record<string, unknown>, config?: any) => {
|
|
404
|
-
const
|
|
405
|
-
const workerId = params.worker_id as string;
|
|
517
|
+
const agentId = params.worker_id as string | undefined;
|
|
406
518
|
const taskDescription = params.task_description as string;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
519
|
+
const mode = (params.mode as string) ?? "sync";
|
|
520
|
+
const turnId = config?.configurable?.turn_id as string | undefined;
|
|
409
521
|
|
|
410
|
-
|
|
411
|
-
const worker = db.query<any, [string]>(
|
|
412
|
-
"SELECT id, name, enabled FROM agents WHERE id = ?"
|
|
413
|
-
).get(workerId);
|
|
522
|
+
if (!agentId) return { ok: false, error: "Provide worker_id." };
|
|
414
523
|
|
|
415
|
-
|
|
416
|
-
|
|
524
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
525
|
+
const parentAgentId = config?.configurable?.agent_id ?? "";
|
|
526
|
+
if (!parentAgentId) {
|
|
527
|
+
return { ok: false, error: "Delegation caller identity is missing (config.configurable.agent_id). The coordinator may exist, but its ID was not propagated to task_delegate." };
|
|
417
528
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
529
|
+
const parentEntry = await agentsCol.get(parentAgentId);
|
|
530
|
+
if (!parentEntry) return { ok: false, error: `Delegation caller agent not found: ${parentAgentId}` };
|
|
531
|
+
if (!parentEntry.doc.enabled) return { ok: false, error: `Delegation caller agent is disabled: ${parentAgentId}` };
|
|
532
|
+
const parent = parentEntry.doc;
|
|
533
|
+
const parentProviderId = fromIndexable(parent.provider_id);
|
|
534
|
+
const parentModelId = fromIndexable(parent.model_id);
|
|
535
|
+
|
|
536
|
+
const workerEntry = await agentsCol.get(agentId);
|
|
537
|
+
if (!workerEntry) return { ok: false, error: `Agent not found: ${agentId}` };
|
|
538
|
+
const worker = workerEntry.doc;
|
|
539
|
+
if (!worker.enabled) return { ok: false, error: `Agent is disabled: ${worker.name}` };
|
|
540
|
+
|
|
541
|
+
const taskName = taskDescription.slice(0, 60);
|
|
542
|
+
const defaultAcceptance: AgentAcceptanceCriterion[] | null = worker.default_acceptance_json
|
|
543
|
+
? JSON.parse(worker.default_acceptance_json)
|
|
425
544
|
: null;
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
545
|
+
const acceptance = ((params.acceptance as AcceptanceCriterion[] | undefined)
|
|
546
|
+
?? defaultAcceptance?.map((criterion) => ({
|
|
547
|
+
id: criterion.id,
|
|
548
|
+
description: criterion.description,
|
|
549
|
+
checkTool: criterion.check_tool,
|
|
550
|
+
}))
|
|
551
|
+
?? [{ id: "objective", description: taskDescription }]);
|
|
552
|
+
|
|
553
|
+
// ── Async mode: create TaskDoc + enqueue worker_task in durable queue ──
|
|
554
|
+
if (mode === "async") {
|
|
555
|
+
try {
|
|
556
|
+
const { nextId, toIndexable, updateDoc } = await import("../../storage/hive.ts");
|
|
557
|
+
const { createRun } = await import("../../agent/run-store.ts");
|
|
558
|
+
const { getDurableQueue } = await import("../../gateway/durable-queue.ts");
|
|
559
|
+
|
|
560
|
+
const taskId = await nextId("tasks");
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
const tasksCol = await col<TaskDoc>("tasks");
|
|
563
|
+
await tasksCol.put(taskId, {
|
|
564
|
+
id: taskId,
|
|
565
|
+
agent_id: toIndexable(agentId),
|
|
566
|
+
name: taskName,
|
|
567
|
+
description: taskDescription,
|
|
568
|
+
status: "pending",
|
|
569
|
+
progress: 0,
|
|
570
|
+
result: null,
|
|
571
|
+
error: null,
|
|
572
|
+
metadata: null,
|
|
573
|
+
job_id: null,
|
|
574
|
+
run_id: null,
|
|
575
|
+
thread_id: null,
|
|
576
|
+
delegation_group_id: turnId ?? null,
|
|
577
|
+
catalog_agent_id: toIndexable(worker.source === "catalog" ? agentId : null),
|
|
578
|
+
started_at: null,
|
|
579
|
+
attempts: 0,
|
|
580
|
+
created_at: now,
|
|
581
|
+
updated_at: now,
|
|
582
|
+
completed_at: null,
|
|
583
|
+
}, { expectedVersion: 0 });
|
|
584
|
+
|
|
585
|
+
if (turnId) {
|
|
586
|
+
const { registerDelegatedTask } = await import("../../gateway/delegation-groups.ts");
|
|
587
|
+
await registerDelegatedTask({
|
|
588
|
+
turnId,
|
|
589
|
+
taskId,
|
|
590
|
+
threadId: config?.configurable?.thread_id ?? "",
|
|
591
|
+
channel: config?.configurable?.channel,
|
|
592
|
+
userId: config?.configurable?.user_id,
|
|
593
|
+
sessionId: config?.configurable?.session_id,
|
|
594
|
+
coordinatorAgentId: parentAgentId,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const run = await createRun({
|
|
599
|
+
thread_id: `task-${taskId}-${agentId}`,
|
|
600
|
+
agent_id: agentId,
|
|
601
|
+
user_id: config?.configurable?.user_id ?? "",
|
|
602
|
+
channel: config?.configurable?.channel ?? null,
|
|
603
|
+
kind: "worker",
|
|
604
|
+
max_iterations: worker.max_iterations || 10,
|
|
605
|
+
resume_policy: "resume",
|
|
606
|
+
acceptance,
|
|
607
|
+
catalog_agent_id: worker.source === "catalog" ? agentId : undefined,
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const queue = getDurableQueue();
|
|
611
|
+
const job = await queue.enqueue({
|
|
612
|
+
lane: `task:${taskId}`,
|
|
613
|
+
type: "worker_task",
|
|
614
|
+
run_id: run.id,
|
|
615
|
+
payload: {
|
|
616
|
+
workerId: agentId,
|
|
617
|
+
taskDescription,
|
|
618
|
+
taskName,
|
|
619
|
+
taskId,
|
|
620
|
+
acceptance,
|
|
621
|
+
parentAgentId,
|
|
622
|
+
parentProviderId,
|
|
623
|
+
parentModelId,
|
|
624
|
+
userId: config?.configurable?.user_id ?? "",
|
|
625
|
+
workspace: config?.configurable?.workspace ?? null,
|
|
626
|
+
// Delegating conversation's thread — lets delegation-notify.ts relay
|
|
627
|
+
// the outcome back to the user once this job reaches a terminal state.
|
|
628
|
+
originThreadId: config?.configurable?.thread_id ?? null,
|
|
629
|
+
originChannel: config?.configurable?.channel ?? null,
|
|
630
|
+
originSessionId: config?.configurable?.session_id ?? null,
|
|
631
|
+
turnId: turnId ?? null,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
await updateDoc<TaskDoc>("tasks", taskId, {
|
|
636
|
+
job_id: job.id,
|
|
637
|
+
run_id: run.id,
|
|
638
|
+
thread_id: `task-${taskId}-${agentId}`,
|
|
639
|
+
updated_at: Date.now(),
|
|
640
|
+
} as Partial<TaskDoc>);
|
|
641
|
+
|
|
642
|
+
agentBus.notifyTaskStarted(agentId, worker.name, 0, taskName, "");
|
|
643
|
+
if (turnId) {
|
|
644
|
+
const { publishNarration } = await import("../../events/narration.ts");
|
|
645
|
+
await publishNarration({
|
|
646
|
+
turnId,
|
|
647
|
+
threadId: config?.configurable?.thread_id ?? "",
|
|
648
|
+
channel: config?.configurable?.channel,
|
|
649
|
+
userId: config?.configurable?.user_id,
|
|
650
|
+
sessionId: config?.configurable?.session_id,
|
|
651
|
+
agentId,
|
|
652
|
+
agentName: worker.name,
|
|
653
|
+
kind: "delegated",
|
|
654
|
+
status: "queued",
|
|
655
|
+
label: `Delegué “${taskName}” a ${worker.name}`,
|
|
656
|
+
dedupeKey: `delegated:${taskId}`,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
435
659
|
|
|
436
|
-
|
|
437
|
-
|
|
660
|
+
return {
|
|
661
|
+
ok: true,
|
|
662
|
+
task_id: taskId,
|
|
663
|
+
job_id: job.id,
|
|
664
|
+
run_id: run.id,
|
|
665
|
+
worker_id: agentId,
|
|
666
|
+
worker_name: worker.name,
|
|
667
|
+
status: "queued",
|
|
668
|
+
message: `Task enqueued (async). Use task_list for the queue or task_status with task_id="${taskId}" for this task.`,
|
|
669
|
+
};
|
|
670
|
+
} catch (err) {
|
|
671
|
+
return { ok: false, error: `Async delegation failed: ${(err as Error).message}` };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
438
674
|
|
|
439
|
-
|
|
675
|
+
// ── Sync mode: blocking execution with 2min timeout ──
|
|
676
|
+
const { prepareDelegation } = await import("../../agent/delegation-runtime.ts");
|
|
677
|
+
const { getMCPManager } = await import("../../mcp/singleton.ts");
|
|
678
|
+
const mcpManager = getMCPManager();
|
|
440
679
|
|
|
680
|
+
let prepared: PreparedDelegation;
|
|
441
681
|
try {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
agentId: workerId,
|
|
448
|
-
taskDescription,
|
|
449
|
-
threadId,
|
|
682
|
+
prepared = await prepareDelegation(agentId, {
|
|
683
|
+
workspace: config?.configurable?.workspace ?? null,
|
|
684
|
+
parentProviderId,
|
|
685
|
+
parentModelId,
|
|
686
|
+
mcpManager,
|
|
450
687
|
});
|
|
688
|
+
} catch (err) {
|
|
689
|
+
return { ok: false, error: (err as Error).message };
|
|
690
|
+
}
|
|
451
691
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
).run(result, taskId);
|
|
457
|
-
emitCanvas("canvas:node_update", { id: taskId.toString(), type: "task", data: { status: "completed", progress: 100 } });
|
|
458
|
-
|
|
459
|
-
// Recalculate project progress if project_id provided
|
|
460
|
-
if (resolvedProjectId) {
|
|
461
|
-
const rows = db.query<any, [string]>(
|
|
462
|
-
"SELECT AVG(progress) as avg FROM tasks WHERE project_id=?"
|
|
463
|
-
).get(resolvedProjectId);
|
|
464
|
-
const avg = Math.round(rows?.avg ?? 0);
|
|
465
|
-
db.query("UPDATE projects SET progress=?, updated_at=unixepoch() WHERE id=?")
|
|
466
|
-
.run(avg, resolvedProjectId);
|
|
467
|
-
emitCanvas("canvas:node_update", { id: resolvedProjectId, type: "project", data: { progress: avg } });
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Notify Agent Bus: task completed
|
|
472
|
-
agentBus.notifyTaskCompleted(workerId, worker.name, taskId ?? 0, taskName, resolvedProjectId, result);
|
|
473
|
-
|
|
474
|
-
const finalProgress = resolvedProjectId
|
|
475
|
-
? (db.query<any, [string]>("SELECT progress FROM projects WHERE id=?").get(resolvedProjectId)?.progress ?? null)
|
|
476
|
-
: null;
|
|
692
|
+
agentBus.notifyTaskStarted(agentId, worker.name, 0, taskName, "");
|
|
693
|
+
log.info(`[task_delegate] Delegating (sync) to ${worker.name} (${agentId})`);
|
|
694
|
+
const syncDelegationRef = `sync-${Date.now()}-${agentId}`;
|
|
695
|
+
emitDelegationStarted({ workerId: agentId, parentAgentId, taskRef: syncDelegationRef, taskName });
|
|
477
696
|
|
|
697
|
+
try {
|
|
698
|
+
const { runAgentIsolated, withTimeout } = await import("../../agent/agent-loop.ts");
|
|
699
|
+
|
|
700
|
+
const threadId = `task-${Date.now()}-${agentId}`;
|
|
701
|
+
const SYNC_TIMEOUT_MS = 2 * 60 * 1000;
|
|
702
|
+
|
|
703
|
+
// Real cancellation (e.g. the user's "stop" button): the job's AbortSignal
|
|
704
|
+
// reaches us via config.signal (tool-runtime/index.ts's executeToolBatch),
|
|
705
|
+
// and runAgentIsolated/runAgent already honor it mid-run. withTimeout stays
|
|
706
|
+
// as a hard ceiling in case the signal path doesn't stop things in time.
|
|
707
|
+
const signal = config?.signal as AbortSignal | undefined;
|
|
708
|
+
const result = await withTimeout(
|
|
709
|
+
() => runAgentIsolated({
|
|
710
|
+
agentId,
|
|
711
|
+
taskDescription,
|
|
712
|
+
threadId,
|
|
713
|
+
mcpManager,
|
|
714
|
+
signal,
|
|
715
|
+
}),
|
|
716
|
+
SYNC_TIMEOUT_MS,
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
agentBus.notifyTaskCompleted(agentId, worker.name, 0, taskName, "", result);
|
|
720
|
+
|
|
721
|
+
// Deterministic acceptance checks now always run, closing the old gap
|
|
722
|
+
// where a plain worker_id delegation (agent_create) skipped them
|
|
723
|
+
// entirely in sync mode. No LLM call: the calling agent (usually the
|
|
724
|
+
// coordinator) judges the delivery itself in this same tool response.
|
|
725
|
+
const { runAcceptanceChecks, recordAgentOutcome } = await import("../../agent/acceptance-checks.ts");
|
|
726
|
+
const checks = await runAcceptanceChecks({
|
|
727
|
+
objective: taskDescription,
|
|
728
|
+
acceptance,
|
|
729
|
+
delivery: result,
|
|
730
|
+
evidence: [result],
|
|
731
|
+
});
|
|
732
|
+
if (checks.status === "failed") {
|
|
733
|
+
await recordAgentOutcome(agentId, "harmful");
|
|
734
|
+
emitWorkEvent({
|
|
735
|
+
phase: "review_failed",
|
|
736
|
+
taskRef: syncDelegationRef,
|
|
737
|
+
taskName,
|
|
738
|
+
actorId: parentAgentId || agentId,
|
|
739
|
+
targetId: agentId,
|
|
740
|
+
detail: checks.summary,
|
|
741
|
+
});
|
|
742
|
+
return {
|
|
743
|
+
ok: false,
|
|
744
|
+
status: checks.status,
|
|
745
|
+
error: checks.summary,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
await recordAgentOutcome(agentId, "helpful");
|
|
749
|
+
emitWorkEvent({
|
|
750
|
+
phase: "review_passed",
|
|
751
|
+
taskRef: syncDelegationRef,
|
|
752
|
+
taskName,
|
|
753
|
+
actorId: parentAgentId || agentId,
|
|
754
|
+
targetId: agentId,
|
|
755
|
+
});
|
|
756
|
+
emitWorkEvent({
|
|
757
|
+
phase: "completed",
|
|
758
|
+
taskRef: syncDelegationRef,
|
|
759
|
+
taskName,
|
|
760
|
+
actorId: agentId,
|
|
761
|
+
targetId: parentAgentId || null,
|
|
762
|
+
});
|
|
478
763
|
return {
|
|
479
764
|
ok: true,
|
|
480
|
-
worker_id:
|
|
765
|
+
worker_id: agentId,
|
|
481
766
|
worker_name: worker.name,
|
|
482
|
-
|
|
767
|
+
acceptance,
|
|
768
|
+
checks,
|
|
483
769
|
result,
|
|
484
|
-
project_progress: finalProgress,
|
|
485
770
|
};
|
|
486
771
|
} catch (err) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
772
|
+
const errorMessage = (err as Error).message;
|
|
773
|
+
const wasAborted = (config?.signal as AbortSignal | undefined)?.aborted === true;
|
|
774
|
+
agentBus.notifyTaskFailed(agentId, worker.name, 0, taskName, "", errorMessage);
|
|
775
|
+
emitWorkEvent({
|
|
776
|
+
phase: wasAborted ? "aborted" : "failed",
|
|
777
|
+
taskRef: syncDelegationRef,
|
|
778
|
+
taskName,
|
|
779
|
+
actorId: agentId,
|
|
780
|
+
targetId: parentAgentId || null,
|
|
781
|
+
detail: wasAborted ? "Trabajo interrumpido" : errorMessage,
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
ok: false,
|
|
786
|
+
worker_id: agentId,
|
|
787
|
+
error: errorMessage,
|
|
788
|
+
};
|
|
789
|
+
} finally {
|
|
790
|
+
emitDelegationFinished({ workerId: agentId, taskRef: syncDelegationRef });
|
|
791
|
+
await prepared.release();
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// ─── task_revise ─────────────────────────────────────────────────────────────
|
|
797
|
+
|
|
798
|
+
const MAX_TASK_REVISIONS = 2;
|
|
799
|
+
|
|
800
|
+
export const taskReviseTool: Tool = {
|
|
801
|
+
name: "task_revise",
|
|
802
|
+
description: "Send a completed or blocked delegated task back to its worker with concrete feedback, instead of reporting it as done. The worker resumes on the SAME thread — it keeps its prior context, so the feedback only needs to describe what's missing. Use this when a delivery doesn't meet its acceptance criteria and you can't fix it yourself.",
|
|
803
|
+
parameters: {
|
|
804
|
+
type: "object",
|
|
805
|
+
properties: {
|
|
806
|
+
task_id: { type: "string", description: "The task_id from task_delegate's result." },
|
|
807
|
+
feedback: { type: "string", description: "Concrete, actionable feedback: what's wrong and what the worker still needs to do." },
|
|
808
|
+
acceptance: {
|
|
809
|
+
type: "array",
|
|
810
|
+
description: "Updated acceptance criteria, if they need to change. Defaults to the original task's criteria.",
|
|
811
|
+
items: {
|
|
812
|
+
type: "object",
|
|
813
|
+
properties: {
|
|
814
|
+
id: { type: "string" },
|
|
815
|
+
description: { type: "string" },
|
|
816
|
+
checkTool: { type: "string" },
|
|
817
|
+
},
|
|
818
|
+
required: ["id", "description"],
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
required: ["task_id", "feedback"],
|
|
823
|
+
},
|
|
824
|
+
execute: async (params: Record<string, unknown>, config?: any) => {
|
|
825
|
+
const taskId = params.task_id as string | undefined;
|
|
826
|
+
const feedback = params.feedback as string | undefined;
|
|
827
|
+
if (!taskId) return { ok: false, error: "Provide task_id." };
|
|
828
|
+
if (!feedback || !feedback.trim()) return { ok: false, error: "Provide feedback describing what's missing." };
|
|
829
|
+
|
|
830
|
+
const parentAgentId = config?.configurable?.agent_id ?? "";
|
|
831
|
+
if (!parentAgentId) {
|
|
832
|
+
return { ok: false, error: "Caller identity is missing (config.configurable.agent_id)." };
|
|
833
|
+
}
|
|
834
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
835
|
+
const parentEntry = await agentsCol.get(parentAgentId);
|
|
836
|
+
if (!parentEntry) return { ok: false, error: `Caller agent not found: ${parentAgentId}` };
|
|
837
|
+
const parentProviderId = fromIndexable(parentEntry.doc.provider_id);
|
|
838
|
+
const parentModelId = fromIndexable(parentEntry.doc.model_id);
|
|
839
|
+
|
|
840
|
+
const tasksCol = await col<TaskDoc>("tasks");
|
|
841
|
+
const taskEntry = await tasksCol.get(taskId);
|
|
842
|
+
if (!taskEntry) return { ok: false, error: `Task not found: ${taskId}` };
|
|
843
|
+
const task = taskEntry.doc;
|
|
844
|
+
if (task.status !== "completed" && task.status !== "blocked") {
|
|
845
|
+
return { ok: false, error: `Task ${taskId} is "${task.status}" — only completed or blocked tasks can be revised.` };
|
|
846
|
+
}
|
|
847
|
+
if ((task.attempts ?? 0) >= MAX_TASK_REVISIONS) {
|
|
848
|
+
return { ok: false, error: `Task ${taskId} already used its ${MAX_TASK_REVISIONS} revision attempt(s). Fix it yourself or report the limit to the user.` };
|
|
849
|
+
}
|
|
850
|
+
if (!task.thread_id) {
|
|
851
|
+
return { ok: false, error: `Task ${taskId} has no thread_id — cannot resume its worker.` };
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const agentId = fromIndexable(task.agent_id);
|
|
855
|
+
const workerEntry = agentId ? await agentsCol.get(agentId) : null;
|
|
856
|
+
if (!workerEntry) return { ok: false, error: `Worker not found for task ${taskId}: ${agentId}` };
|
|
857
|
+
const worker = workerEntry.doc;
|
|
858
|
+
if (!worker.enabled) return { ok: false, error: `Worker is disabled: ${worker.name}` };
|
|
859
|
+
|
|
860
|
+
const { createRun, getRun, deserializeAcceptance } = await import("../../agent/run-store.ts");
|
|
861
|
+
const { getDurableQueue } = await import("../../gateway/durable-queue.ts");
|
|
862
|
+
const { updateDoc } = await import("../../storage/hive.ts");
|
|
863
|
+
|
|
864
|
+
const previousRun = task.run_id ? await getRun(task.run_id) : null;
|
|
865
|
+
const previousAcceptance = previousRun ? deserializeAcceptance(previousRun) : null;
|
|
866
|
+
const acceptance = (params.acceptance as AcceptanceCriterion[] | undefined) ?? previousAcceptance ?? [{ id: "objective", description: task.description ?? feedback }];
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
const run = await createRun({
|
|
870
|
+
thread_id: task.thread_id,
|
|
871
|
+
agent_id: agentId!,
|
|
872
|
+
user_id: config?.configurable?.user_id ?? "",
|
|
873
|
+
channel: config?.configurable?.channel ?? null,
|
|
874
|
+
kind: "worker",
|
|
875
|
+
max_iterations: worker.max_iterations || 10,
|
|
876
|
+
resume_policy: "resume",
|
|
877
|
+
acceptance,
|
|
878
|
+
catalog_agent_id: worker.source === "catalog" ? agentId! : undefined,
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
const turnId = config?.configurable?.turn_id as string | undefined;
|
|
882
|
+
if (turnId) {
|
|
883
|
+
const { registerDelegatedTask } = await import("../../gateway/delegation-groups.ts");
|
|
884
|
+
await registerDelegatedTask({
|
|
885
|
+
turnId,
|
|
886
|
+
taskId,
|
|
887
|
+
threadId: config?.configurable?.thread_id ?? "",
|
|
888
|
+
channel: config?.configurable?.channel,
|
|
889
|
+
userId: config?.configurable?.user_id,
|
|
890
|
+
sessionId: config?.configurable?.session_id,
|
|
891
|
+
coordinatorAgentId: parentAgentId,
|
|
892
|
+
});
|
|
493
893
|
}
|
|
494
894
|
|
|
495
|
-
|
|
496
|
-
|
|
895
|
+
const queue = getDurableQueue();
|
|
896
|
+
const job = await queue.enqueue({
|
|
897
|
+
lane: `task:${taskId}`,
|
|
898
|
+
type: "worker_task",
|
|
899
|
+
run_id: run.id,
|
|
900
|
+
payload: {
|
|
901
|
+
workerId: agentId,
|
|
902
|
+
taskDescription: `${task.description ?? ""}\n\nCORRECCIÓN SOLICITADA: ${feedback}`,
|
|
903
|
+
taskName: task.name,
|
|
904
|
+
taskId,
|
|
905
|
+
acceptance,
|
|
906
|
+
parentAgentId,
|
|
907
|
+
parentProviderId,
|
|
908
|
+
parentModelId,
|
|
909
|
+
userId: config?.configurable?.user_id ?? "",
|
|
910
|
+
workspace: config?.configurable?.workspace ?? null,
|
|
911
|
+
originThreadId: config?.configurable?.thread_id ?? null,
|
|
912
|
+
originChannel: config?.configurable?.channel ?? null,
|
|
913
|
+
originSessionId: config?.configurable?.session_id ?? null,
|
|
914
|
+
turnId: turnId ?? null,
|
|
915
|
+
revision: true,
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
await updateDoc<TaskDoc>("tasks", taskId, {
|
|
920
|
+
status: "pending",
|
|
921
|
+
error: null,
|
|
922
|
+
job_id: job.id,
|
|
923
|
+
run_id: run.id,
|
|
924
|
+
attempts: (task.attempts ?? 0) + 1,
|
|
925
|
+
delegation_group_id: turnId ?? task.delegation_group_id ?? null,
|
|
926
|
+
updated_at: Date.now(),
|
|
927
|
+
} as Partial<TaskDoc>);
|
|
928
|
+
|
|
929
|
+
const { recordAgentOutcome } = await import("../../agent/acceptance-checks.ts");
|
|
930
|
+
await recordAgentOutcome(agentId, "harmful");
|
|
931
|
+
|
|
932
|
+
emitWorkEvent({
|
|
933
|
+
phase: "review_failed",
|
|
934
|
+
taskRef: taskId,
|
|
935
|
+
taskName: task.name,
|
|
936
|
+
actorId: parentAgentId,
|
|
937
|
+
targetId: agentId!,
|
|
938
|
+
detail: feedback,
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
if (turnId) {
|
|
942
|
+
const { publishNarration } = await import("../../events/narration.ts");
|
|
943
|
+
await publishNarration({
|
|
944
|
+
turnId,
|
|
945
|
+
threadId: config?.configurable?.thread_id ?? "",
|
|
946
|
+
channel: config?.configurable?.channel,
|
|
947
|
+
userId: config?.configurable?.user_id,
|
|
948
|
+
sessionId: config?.configurable?.session_id,
|
|
949
|
+
agentId: agentId!,
|
|
950
|
+
agentName: worker.name,
|
|
951
|
+
kind: "delegated",
|
|
952
|
+
status: "queued",
|
|
953
|
+
label: `Devolví “${task.name}” a ${worker.name} con correcciones`,
|
|
954
|
+
dedupeKey: `revised:${taskId}:${(task.attempts ?? 0) + 1}`,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
497
957
|
|
|
498
958
|
return {
|
|
499
|
-
ok:
|
|
500
|
-
worker_id: workerId,
|
|
959
|
+
ok: true,
|
|
501
960
|
task_id: taskId,
|
|
502
|
-
|
|
961
|
+
job_id: job.id,
|
|
962
|
+
run_id: run.id,
|
|
963
|
+
worker_id: agentId,
|
|
964
|
+
attempts: (task.attempts ?? 0) + 1,
|
|
965
|
+
status: "queued",
|
|
966
|
+
message: `Revision enqueued (async). Use task_status with task_id="${taskId}" to check it.`,
|
|
503
967
|
};
|
|
968
|
+
} catch (err) {
|
|
969
|
+
return { ok: false, error: `Task revision failed: ${(err as Error).message}` };
|
|
504
970
|
}
|
|
505
971
|
},
|
|
506
972
|
};
|
|
507
973
|
|
|
508
|
-
// ───
|
|
974
|
+
// ─── task_list ──────────────────────────────────────────────────────────────
|
|
509
975
|
|
|
510
|
-
export const
|
|
511
|
-
name: "
|
|
512
|
-
description: "
|
|
976
|
+
export const taskListTool: Tool = {
|
|
977
|
+
name: "task_list",
|
|
978
|
+
description: "List real delegated task executions for the current user. TaskDoc and JobDoc are the source of truth. Use this instead of agent_find to determine whether work is pending, running, completed, failed, or blocked.",
|
|
513
979
|
parameters: {
|
|
514
980
|
type: "object",
|
|
515
981
|
properties: {
|
|
516
|
-
|
|
517
|
-
|
|
982
|
+
status: {
|
|
983
|
+
type: "string",
|
|
984
|
+
enum: ["pending", "running", "completed", "failed", "blocked", "all"],
|
|
985
|
+
description: "Execution-state filter (default: all)",
|
|
986
|
+
},
|
|
987
|
+
worker_id: { type: "string", description: "Optional worker agent ID" },
|
|
988
|
+
limit: { type: "number", description: "Maximum tasks to return (default 20, maximum 100)" },
|
|
518
989
|
},
|
|
519
|
-
required: ["cli", "task_instructions"],
|
|
520
990
|
},
|
|
521
|
-
execute: async (params: Record<string, unknown
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
991
|
+
execute: async (params: Record<string, unknown>, config?: any) => {
|
|
992
|
+
const userId = config?.configurable?.user_id as string | undefined;
|
|
993
|
+
if (!userId) return { ok: false, error: "Missing user context for task_list." };
|
|
994
|
+
|
|
995
|
+
const status = (params.status as string | undefined) ?? "all";
|
|
996
|
+
const workerId = params.worker_id as string | undefined;
|
|
997
|
+
const limit = Math.max(1, Math.min(100, Number(params.limit) || 20));
|
|
998
|
+
|
|
999
|
+
try {
|
|
1000
|
+
const tasksCol = await col<TaskDoc>("tasks");
|
|
1001
|
+
const runsCol = await col<import("../../storage/collections.ts").AgentRunDoc>("agentRuns");
|
|
1002
|
+
const { getJob } = await import("../../gateway/job-store.ts");
|
|
1003
|
+
const allTasks = (await tasksCol.scan({}))
|
|
1004
|
+
.map((entry) => entry.doc)
|
|
1005
|
+
.sort((a, b) => b.updated_at - a.updated_at);
|
|
1006
|
+
|
|
1007
|
+
const result: Array<Record<string, unknown>> = [];
|
|
1008
|
+
for (const task of allTasks) {
|
|
1009
|
+
if (result.length >= limit) break;
|
|
1010
|
+
if (workerId && fromIndexable(task.agent_id) !== workerId) continue;
|
|
1011
|
+
|
|
1012
|
+
const run = task.run_id ? await runsCol.get(task.run_id) : null;
|
|
1013
|
+
if (!run || run.doc.user_id !== userId) continue;
|
|
1014
|
+
|
|
1015
|
+
const job = task.job_id ? await getJob(task.job_id) : null;
|
|
1016
|
+
const executionState =
|
|
1017
|
+
task.status === "pending"
|
|
1018
|
+
? "pending"
|
|
1019
|
+
: task.status === "in_progress"
|
|
1020
|
+
? "running"
|
|
1021
|
+
: task.status;
|
|
1022
|
+
if (status !== "all" && executionState !== status) continue;
|
|
1023
|
+
|
|
1024
|
+
result.push({
|
|
1025
|
+
id: task.id,
|
|
1026
|
+
name: task.name,
|
|
1027
|
+
description: task.description,
|
|
1028
|
+
worker_id: fromIndexable(task.agent_id),
|
|
1029
|
+
status: task.status,
|
|
1030
|
+
execution_state: executionState,
|
|
1031
|
+
progress: task.progress,
|
|
1032
|
+
result: task.result,
|
|
1033
|
+
error: task.error,
|
|
1034
|
+
job_id: task.job_id,
|
|
1035
|
+
job_status: job?.status ?? null,
|
|
1036
|
+
run_id: task.run_id,
|
|
1037
|
+
run_status: run.doc.status,
|
|
1038
|
+
attempts: task.attempts ?? 0,
|
|
1039
|
+
created_at: task.created_at,
|
|
1040
|
+
started_at: task.started_at,
|
|
1041
|
+
updated_at: task.updated_at,
|
|
1042
|
+
completed_at: task.completed_at,
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return { ok: true, task_count: result.length, tasks: result };
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
return { ok: false, error: `Failed to list delegated tasks: ${(error as Error).message}` };
|
|
1049
|
+
}
|
|
530
1050
|
},
|
|
531
1051
|
};
|
|
532
1052
|
|
|
@@ -534,34 +1054,58 @@ export const taskDelegateCodeTool: Tool = {
|
|
|
534
1054
|
|
|
535
1055
|
export const taskStatusTool: Tool = {
|
|
536
1056
|
name: "task_status",
|
|
537
|
-
description: "Get execution status of one or more delegated tasks. Spanish: estado tarea delegada, verificar progreso, consultar tarea",
|
|
1057
|
+
description: "Get execution status of one or more delegated tasks. Accepts string or numeric IDs. Spanish: estado tarea delegada, verificar progreso, consultar tarea",
|
|
538
1058
|
parameters: {
|
|
539
1059
|
type: "object",
|
|
540
1060
|
properties: {
|
|
541
|
-
task_ids: {
|
|
1061
|
+
task_ids: {
|
|
1062
|
+
type: "array",
|
|
1063
|
+
description: "List of task IDs (strings or numbers)",
|
|
1064
|
+
items: { type: "string" },
|
|
1065
|
+
},
|
|
542
1066
|
},
|
|
543
1067
|
required: ["task_ids"],
|
|
544
1068
|
},
|
|
545
1069
|
execute: async (params: Record<string, unknown>) => {
|
|
546
|
-
const
|
|
547
|
-
const taskIds = params.task_ids as number[];
|
|
1070
|
+
const taskIds = params.task_ids as Array<string | number>;
|
|
548
1071
|
|
|
549
1072
|
try {
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
).
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
1073
|
+
const tasksCol = await col<TaskDoc>("tasks");
|
|
1074
|
+
const ids = taskIds.map((id) => String(id).padStart(15, "0"));
|
|
1075
|
+
const entries = await Promise.all(ids.map((id) => tasksCol.get(id)));
|
|
1076
|
+
const tasks = entries.filter((e): e is NonNullable<typeof e> => !!e).map((e) => e.doc);
|
|
1077
|
+
|
|
1078
|
+
const result = await Promise.all(tasks.map(async (t) => {
|
|
1079
|
+
let jobStatus: string | null = null;
|
|
1080
|
+
if (t.job_id) {
|
|
1081
|
+
try {
|
|
1082
|
+
const { getJob } = await import("../../gateway/job-store.ts");
|
|
1083
|
+
const job = await getJob(t.job_id);
|
|
1084
|
+
if (job) {
|
|
1085
|
+
jobStatus = job.status;
|
|
1086
|
+
}
|
|
1087
|
+
} catch { /* non-critical */ }
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
559
1090
|
id: t.id,
|
|
560
1091
|
name: t.name,
|
|
561
1092
|
status: t.status,
|
|
562
1093
|
progress: t.progress,
|
|
563
1094
|
result: t.result,
|
|
564
|
-
|
|
1095
|
+
error: t.error,
|
|
1096
|
+
job_id: t.job_id,
|
|
1097
|
+
run_id: t.run_id,
|
|
1098
|
+
job_status: jobStatus,
|
|
1099
|
+
attempts: t.attempts ?? 0,
|
|
1100
|
+
started_at: t.started_at,
|
|
1101
|
+
completed_at: t.completed_at,
|
|
1102
|
+
};
|
|
1103
|
+
}));
|
|
1104
|
+
|
|
1105
|
+
return {
|
|
1106
|
+
ok: true,
|
|
1107
|
+
task_count: result.length,
|
|
1108
|
+
tasks: result,
|
|
565
1109
|
};
|
|
566
1110
|
} catch (error) {
|
|
567
1111
|
return { ok: false, error: `Failed to get task status: ${(error as Error).message}` };
|
|
@@ -619,39 +1163,34 @@ export const busReadTool: Tool = {
|
|
|
619
1163
|
},
|
|
620
1164
|
},
|
|
621
1165
|
execute: async (params: Record<string, unknown>) => {
|
|
622
|
-
const db = getDb();
|
|
623
1166
|
const workerId = params.worker_id as string | undefined;
|
|
624
1167
|
const limit = (params.limit as number) ?? 10;
|
|
625
1168
|
|
|
626
1169
|
try {
|
|
627
|
-
|
|
628
|
-
|
|
1170
|
+
const messagesCol = await col<AgentBusMessageDoc>("agentBusMessages");
|
|
1171
|
+
let entries = (await messagesCol.scan({})).filter(e => !e.doc.read);
|
|
629
1172
|
|
|
630
1173
|
if (workerId) {
|
|
631
|
-
|
|
632
|
-
args.push(workerId);
|
|
1174
|
+
entries = entries.filter(e => e.doc.to_worker_id === workerId || e.doc.to_worker_id === BROADCAST);
|
|
633
1175
|
}
|
|
634
1176
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const messages = db.query(query).all(...args) as any[];
|
|
1177
|
+
entries.sort((a, b) => a.doc.created_at - b.doc.created_at);
|
|
1178
|
+
entries = entries.slice(0, limit);
|
|
639
1179
|
|
|
640
1180
|
// Mark as read
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
db.query(`UPDATE agent_bus_messages SET read = 1 WHERE id IN (${ids})`).run();
|
|
1181
|
+
for (const entry of entries) {
|
|
1182
|
+
await messagesCol.put(entry.id, { ...entry.doc, read: true }, { expectedVersion: entry.version });
|
|
644
1183
|
}
|
|
645
1184
|
|
|
646
1185
|
return {
|
|
647
1186
|
ok: true,
|
|
648
|
-
count:
|
|
649
|
-
messages:
|
|
1187
|
+
count: entries.length,
|
|
1188
|
+
messages: entries.map(({ doc: m }) => ({
|
|
650
1189
|
id: m.id,
|
|
651
1190
|
event_type: m.event_type,
|
|
652
1191
|
content: m.content,
|
|
653
|
-
from_worker_id: m.from_worker_id,
|
|
654
|
-
created_at: new Date(m.created_at
|
|
1192
|
+
from_worker_id: fromIndexable(m.from_worker_id),
|
|
1193
|
+
created_at: new Date(m.created_at).toISOString(),
|
|
655
1194
|
})),
|
|
656
1195
|
};
|
|
657
1196
|
} catch (error) {
|
|
@@ -660,53 +1199,6 @@ export const busReadTool: Tool = {
|
|
|
660
1199
|
},
|
|
661
1200
|
};
|
|
662
1201
|
|
|
663
|
-
// ─── project_updates ─────────────────────────────────────────────────────────
|
|
664
|
-
|
|
665
|
-
export const projectUpdatesTool: Tool = {
|
|
666
|
-
name: "project_updates",
|
|
667
|
-
description: "Get recent status updates from workers in the same project. Spanish: actualizaciones proyecto, estado workers, progreso equipo",
|
|
668
|
-
parameters: {
|
|
669
|
-
type: "object",
|
|
670
|
-
properties: {
|
|
671
|
-
project_id: { type: "string", description: "Project ID to get updates from" },
|
|
672
|
-
limit: { type: "number", description: "Maximum updates to return (default: 10)" },
|
|
673
|
-
},
|
|
674
|
-
required: ["project_id"],
|
|
675
|
-
},
|
|
676
|
-
execute: async (params: Record<string, unknown>) => {
|
|
677
|
-
const db = getDb();
|
|
678
|
-
const projectId = params.project_id as string;
|
|
679
|
-
const limit = (params.limit as number) ?? 10;
|
|
680
|
-
|
|
681
|
-
try {
|
|
682
|
-
const tasks = db.query<any, [string, number]>(
|
|
683
|
-
`SELECT t.id, t.name, t.status, t.progress, t.result, t.updated_at, a.name as agent_name
|
|
684
|
-
FROM tasks t
|
|
685
|
-
LEFT JOIN agents a ON t.agent_id = a.id
|
|
686
|
-
WHERE t.project_id = ?
|
|
687
|
-
ORDER BY t.updated_at DESC
|
|
688
|
-
LIMIT ?`
|
|
689
|
-
).all(projectId, limit) as any[];
|
|
690
|
-
|
|
691
|
-
return {
|
|
692
|
-
ok: true,
|
|
693
|
-
project_id: projectId,
|
|
694
|
-
count: tasks.length,
|
|
695
|
-
updates: tasks.map((t) => ({
|
|
696
|
-
task_id: t.id,
|
|
697
|
-
task_name: t.name,
|
|
698
|
-
agent_name: t.agent_name,
|
|
699
|
-
status: t.status,
|
|
700
|
-
progress: t.progress,
|
|
701
|
-
result: t.result,
|
|
702
|
-
updated_at: new Date(t.updated_at * 1000).toISOString(),
|
|
703
|
-
})),
|
|
704
|
-
};
|
|
705
|
-
} catch (error) {
|
|
706
|
-
return { ok: false, error: `Failed to get updates: ${(error as Error).message}` };
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
};
|
|
710
1202
|
|
|
711
1203
|
import crypto from "crypto";
|
|
712
1204
|
import { getAvailableModelsTool } from "./get-available-models.ts";
|
|
@@ -723,10 +1215,10 @@ export function createTools(): Tool[] {
|
|
|
723
1215
|
agentFindTool,
|
|
724
1216
|
agentArchiveTool,
|
|
725
1217
|
taskDelegateTool,
|
|
726
|
-
|
|
1218
|
+
taskReviseTool,
|
|
1219
|
+
taskListTool,
|
|
727
1220
|
taskStatusTool,
|
|
728
1221
|
busPublishTool,
|
|
729
1222
|
busReadTool,
|
|
730
|
-
projectUpdatesTool,
|
|
731
1223
|
];
|
|
732
1224
|
}
|