verybot 0.1.8
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/README.md +167 -0
- package/dist/aliases/store.d.ts +21 -0
- package/dist/aliases/store.js +148 -0
- package/dist/aliases/types.d.ts +6 -0
- package/dist/aliases/types.js +1 -0
- package/dist/brain/agent-registry.d.ts +96 -0
- package/dist/brain/agent-registry.js +141 -0
- package/dist/brain/agent.d.ts +167 -0
- package/dist/brain/agent.js +932 -0
- package/dist/brain/channel-store.d.ts +27 -0
- package/dist/brain/channel-store.js +78 -0
- package/dist/brain/compaction.d.ts +37 -0
- package/dist/brain/compaction.js +214 -0
- package/dist/brain/context.d.ts +43 -0
- package/dist/brain/context.js +139 -0
- package/dist/brain/delegation-store.d.ts +33 -0
- package/dist/brain/delegation-store.js +106 -0
- package/dist/brain/loop.d.ts +24 -0
- package/dist/brain/loop.js +318 -0
- package/dist/brain/mcp-adapter.d.ts +43 -0
- package/dist/brain/mcp-adapter.js +244 -0
- package/dist/brain/memory-extractor.d.ts +26 -0
- package/dist/brain/memory-extractor.js +82 -0
- package/dist/brain/providers.d.ts +14 -0
- package/dist/brain/providers.js +85 -0
- package/dist/brain/queue.d.ts +18 -0
- package/dist/brain/queue.js +111 -0
- package/dist/brain/run-tools.d.ts +50 -0
- package/dist/brain/run-tools.js +136 -0
- package/dist/brain/session-key.d.ts +23 -0
- package/dist/brain/session-key.js +41 -0
- package/dist/brain/session-state.d.ts +36 -0
- package/dist/brain/session-state.js +51 -0
- package/dist/brain/session-store.d.ts +50 -0
- package/dist/brain/session-store.js +207 -0
- package/dist/brain/session.d.ts +32 -0
- package/dist/brain/session.js +75 -0
- package/dist/brain/task-subscriber.d.ts +56 -0
- package/dist/brain/task-subscriber.js +317 -0
- package/dist/brain/user-content.d.ts +16 -0
- package/dist/brain/user-content.js +32 -0
- package/dist/brain/utils.d.ts +4 -0
- package/dist/brain/utils.js +26 -0
- package/dist/brain/worker-coordinator.d.ts +25 -0
- package/dist/brain/worker-coordinator.js +83 -0
- package/dist/channels/commands.d.ts +50 -0
- package/dist/channels/commands.js +132 -0
- package/dist/channels/discord/channel.d.ts +29 -0
- package/dist/channels/discord/channel.js +159 -0
- package/dist/channels/discord/markdown.d.ts +19 -0
- package/dist/channels/discord/markdown.js +62 -0
- package/dist/channels/manager.d.ts +29 -0
- package/dist/channels/manager.js +100 -0
- package/dist/channels/slack/channel.d.ts +37 -0
- package/dist/channels/slack/channel.js +227 -0
- package/dist/channels/slack/markdown.d.ts +19 -0
- package/dist/channels/slack/markdown.js +62 -0
- package/dist/channels/specs.d.ts +32 -0
- package/dist/channels/specs.js +99 -0
- package/dist/channels/telegram/channel.d.ts +29 -0
- package/dist/channels/telegram/channel.js +182 -0
- package/dist/channels/telegram/markdown.d.ts +17 -0
- package/dist/channels/telegram/markdown.js +66 -0
- package/dist/channels/types.d.ts +26 -0
- package/dist/channels/types.js +1 -0
- package/dist/channels/whatsapp/channel.d.ts +34 -0
- package/dist/channels/whatsapp/channel.js +276 -0
- package/dist/channels/whatsapp/markdown.d.ts +20 -0
- package/dist/channels/whatsapp/markdown.js +51 -0
- package/dist/cli/claude-login.d.ts +5 -0
- package/dist/cli/claude-login.js +47 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.js +78 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +96 -0
- package/dist/computer/browser/actions.d.ts +31 -0
- package/dist/computer/browser/actions.js +148 -0
- package/dist/computer/browser/context-manager.d.ts +28 -0
- package/dist/computer/browser/context-manager.js +78 -0
- package/dist/computer/browser/manager.d.ts +91 -0
- package/dist/computer/browser/manager.js +344 -0
- package/dist/computer/browser/profile-badge.d.ts +13 -0
- package/dist/computer/browser/profile-badge.js +67 -0
- package/dist/computer/browser/screenshot.d.ts +5 -0
- package/dist/computer/browser/screenshot.js +21 -0
- package/dist/computer/browser/snapshot.d.ts +30 -0
- package/dist/computer/browser/snapshot.js +242 -0
- package/dist/computer/browser/tools.d.ts +5 -0
- package/dist/computer/browser/tools.js +167 -0
- package/dist/computer/browser/types.d.ts +26 -0
- package/dist/computer/browser/types.js +1 -0
- package/dist/computer/desktop/adapter.d.ts +25 -0
- package/dist/computer/desktop/adapter.js +11 -0
- package/dist/computer/desktop/macos.d.ts +24 -0
- package/dist/computer/desktop/macos.js +223 -0
- package/dist/computer/desktop/tools.d.ts +25 -0
- package/dist/computer/desktop/tools.js +114 -0
- package/dist/config/agent-config.d.ts +55 -0
- package/dist/config/agent-config.js +16 -0
- package/dist/config/model-catalog.d.ts +22 -0
- package/dist/config/model-catalog.js +112 -0
- package/dist/config/model-spec.d.ts +8 -0
- package/dist/config/model-spec.js +66 -0
- package/dist/config/store.d.ts +25 -0
- package/dist/config/store.js +143 -0
- package/dist/config.d.ts +110 -0
- package/dist/config.js +259 -0
- package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
- package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
- package/dist/control-ui/index.html +14 -0
- package/dist/control-ui/vite.svg +1 -0
- package/dist/events.d.ts +2 -0
- package/dist/events.js +11 -0
- package/dist/gateway/broadcast.d.ts +5 -0
- package/dist/gateway/broadcast.js +33 -0
- package/dist/gateway/methods/aliases.d.ts +17 -0
- package/dist/gateway/methods/aliases.js +22 -0
- package/dist/gateway/methods/chat.d.ts +33 -0
- package/dist/gateway/methods/chat.js +37 -0
- package/dist/gateway/methods/config.d.ts +14 -0
- package/dist/gateway/methods/config.js +24 -0
- package/dist/gateway/methods/models.d.ts +10 -0
- package/dist/gateway/methods/models.js +14 -0
- package/dist/gateway/methods/playbooks.d.ts +45 -0
- package/dist/gateway/methods/playbooks.js +488 -0
- package/dist/gateway/methods/prompt-templates.d.ts +27 -0
- package/dist/gateway/methods/prompt-templates.js +106 -0
- package/dist/gateway/methods/scheduler.d.ts +62 -0
- package/dist/gateway/methods/scheduler.js +129 -0
- package/dist/gateway/methods/sessions.d.ts +44 -0
- package/dist/gateway/methods/sessions.js +111 -0
- package/dist/gateway/methods/system.d.ts +12 -0
- package/dist/gateway/methods/system.js +39 -0
- package/dist/gateway/methods/tasks.d.ts +40 -0
- package/dist/gateway/methods/tasks.js +151 -0
- package/dist/gateway/methods/teams.d.ts +69 -0
- package/dist/gateway/methods/teams.js +376 -0
- package/dist/gateway/methods/tools.d.ts +6 -0
- package/dist/gateway/methods/tools.js +7 -0
- package/dist/gateway/methods/whatsapp.d.ts +19 -0
- package/dist/gateway/methods/whatsapp.js +35 -0
- package/dist/gateway/rpc.d.ts +38 -0
- package/dist/gateway/rpc.js +79 -0
- package/dist/gateway/server.d.ts +9 -0
- package/dist/gateway/server.js +137 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +254 -0
- package/dist/integrations/github.d.ts +7 -0
- package/dist/integrations/github.js +133 -0
- package/dist/integrations/mcp.d.ts +7 -0
- package/dist/integrations/mcp.js +106 -0
- package/dist/integrations/registry.d.ts +47 -0
- package/dist/integrations/registry.js +332 -0
- package/dist/integrations/scanner.d.ts +10 -0
- package/dist/integrations/scanner.js +122 -0
- package/dist/integrations/twitter.d.ts +10 -0
- package/dist/integrations/twitter.js +120 -0
- package/dist/integrations/types.d.ts +72 -0
- package/dist/integrations/types.js +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +104 -0
- package/dist/markdown/chunk.d.ts +9 -0
- package/dist/markdown/chunk.js +52 -0
- package/dist/markdown/ir.d.ts +37 -0
- package/dist/markdown/ir.js +529 -0
- package/dist/markdown/render.d.ts +22 -0
- package/dist/markdown/render.js +148 -0
- package/dist/markdown/table-render.d.ts +43 -0
- package/dist/markdown/table-render.js +219 -0
- package/dist/markdown/tables.d.ts +17 -0
- package/dist/markdown/tables.js +27 -0
- package/dist/memory/embedding.d.ts +16 -0
- package/dist/memory/embedding.js +66 -0
- package/dist/memory/explicit.d.ts +16 -0
- package/dist/memory/explicit.js +29 -0
- package/dist/memory/extractor.d.ts +13 -0
- package/dist/memory/extractor.js +82 -0
- package/dist/memory/search.d.ts +15 -0
- package/dist/memory/search.js +57 -0
- package/dist/memory/session-learning.d.ts +23 -0
- package/dist/memory/session-learning.js +55 -0
- package/dist/memory/store.d.ts +36 -0
- package/dist/memory/store.js +334 -0
- package/dist/memory/types.d.ts +9 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.d.ts +28 -0
- package/dist/paths.js +48 -0
- package/dist/prompt-templates/builtins/index.d.ts +4 -0
- package/dist/prompt-templates/builtins/index.js +5 -0
- package/dist/prompt-templates/builtins/planner.d.ts +4 -0
- package/dist/prompt-templates/builtins/planner.js +77 -0
- package/dist/prompt-templates/store.d.ts +45 -0
- package/dist/prompt-templates/store.js +224 -0
- package/dist/prompt-templates/types.d.ts +10 -0
- package/dist/prompt-templates/types.js +1 -0
- package/dist/scheduler/connected-channels.d.ts +24 -0
- package/dist/scheduler/connected-channels.js +57 -0
- package/dist/scheduler/scheduler.d.ts +22 -0
- package/dist/scheduler/scheduler.js +132 -0
- package/dist/scheduler/store.d.ts +27 -0
- package/dist/scheduler/store.js +205 -0
- package/dist/scheduler/types.d.ts +29 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/security/command-validator.d.ts +22 -0
- package/dist/security/command-validator.js +160 -0
- package/dist/security/docker-sandbox.d.ts +48 -0
- package/dist/security/docker-sandbox.js +218 -0
- package/dist/security/env-filter.d.ts +8 -0
- package/dist/security/env-filter.js +41 -0
- package/dist/skills/loader.d.ts +33 -0
- package/dist/skills/loader.js +132 -0
- package/dist/skills/prompt.d.ts +6 -0
- package/dist/skills/prompt.js +17 -0
- package/dist/skills/read-tool.d.ts +7 -0
- package/dist/skills/read-tool.js +24 -0
- package/dist/skills/scanner.d.ts +6 -0
- package/dist/skills/scanner.js +73 -0
- package/dist/skills/types.d.ts +15 -0
- package/dist/skills/types.js +1 -0
- package/dist/tasks/inline-attachment-content.d.ts +9 -0
- package/dist/tasks/inline-attachment-content.js +64 -0
- package/dist/tasks/store.d.ts +112 -0
- package/dist/tasks/store.js +519 -0
- package/dist/tasks/types.d.ts +129 -0
- package/dist/tasks/types.js +80 -0
- package/dist/teams/status-config.d.ts +8 -0
- package/dist/teams/status-config.js +40 -0
- package/dist/teams/store.d.ts +111 -0
- package/dist/teams/store.js +671 -0
- package/dist/teams/types.d.ts +30 -0
- package/dist/teams/types.js +1 -0
- package/dist/tools/bash.d.ts +18 -0
- package/dist/tools/bash.js +64 -0
- package/dist/tools/channel-history.d.ts +10 -0
- package/dist/tools/channel-history.js +43 -0
- package/dist/tools/delegate.d.ts +20 -0
- package/dist/tools/delegate.js +299 -0
- package/dist/tools/fs.d.ts +4 -0
- package/dist/tools/fs.js +335 -0
- package/dist/tools/integration-toggle.d.ts +14 -0
- package/dist/tools/integration-toggle.js +47 -0
- package/dist/tools/memory.d.ts +13 -0
- package/dist/tools/memory.js +59 -0
- package/dist/tools/prompt-templates.d.ts +7 -0
- package/dist/tools/prompt-templates.js +133 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +9 -0
- package/dist/tools/schedule.d.ts +8 -0
- package/dist/tools/schedule.js +219 -0
- package/dist/tools/speak.d.ts +10 -0
- package/dist/tools/speak.js +56 -0
- package/dist/tools/tasks.d.ts +67 -0
- package/dist/tools/tasks.js +288 -0
- package/dist/tools/teams.d.ts +22 -0
- package/dist/tools/teams.js +470 -0
- package/dist/tools/web-fetch.d.ts +3 -0
- package/dist/tools/web-fetch.js +22 -0
- package/dist/tts/edge.d.ts +10 -0
- package/dist/tts/edge.js +60 -0
- package/dist/tts/speak.d.ts +12 -0
- package/dist/tts/speak.js +81 -0
- package/dist/tts/transcribe.d.ts +5 -0
- package/dist/tts/transcribe.js +40 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +22 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +13 -0
- package/package.json +102 -0
- package/verybot.js +2 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { DEFAULT_TASK_STATUSES } from "../tasks/types.js";
|
|
2
|
+
import { Session } from "./session.js";
|
|
3
|
+
import { buildSystemPrompt } from "./context.js";
|
|
4
|
+
import { runLoop } from "./loop.js";
|
|
5
|
+
import { adaptTools } from "./mcp-adapter.js";
|
|
6
|
+
import { getModel } from "./providers.js";
|
|
7
|
+
import { parseModel } from "./agent-registry.js";
|
|
8
|
+
import { createMemorySearchTool } from "../tools/memory.js";
|
|
9
|
+
import { createTaskTools } from "../tools/tasks.js";
|
|
10
|
+
import { resolveInlineAttachmentContent } from "../tasks/inline-attachment-content.js";
|
|
11
|
+
import { BrowserManager } from "../computer/browser/manager.js";
|
|
12
|
+
import { createBrowserTools, BROWSER_TOOL_NAMES } from "../computer/browser/tools.js";
|
|
13
|
+
import { emit } from "../events.js";
|
|
14
|
+
import { logger } from "../logger.js";
|
|
15
|
+
import { buildUserMessageContent } from "./user-content.js";
|
|
16
|
+
/** Poll interval for checking subscribed tasks. */
|
|
17
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
18
|
+
/** Default max steps for subscription workers. */
|
|
19
|
+
const DEFAULT_WORKER_MAX_STEPS = 20;
|
|
20
|
+
/** Claims older than this are considered stale and released (30 minutes). */
|
|
21
|
+
const STALE_CLAIM_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
22
|
+
/** Max candidates to consider per poll tick. */
|
|
23
|
+
const CLAIMABLE_CANDIDATE_LIMIT = 20;
|
|
24
|
+
/** Safety cap: max claims per single poll tick to prevent runaway. */
|
|
25
|
+
const MAX_CLAIMS_PER_TICK = 50;
|
|
26
|
+
/**
|
|
27
|
+
* Pull-based task execution: workers subscribe to task statuses
|
|
28
|
+
* and self-select tasks from the board. Runs a single poll timer
|
|
29
|
+
* and an efficient cross-table query per tick.
|
|
30
|
+
*/
|
|
31
|
+
export class TaskSubscriberManager {
|
|
32
|
+
deps;
|
|
33
|
+
timer = null;
|
|
34
|
+
/** agentId → Set<taskId> of currently running tasks */
|
|
35
|
+
activeRuns = new Map();
|
|
36
|
+
polling = false;
|
|
37
|
+
constructor(deps) {
|
|
38
|
+
this.deps = deps;
|
|
39
|
+
}
|
|
40
|
+
start() {
|
|
41
|
+
if (this.timer)
|
|
42
|
+
return;
|
|
43
|
+
this.poll();
|
|
44
|
+
this.timer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
45
|
+
}
|
|
46
|
+
stop() {
|
|
47
|
+
if (this.timer) {
|
|
48
|
+
clearInterval(this.timer);
|
|
49
|
+
this.timer = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
poll() {
|
|
53
|
+
if (this.polling)
|
|
54
|
+
return;
|
|
55
|
+
this.polling = true;
|
|
56
|
+
try {
|
|
57
|
+
// Release stale claims from crashed workers
|
|
58
|
+
this.cleanupStaleClaims();
|
|
59
|
+
// Claim as many tasks as possible in one tick (capped to prevent runaway)
|
|
60
|
+
for (let i = 0; i < MAX_CLAIMS_PER_TICK; i++) {
|
|
61
|
+
const result = this.findAndClaim();
|
|
62
|
+
if (!result)
|
|
63
|
+
break;
|
|
64
|
+
// Fire-and-forget — runWorker is async, poll returns immediately
|
|
65
|
+
this.runWorker(result).catch((err) => {
|
|
66
|
+
logger.error(`[task-subscriber] runWorker unhandled error: ${err instanceof Error ? err.message : err}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
this.polling = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Release claims that have been held longer than STALE_CLAIM_TIMEOUT_MS. */
|
|
75
|
+
cleanupStaleClaims() {
|
|
76
|
+
const cutoff = Date.now() - STALE_CLAIM_TIMEOUT_MS;
|
|
77
|
+
const released = this.deps.taskStore.cleanupStaleClaims(cutoff);
|
|
78
|
+
if (released > 0) {
|
|
79
|
+
logger.warn(`[task-subscriber] Released ${released} stale claim(s)`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Emit `taskChange.updated` with the latest task snapshot if it still exists. */
|
|
83
|
+
emitTaskUpdated(taskId) {
|
|
84
|
+
const latest = this.deps.taskStore.getById(taskId);
|
|
85
|
+
if (!latest)
|
|
86
|
+
return;
|
|
87
|
+
emit("taskChange", { action: "updated", task: latest });
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find a claimable task and atomically claim it via the store.
|
|
91
|
+
* Uses TeamStore.findClaimableTasks() for cross-table query,
|
|
92
|
+
* then TaskStore.claimTask() for atomic claim.
|
|
93
|
+
*/
|
|
94
|
+
findAndClaim() {
|
|
95
|
+
// Compute busy agents in-memory (not DB-level)
|
|
96
|
+
const fullAgentIds = new Set();
|
|
97
|
+
for (const [agentId, tasks] of this.activeRuns) {
|
|
98
|
+
const agent = this.deps.teamStore.getAgentById(agentId);
|
|
99
|
+
const limit = agent?.concurrency ?? 1;
|
|
100
|
+
if (tasks.size >= limit)
|
|
101
|
+
fullAgentIds.add(agentId);
|
|
102
|
+
}
|
|
103
|
+
// Query candidates via store method (small result set)
|
|
104
|
+
const candidates = this.deps.teamStore.findClaimableTasks(CLAIMABLE_CANDIDATE_LIMIT);
|
|
105
|
+
// Find first candidate whose agent isn't at capacity
|
|
106
|
+
const match = candidates.find((c) => !fullAgentIds.has(c.agentId));
|
|
107
|
+
if (!match)
|
|
108
|
+
return null;
|
|
109
|
+
// Claim atomically via store
|
|
110
|
+
const task = this.deps.taskStore.claimTaskById(match.taskId, match.agentId);
|
|
111
|
+
if (!task)
|
|
112
|
+
return null;
|
|
113
|
+
return {
|
|
114
|
+
agentId: match.agentId,
|
|
115
|
+
teamId: match.teamId,
|
|
116
|
+
agentName: match.agentName,
|
|
117
|
+
model: match.model,
|
|
118
|
+
task,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Run full LLM loop for a claimed task.
|
|
123
|
+
* Follows same pattern as delegate.ts runWorker().
|
|
124
|
+
*/
|
|
125
|
+
async runWorker(claim) {
|
|
126
|
+
const { agentId, teamId, agentName, model: agentModel, task } = claim;
|
|
127
|
+
let teamName = this.deps.teamStore.getTeamById(task.teamId)?.name;
|
|
128
|
+
const sessionKey = `${teamId}:task:${task.id}:${agentName}:${Date.now()}`;
|
|
129
|
+
const session = new Session(sessionKey);
|
|
130
|
+
let activeRunsForAgent = this.activeRuns.get(agentId);
|
|
131
|
+
if (!activeRunsForAgent) {
|
|
132
|
+
activeRunsForAgent = new Set();
|
|
133
|
+
this.activeRuns.set(agentId, activeRunsForAgent);
|
|
134
|
+
}
|
|
135
|
+
activeRunsForAgent.add(task.id);
|
|
136
|
+
try {
|
|
137
|
+
const userMessage = task.description
|
|
138
|
+
? `${task.title}\n\n${task.description}`
|
|
139
|
+
: task.title;
|
|
140
|
+
const { normalizedText, imageDataUrls } = await resolveInlineAttachmentContent(userMessage);
|
|
141
|
+
session.append({ role: "user", content: buildUserMessageContent(normalizedText, imageDataUrls) });
|
|
142
|
+
// 1. Resolve agent config
|
|
143
|
+
const agentRow = this.deps.teamStore.getRuntimeAgentById(agentId);
|
|
144
|
+
if (!agentRow)
|
|
145
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
146
|
+
// 2. Resolve model
|
|
147
|
+
const { provider, modelId, codexReasoningEffort } = parseModel(agentModel);
|
|
148
|
+
const model = getModel(provider, modelId, { codexReasoningEffort });
|
|
149
|
+
// 3. Build worker tools (filtered base tools + memory_search + task tools)
|
|
150
|
+
const baseTools = this.filterTools(agentRow.tools);
|
|
151
|
+
const workerTools = { ...baseTools };
|
|
152
|
+
// Browser: give worker its own instance
|
|
153
|
+
const hasBrowserTools = BROWSER_TOOL_NAMES.some((n) => n in workerTools);
|
|
154
|
+
let workerBrowser = null;
|
|
155
|
+
if (hasBrowserTools && this.deps.browserConfig) {
|
|
156
|
+
for (const n of BROWSER_TOOL_NAMES)
|
|
157
|
+
delete workerTools[n];
|
|
158
|
+
workerBrowser = new BrowserManager({ ...this.deps.browserConfig, profileDir: "temp" });
|
|
159
|
+
Object.assign(workerTools, createBrowserTools(workerBrowser));
|
|
160
|
+
}
|
|
161
|
+
// Memory search (read-only)
|
|
162
|
+
if (this.deps.memoryStore) {
|
|
163
|
+
workerTools.memory_search = createMemorySearchTool(this.deps.memoryStore, this.deps.embeddingProvider, this.deps.memoryMaxResults);
|
|
164
|
+
}
|
|
165
|
+
// Task tools scoped to the task's team, with team's custom statuses
|
|
166
|
+
const team = this.deps.teamStore.getTeamById(task.teamId);
|
|
167
|
+
if (team?.name)
|
|
168
|
+
teamName = team.name;
|
|
169
|
+
const configuredStatuses = team?.statuses && team.statuses.length > 0 ? team.statuses : DEFAULT_TASK_STATUSES;
|
|
170
|
+
const taskTools = createTaskTools(this.deps.taskStore, task.teamId, configuredStatuses, {
|
|
171
|
+
clearClaimOnStatusChange: false,
|
|
172
|
+
requireClaimedByForStatusChange: agentId,
|
|
173
|
+
updatedBy: agentId,
|
|
174
|
+
});
|
|
175
|
+
Object.assign(workerTools, this.filterToolSetByAllowlist(taskTools, agentRow.tools));
|
|
176
|
+
// 4. System prompt (task-specific mode for subscribed workers)
|
|
177
|
+
const system = buildSystemPrompt({
|
|
178
|
+
identity: agentRow.identity,
|
|
179
|
+
modelId,
|
|
180
|
+
teamWorkspace: team?.workspace,
|
|
181
|
+
hasMemory: this.deps.memoryStore !== null,
|
|
182
|
+
subscribedTask: {
|
|
183
|
+
id: task.id,
|
|
184
|
+
teamId: task.teamId,
|
|
185
|
+
title: task.title,
|
|
186
|
+
currentStatus: task.status,
|
|
187
|
+
availableStatusKeys: configuredStatuses.map((status) => status.key),
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
logger.info(`[task-subscriber] ${agentRow.name} claimed task ${task.id}: ${task.title.slice(0, 100)}`);
|
|
191
|
+
try {
|
|
192
|
+
await this.deps.sessionStore.save(session);
|
|
193
|
+
this.deps.sessionStore.updateMetadata(sessionKey, {
|
|
194
|
+
teamId: task.teamId,
|
|
195
|
+
...(teamName ? { teamName } : {}),
|
|
196
|
+
channelType: "worker",
|
|
197
|
+
agentId,
|
|
198
|
+
agentName: agentRow.name,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
logger.warn(`[task-subscriber] ${agentRow.name} failed to create session ${sessionKey}: ${err instanceof Error ? err.message : err}`);
|
|
203
|
+
}
|
|
204
|
+
this.emitTaskUpdated(task.id);
|
|
205
|
+
emit("agent", {
|
|
206
|
+
sessionKey,
|
|
207
|
+
tools: [],
|
|
208
|
+
subscription: { agentId, agentName, taskId: task.id, status: "started" },
|
|
209
|
+
});
|
|
210
|
+
// 6. Adapt tools and run loop
|
|
211
|
+
const maxSteps = agentRow.maxSteps > 0 ? agentRow.maxSteps : DEFAULT_WORKER_MAX_STEPS;
|
|
212
|
+
const workerLabel = `worker:${team?.name ?? task.teamId}:${agentRow.name}:task:${task.id}`;
|
|
213
|
+
let cleanup;
|
|
214
|
+
try {
|
|
215
|
+
const adapted = await adaptTools(provider, modelId, model, workerTools, {
|
|
216
|
+
sandboxEnabled: this.deps.sandboxEnabled,
|
|
217
|
+
cwd: team?.workspace?.trim() || undefined,
|
|
218
|
+
});
|
|
219
|
+
cleanup = adapted.cleanup;
|
|
220
|
+
const { text, responseMessages } = await runLoop({
|
|
221
|
+
model: adapted.model,
|
|
222
|
+
system,
|
|
223
|
+
messages: session.getMessages(),
|
|
224
|
+
tools: adapted.tools,
|
|
225
|
+
sessionKey,
|
|
226
|
+
sessionLabel: workerLabel,
|
|
227
|
+
agentId,
|
|
228
|
+
maxSteps,
|
|
229
|
+
silent: false,
|
|
230
|
+
});
|
|
231
|
+
// Append response messages and save session
|
|
232
|
+
for (const msg of responseMessages)
|
|
233
|
+
session.append(msg);
|
|
234
|
+
await this.deps.sessionStore.save(session);
|
|
235
|
+
this.deps.sessionStore.updateMetadata(sessionKey, {
|
|
236
|
+
teamId: task.teamId,
|
|
237
|
+
...(teamName ? { teamName } : {}),
|
|
238
|
+
channelType: "worker",
|
|
239
|
+
agentId,
|
|
240
|
+
agentName: agentRow.name,
|
|
241
|
+
});
|
|
242
|
+
// NOTE: Workers set final status only when task_update is available to that worker.
|
|
243
|
+
// We no longer hardcode status: "done" here because teams may use custom statuses.
|
|
244
|
+
emit("agent", {
|
|
245
|
+
sessionKey,
|
|
246
|
+
tools: [],
|
|
247
|
+
subscription: { agentId, agentName, taskId: task.id, status: "completed", resultLength: text.length },
|
|
248
|
+
});
|
|
249
|
+
logger.info(`[task-subscriber] ${agentRow.name} completed task ${task.id} (${text.length} chars)`);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
if (cleanup) {
|
|
253
|
+
try {
|
|
254
|
+
await cleanup();
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
logger.warn(`[task-subscriber] ${agentRow.name} MCP cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (workerBrowser?.isLaunched()) {
|
|
261
|
+
workerBrowser.close().catch((err) => {
|
|
262
|
+
logger.warn(`[task-subscriber] ${agentRow.name} browser cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
logger.error(`[task-subscriber] ${agentName} failed on task ${task.id}: ${err instanceof Error ? err.message : err}`);
|
|
269
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
270
|
+
session.append({ role: "assistant", content: `Task execution failed: ${error}` });
|
|
271
|
+
try {
|
|
272
|
+
await this.deps.sessionStore.save(session);
|
|
273
|
+
this.deps.sessionStore.updateMetadata(sessionKey, {
|
|
274
|
+
teamId: task.teamId,
|
|
275
|
+
...(teamName ? { teamName } : {}),
|
|
276
|
+
channelType: "worker",
|
|
277
|
+
agentId,
|
|
278
|
+
agentName,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (saveErr) {
|
|
282
|
+
logger.warn(`[task-subscriber] ${agentName} failed to persist error session ${sessionKey}: ${saveErr instanceof Error ? saveErr.message : saveErr}`);
|
|
283
|
+
}
|
|
284
|
+
emit("agent", {
|
|
285
|
+
sessionKey,
|
|
286
|
+
tools: [],
|
|
287
|
+
subscription: { agentId, agentName, taskId: task.id, status: "failed", error },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
// Finalize only if this worker still owns the claim:
|
|
292
|
+
// mark processed for current task version and release claim atomically.
|
|
293
|
+
const finalized = this.deps.taskStore.finalizeClaimedTaskRun(task.id, agentId);
|
|
294
|
+
if (finalized)
|
|
295
|
+
this.emitTaskUpdated(task.id);
|
|
296
|
+
activeRunsForAgent?.delete(task.id);
|
|
297
|
+
if (activeRunsForAgent?.size === 0)
|
|
298
|
+
this.activeRuns.delete(agentId);
|
|
299
|
+
// Check for more work after a worker finishes (non-blocking)
|
|
300
|
+
queueMicrotask(() => this.poll());
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/** Filter base tools by agent allowlist (empty = inherit all). */
|
|
304
|
+
filterTools(allowlist) {
|
|
305
|
+
return this.filterToolSetByAllowlist(this.deps.baseTools, allowlist);
|
|
306
|
+
}
|
|
307
|
+
filterToolSetByAllowlist(toolSet, allowlist) {
|
|
308
|
+
if (allowlist.length === 0)
|
|
309
|
+
return { ...toolSet };
|
|
310
|
+
const filtered = {};
|
|
311
|
+
for (const name of allowlist) {
|
|
312
|
+
if (name in toolSet)
|
|
313
|
+
filtered[name] = toolSet[name];
|
|
314
|
+
}
|
|
315
|
+
return filtered;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type UserImageContentPart = {
|
|
2
|
+
type: "image";
|
|
3
|
+
image: string;
|
|
4
|
+
mediaType?: string;
|
|
5
|
+
};
|
|
6
|
+
export type UserTextContentPart = {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
export type UserMessageContent = string | Array<UserImageContentPart | UserTextContentPart>;
|
|
11
|
+
export declare function mergeImageDataUrls(first?: string[], second?: string[]): string[] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Build AI SDK user message content with image parts when data URLs are available.
|
|
14
|
+
* Keeps text-only content as a plain string for the common path.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildUserMessageContent(text: string, imageDataUrls?: string[]): UserMessageContent;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function mergeImageDataUrls(first, second) {
|
|
2
|
+
if (!first?.length && !second?.length)
|
|
3
|
+
return undefined;
|
|
4
|
+
const merged = [];
|
|
5
|
+
const seen = new Set();
|
|
6
|
+
for (const url of [...(first ?? []), ...(second ?? [])]) {
|
|
7
|
+
if (seen.has(url))
|
|
8
|
+
continue;
|
|
9
|
+
seen.add(url);
|
|
10
|
+
merged.push(url);
|
|
11
|
+
}
|
|
12
|
+
return merged.length > 0 ? merged : undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build AI SDK user message content with image parts when data URLs are available.
|
|
16
|
+
* Keeps text-only content as a plain string for the common path.
|
|
17
|
+
*/
|
|
18
|
+
export function buildUserMessageContent(text, imageDataUrls) {
|
|
19
|
+
if (!imageDataUrls?.length)
|
|
20
|
+
return text;
|
|
21
|
+
return [
|
|
22
|
+
...imageDataUrls.map((dataUrl) => {
|
|
23
|
+
// Parse data URL (data:image/png;base64,AAAA...) into raw base64 + mediaType
|
|
24
|
+
const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/s);
|
|
25
|
+
if (match) {
|
|
26
|
+
return { type: "image", image: match[2], mediaType: match[1] };
|
|
27
|
+
}
|
|
28
|
+
return { type: "image", image: dataUrl };
|
|
29
|
+
}),
|
|
30
|
+
{ type: "text", text },
|
|
31
|
+
];
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Check if two sets have the same elements. */
|
|
2
|
+
export function setsEqual(a, b) {
|
|
3
|
+
if (a.size !== b.size)
|
|
4
|
+
return false;
|
|
5
|
+
for (const item of a) {
|
|
6
|
+
if (!b.has(item))
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
/** Extract a user-facing message from API/runtime errors. */
|
|
12
|
+
export function friendlyError(err) {
|
|
13
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14
|
+
const lower = msg.toLowerCase();
|
|
15
|
+
if (lower.includes("credit") || lower.includes("billing") || lower.includes("402") || lower.includes("payment"))
|
|
16
|
+
return "API credit balance is too low. Please top up your account and try again.";
|
|
17
|
+
if (lower.includes("rate limit") || lower.includes("429"))
|
|
18
|
+
return "Rate limited by the API. Please wait a moment and try again.";
|
|
19
|
+
if (lower.includes("authentication") || lower.includes("api key") || lower.includes("401"))
|
|
20
|
+
return "API authentication failed. Please check your API key.";
|
|
21
|
+
if (lower.includes("overloaded") || lower.includes("529"))
|
|
22
|
+
return "The AI service is currently overloaded. Please try again shortly.";
|
|
23
|
+
if (lower.includes("context") && lower.includes("overflow"))
|
|
24
|
+
return "The conversation is too long. Please start a new session.";
|
|
25
|
+
return `Something went wrong: ${msg.slice(0, 200)}`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MessageQueue } from "./queue.js";
|
|
2
|
+
import type { SessionStateMap } from "./session-state.js";
|
|
3
|
+
/**
|
|
4
|
+
* Coordinates worker completion and orchestrator re-triggering.
|
|
5
|
+
* Debounces multiple concurrent worker completions into a single LLM call.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WorkerCoordinator {
|
|
8
|
+
private sessions;
|
|
9
|
+
private queue;
|
|
10
|
+
constructor(sessions: SessionStateMap, queue: MessageQueue);
|
|
11
|
+
/**
|
|
12
|
+
* Called when a worker finishes. Debounces multiple completions into a single
|
|
13
|
+
* orchestrator re-trigger so concurrent workers batch into one LLM call.
|
|
14
|
+
*/
|
|
15
|
+
onWorkerComplete(sessionKey: string, channelId: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Track delegation depth: synthetic worker triggers increment, real messages reset.
|
|
18
|
+
* Returns true if the message is a worker result synthetic message.
|
|
19
|
+
*/
|
|
20
|
+
trackDelegationDepth(sessionKey: string, text: string): void;
|
|
21
|
+
/** Cancel all pending debounce timers (e.g. on shutdown). */
|
|
22
|
+
clearAllTimers(): void;
|
|
23
|
+
/** Enqueue a synthetic message to re-trigger the orchestrator after workers finish. */
|
|
24
|
+
private triggerOrchestratorForWorkerResults;
|
|
25
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
/** Debounce delay before re-triggering orchestrator after worker completion. */
|
|
3
|
+
const WORKER_TRIGGER_DEBOUNCE_MS = 2_000;
|
|
4
|
+
/** Max times the orchestrator can be auto-triggered by worker results per user message. */
|
|
5
|
+
const MAX_DELEGATION_DEPTH = 5;
|
|
6
|
+
/** Prefix for synthetic messages injected when workers complete. */
|
|
7
|
+
const WORKER_RESULTS_PREFIX = "[worker_results]";
|
|
8
|
+
/**
|
|
9
|
+
* Coordinates worker completion and orchestrator re-triggering.
|
|
10
|
+
* Debounces multiple concurrent worker completions into a single LLM call.
|
|
11
|
+
*/
|
|
12
|
+
export class WorkerCoordinator {
|
|
13
|
+
sessions;
|
|
14
|
+
queue;
|
|
15
|
+
constructor(sessions, queue) {
|
|
16
|
+
this.sessions = sessions;
|
|
17
|
+
this.queue = queue;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Called when a worker finishes. Debounces multiple completions into a single
|
|
21
|
+
* orchestrator re-trigger so concurrent workers batch into one LLM call.
|
|
22
|
+
*/
|
|
23
|
+
onWorkerComplete(sessionKey, channelId) {
|
|
24
|
+
const state = this.sessions.get(sessionKey);
|
|
25
|
+
if (!state)
|
|
26
|
+
return;
|
|
27
|
+
// Accumulate completed channel IDs during debounce window
|
|
28
|
+
if (!state.workerPendingChannels) {
|
|
29
|
+
state.workerPendingChannels = new Set();
|
|
30
|
+
}
|
|
31
|
+
state.workerPendingChannels.add(channelId);
|
|
32
|
+
// Debounce: reset timer on each completion
|
|
33
|
+
if (state.workerTriggerTimer)
|
|
34
|
+
clearTimeout(state.workerTriggerTimer);
|
|
35
|
+
state.workerTriggerTimer = setTimeout(() => {
|
|
36
|
+
state.workerTriggerTimer = undefined;
|
|
37
|
+
this.triggerOrchestratorForWorkerResults(sessionKey);
|
|
38
|
+
}, WORKER_TRIGGER_DEBOUNCE_MS);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Track delegation depth: synthetic worker triggers increment, real messages reset.
|
|
42
|
+
* Returns true if the message is a worker result synthetic message.
|
|
43
|
+
*/
|
|
44
|
+
trackDelegationDepth(sessionKey, text) {
|
|
45
|
+
const state = this.sessions.get(sessionKey);
|
|
46
|
+
if (!state)
|
|
47
|
+
return;
|
|
48
|
+
if (text.startsWith(WORKER_RESULTS_PREFIX)) {
|
|
49
|
+
state.delegationDepth++;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
state.delegationDepth = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Cancel all pending debounce timers (e.g. on shutdown). */
|
|
56
|
+
clearAllTimers() {
|
|
57
|
+
this.sessions.clearAllTimers();
|
|
58
|
+
}
|
|
59
|
+
/** Enqueue a synthetic message to re-trigger the orchestrator after workers finish. */
|
|
60
|
+
triggerOrchestratorForWorkerResults(sessionKey) {
|
|
61
|
+
const state = this.sessions.get(sessionKey);
|
|
62
|
+
if (!state)
|
|
63
|
+
return;
|
|
64
|
+
if (state.delegationDepth >= MAX_DELEGATION_DEPTH) {
|
|
65
|
+
logger.warn(`[${sessionKey}] Delegation depth limit reached (${MAX_DELEGATION_DEPTH}), skipping re-trigger`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const channelIds = state.workerPendingChannels ? [...state.workerPendingChannels] : [];
|
|
69
|
+
state.workerPendingChannels = undefined;
|
|
70
|
+
const channelList = channelIds.map((id) => `- ${id}`).join("\n");
|
|
71
|
+
const synthetic = `${WORKER_RESULTS_PREFIX}\nWorker agents have completed. Call read_channel for each:\n${channelList}`;
|
|
72
|
+
const deliver = state.replyCallback;
|
|
73
|
+
this.queue.enqueue(sessionKey, synthetic).then((reply) => {
|
|
74
|
+
if (reply && deliver) {
|
|
75
|
+
deliver(reply).catch((err) => {
|
|
76
|
+
logger.error(`[${sessionKey}] Failed to deliver worker trigger reply: ${err instanceof Error ? err.message : err}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}, (err) => {
|
|
80
|
+
logger.error(`[${sessionKey}] Failed to enqueue worker trigger: ${err instanceof Error ? err.message : err}`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A segment of a command response that needs channel-specific formatting. */
|
|
2
|
+
export type CommandPart = string | {
|
|
3
|
+
bold: string;
|
|
4
|
+
} | {
|
|
5
|
+
code: string;
|
|
6
|
+
};
|
|
7
|
+
export interface CommandResult {
|
|
8
|
+
parts: CommandPart[];
|
|
9
|
+
}
|
|
10
|
+
export interface CommandRouterOpts {
|
|
11
|
+
onClear?: (channelType: string, channelId: string, teamId?: string) => Promise<void>;
|
|
12
|
+
onLearn?: (channelType: string, channelId: string, topic?: string, teamId?: string) => Promise<{
|
|
13
|
+
topic?: string;
|
|
14
|
+
extracted: number;
|
|
15
|
+
saved: number;
|
|
16
|
+
skipped: number;
|
|
17
|
+
savedFacts: string[];
|
|
18
|
+
}>;
|
|
19
|
+
onRemember?: (channelType: string, channelId: string, fact: string, teamId?: string) => Promise<{
|
|
20
|
+
saved: boolean;
|
|
21
|
+
fact: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** List available teams for the /team command. */
|
|
24
|
+
listTeams?: () => {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}[];
|
|
28
|
+
/** Fallback team when no active team is set for a channel. */
|
|
29
|
+
defaultTeamId?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class CommandRouter {
|
|
32
|
+
private readonly opts;
|
|
33
|
+
private readonly activeTeams;
|
|
34
|
+
constructor(opts: CommandRouterOpts);
|
|
35
|
+
/** Resolve the active team for a channel, falling back to configured defaultTeamId. */
|
|
36
|
+
resolveTeamId(channelId: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Parse raw text for slash-style commands (e.g. "/clear", "/team foo").
|
|
39
|
+
* Returns `null` if the text is not a recognised command.
|
|
40
|
+
*/
|
|
41
|
+
handle(channelType: string, channelId: string, text: string): Promise<CommandResult | null>;
|
|
42
|
+
/** Handle /clear (or /reset). */
|
|
43
|
+
handleClear(channelType: string, channelId: string): Promise<CommandResult>;
|
|
44
|
+
/** Handle /team [name]. */
|
|
45
|
+
handleTeam(channelType: string, channelId: string, arg: string): Promise<CommandResult>;
|
|
46
|
+
/** Handle /learn [topic]. */
|
|
47
|
+
handleLearn(channelType: string, channelId: string, topic?: string): Promise<CommandResult>;
|
|
48
|
+
/** Handle /remember [fact]. */
|
|
49
|
+
handleRemember(channelType: string, channelId: string, fact: string): Promise<CommandResult>;
|
|
50
|
+
}
|