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.
Files changed (277) hide show
  1. package/README.md +167 -0
  2. package/dist/aliases/store.d.ts +21 -0
  3. package/dist/aliases/store.js +148 -0
  4. package/dist/aliases/types.d.ts +6 -0
  5. package/dist/aliases/types.js +1 -0
  6. package/dist/brain/agent-registry.d.ts +96 -0
  7. package/dist/brain/agent-registry.js +141 -0
  8. package/dist/brain/agent.d.ts +167 -0
  9. package/dist/brain/agent.js +932 -0
  10. package/dist/brain/channel-store.d.ts +27 -0
  11. package/dist/brain/channel-store.js +78 -0
  12. package/dist/brain/compaction.d.ts +37 -0
  13. package/dist/brain/compaction.js +214 -0
  14. package/dist/brain/context.d.ts +43 -0
  15. package/dist/brain/context.js +139 -0
  16. package/dist/brain/delegation-store.d.ts +33 -0
  17. package/dist/brain/delegation-store.js +106 -0
  18. package/dist/brain/loop.d.ts +24 -0
  19. package/dist/brain/loop.js +318 -0
  20. package/dist/brain/mcp-adapter.d.ts +43 -0
  21. package/dist/brain/mcp-adapter.js +244 -0
  22. package/dist/brain/memory-extractor.d.ts +26 -0
  23. package/dist/brain/memory-extractor.js +82 -0
  24. package/dist/brain/providers.d.ts +14 -0
  25. package/dist/brain/providers.js +85 -0
  26. package/dist/brain/queue.d.ts +18 -0
  27. package/dist/brain/queue.js +111 -0
  28. package/dist/brain/run-tools.d.ts +50 -0
  29. package/dist/brain/run-tools.js +136 -0
  30. package/dist/brain/session-key.d.ts +23 -0
  31. package/dist/brain/session-key.js +41 -0
  32. package/dist/brain/session-state.d.ts +36 -0
  33. package/dist/brain/session-state.js +51 -0
  34. package/dist/brain/session-store.d.ts +50 -0
  35. package/dist/brain/session-store.js +207 -0
  36. package/dist/brain/session.d.ts +32 -0
  37. package/dist/brain/session.js +75 -0
  38. package/dist/brain/task-subscriber.d.ts +56 -0
  39. package/dist/brain/task-subscriber.js +317 -0
  40. package/dist/brain/user-content.d.ts +16 -0
  41. package/dist/brain/user-content.js +32 -0
  42. package/dist/brain/utils.d.ts +4 -0
  43. package/dist/brain/utils.js +26 -0
  44. package/dist/brain/worker-coordinator.d.ts +25 -0
  45. package/dist/brain/worker-coordinator.js +83 -0
  46. package/dist/channels/commands.d.ts +50 -0
  47. package/dist/channels/commands.js +132 -0
  48. package/dist/channels/discord/channel.d.ts +29 -0
  49. package/dist/channels/discord/channel.js +159 -0
  50. package/dist/channels/discord/markdown.d.ts +19 -0
  51. package/dist/channels/discord/markdown.js +62 -0
  52. package/dist/channels/manager.d.ts +29 -0
  53. package/dist/channels/manager.js +100 -0
  54. package/dist/channels/slack/channel.d.ts +37 -0
  55. package/dist/channels/slack/channel.js +227 -0
  56. package/dist/channels/slack/markdown.d.ts +19 -0
  57. package/dist/channels/slack/markdown.js +62 -0
  58. package/dist/channels/specs.d.ts +32 -0
  59. package/dist/channels/specs.js +99 -0
  60. package/dist/channels/telegram/channel.d.ts +29 -0
  61. package/dist/channels/telegram/channel.js +182 -0
  62. package/dist/channels/telegram/markdown.d.ts +17 -0
  63. package/dist/channels/telegram/markdown.js +66 -0
  64. package/dist/channels/types.d.ts +26 -0
  65. package/dist/channels/types.js +1 -0
  66. package/dist/channels/whatsapp/channel.d.ts +34 -0
  67. package/dist/channels/whatsapp/channel.js +276 -0
  68. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  69. package/dist/channels/whatsapp/markdown.js +51 -0
  70. package/dist/cli/claude-login.d.ts +5 -0
  71. package/dist/cli/claude-login.js +47 -0
  72. package/dist/cli/config.d.ts +5 -0
  73. package/dist/cli/config.js +78 -0
  74. package/dist/cli/index.d.ts +11 -0
  75. package/dist/cli/index.js +96 -0
  76. package/dist/computer/browser/actions.d.ts +31 -0
  77. package/dist/computer/browser/actions.js +148 -0
  78. package/dist/computer/browser/context-manager.d.ts +28 -0
  79. package/dist/computer/browser/context-manager.js +78 -0
  80. package/dist/computer/browser/manager.d.ts +91 -0
  81. package/dist/computer/browser/manager.js +344 -0
  82. package/dist/computer/browser/profile-badge.d.ts +13 -0
  83. package/dist/computer/browser/profile-badge.js +67 -0
  84. package/dist/computer/browser/screenshot.d.ts +5 -0
  85. package/dist/computer/browser/screenshot.js +21 -0
  86. package/dist/computer/browser/snapshot.d.ts +30 -0
  87. package/dist/computer/browser/snapshot.js +242 -0
  88. package/dist/computer/browser/tools.d.ts +5 -0
  89. package/dist/computer/browser/tools.js +167 -0
  90. package/dist/computer/browser/types.d.ts +26 -0
  91. package/dist/computer/browser/types.js +1 -0
  92. package/dist/computer/desktop/adapter.d.ts +25 -0
  93. package/dist/computer/desktop/adapter.js +11 -0
  94. package/dist/computer/desktop/macos.d.ts +24 -0
  95. package/dist/computer/desktop/macos.js +223 -0
  96. package/dist/computer/desktop/tools.d.ts +25 -0
  97. package/dist/computer/desktop/tools.js +114 -0
  98. package/dist/config/agent-config.d.ts +55 -0
  99. package/dist/config/agent-config.js +16 -0
  100. package/dist/config/model-catalog.d.ts +22 -0
  101. package/dist/config/model-catalog.js +112 -0
  102. package/dist/config/model-spec.d.ts +8 -0
  103. package/dist/config/model-spec.js +66 -0
  104. package/dist/config/store.d.ts +25 -0
  105. package/dist/config/store.js +143 -0
  106. package/dist/config.d.ts +110 -0
  107. package/dist/config.js +259 -0
  108. package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
  109. package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
  110. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  111. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  112. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  113. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  114. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  115. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  116. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  117. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  118. package/dist/control-ui/index.html +14 -0
  119. package/dist/control-ui/vite.svg +1 -0
  120. package/dist/events.d.ts +2 -0
  121. package/dist/events.js +11 -0
  122. package/dist/gateway/broadcast.d.ts +5 -0
  123. package/dist/gateway/broadcast.js +33 -0
  124. package/dist/gateway/methods/aliases.d.ts +17 -0
  125. package/dist/gateway/methods/aliases.js +22 -0
  126. package/dist/gateway/methods/chat.d.ts +33 -0
  127. package/dist/gateway/methods/chat.js +37 -0
  128. package/dist/gateway/methods/config.d.ts +14 -0
  129. package/dist/gateway/methods/config.js +24 -0
  130. package/dist/gateway/methods/models.d.ts +10 -0
  131. package/dist/gateway/methods/models.js +14 -0
  132. package/dist/gateway/methods/playbooks.d.ts +45 -0
  133. package/dist/gateway/methods/playbooks.js +488 -0
  134. package/dist/gateway/methods/prompt-templates.d.ts +27 -0
  135. package/dist/gateway/methods/prompt-templates.js +106 -0
  136. package/dist/gateway/methods/scheduler.d.ts +62 -0
  137. package/dist/gateway/methods/scheduler.js +129 -0
  138. package/dist/gateway/methods/sessions.d.ts +44 -0
  139. package/dist/gateway/methods/sessions.js +111 -0
  140. package/dist/gateway/methods/system.d.ts +12 -0
  141. package/dist/gateway/methods/system.js +39 -0
  142. package/dist/gateway/methods/tasks.d.ts +40 -0
  143. package/dist/gateway/methods/tasks.js +151 -0
  144. package/dist/gateway/methods/teams.d.ts +69 -0
  145. package/dist/gateway/methods/teams.js +376 -0
  146. package/dist/gateway/methods/tools.d.ts +6 -0
  147. package/dist/gateway/methods/tools.js +7 -0
  148. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  149. package/dist/gateway/methods/whatsapp.js +35 -0
  150. package/dist/gateway/rpc.d.ts +38 -0
  151. package/dist/gateway/rpc.js +79 -0
  152. package/dist/gateway/server.d.ts +9 -0
  153. package/dist/gateway/server.js +137 -0
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +254 -0
  156. package/dist/integrations/github.d.ts +7 -0
  157. package/dist/integrations/github.js +133 -0
  158. package/dist/integrations/mcp.d.ts +7 -0
  159. package/dist/integrations/mcp.js +106 -0
  160. package/dist/integrations/registry.d.ts +47 -0
  161. package/dist/integrations/registry.js +332 -0
  162. package/dist/integrations/scanner.d.ts +10 -0
  163. package/dist/integrations/scanner.js +122 -0
  164. package/dist/integrations/twitter.d.ts +10 -0
  165. package/dist/integrations/twitter.js +120 -0
  166. package/dist/integrations/types.d.ts +72 -0
  167. package/dist/integrations/types.js +1 -0
  168. package/dist/logger.d.ts +16 -0
  169. package/dist/logger.js +104 -0
  170. package/dist/markdown/chunk.d.ts +9 -0
  171. package/dist/markdown/chunk.js +52 -0
  172. package/dist/markdown/ir.d.ts +37 -0
  173. package/dist/markdown/ir.js +529 -0
  174. package/dist/markdown/render.d.ts +22 -0
  175. package/dist/markdown/render.js +148 -0
  176. package/dist/markdown/table-render.d.ts +43 -0
  177. package/dist/markdown/table-render.js +219 -0
  178. package/dist/markdown/tables.d.ts +17 -0
  179. package/dist/markdown/tables.js +27 -0
  180. package/dist/memory/embedding.d.ts +16 -0
  181. package/dist/memory/embedding.js +66 -0
  182. package/dist/memory/explicit.d.ts +16 -0
  183. package/dist/memory/explicit.js +29 -0
  184. package/dist/memory/extractor.d.ts +13 -0
  185. package/dist/memory/extractor.js +82 -0
  186. package/dist/memory/search.d.ts +15 -0
  187. package/dist/memory/search.js +57 -0
  188. package/dist/memory/session-learning.d.ts +23 -0
  189. package/dist/memory/session-learning.js +55 -0
  190. package/dist/memory/store.d.ts +36 -0
  191. package/dist/memory/store.js +334 -0
  192. package/dist/memory/types.d.ts +9 -0
  193. package/dist/memory/types.js +2 -0
  194. package/dist/paths.d.ts +28 -0
  195. package/dist/paths.js +48 -0
  196. package/dist/prompt-templates/builtins/index.d.ts +4 -0
  197. package/dist/prompt-templates/builtins/index.js +5 -0
  198. package/dist/prompt-templates/builtins/planner.d.ts +4 -0
  199. package/dist/prompt-templates/builtins/planner.js +77 -0
  200. package/dist/prompt-templates/store.d.ts +45 -0
  201. package/dist/prompt-templates/store.js +224 -0
  202. package/dist/prompt-templates/types.d.ts +10 -0
  203. package/dist/prompt-templates/types.js +1 -0
  204. package/dist/scheduler/connected-channels.d.ts +24 -0
  205. package/dist/scheduler/connected-channels.js +57 -0
  206. package/dist/scheduler/scheduler.d.ts +22 -0
  207. package/dist/scheduler/scheduler.js +132 -0
  208. package/dist/scheduler/store.d.ts +27 -0
  209. package/dist/scheduler/store.js +205 -0
  210. package/dist/scheduler/types.d.ts +29 -0
  211. package/dist/scheduler/types.js +1 -0
  212. package/dist/security/command-validator.d.ts +22 -0
  213. package/dist/security/command-validator.js +160 -0
  214. package/dist/security/docker-sandbox.d.ts +48 -0
  215. package/dist/security/docker-sandbox.js +218 -0
  216. package/dist/security/env-filter.d.ts +8 -0
  217. package/dist/security/env-filter.js +41 -0
  218. package/dist/skills/loader.d.ts +33 -0
  219. package/dist/skills/loader.js +132 -0
  220. package/dist/skills/prompt.d.ts +6 -0
  221. package/dist/skills/prompt.js +17 -0
  222. package/dist/skills/read-tool.d.ts +7 -0
  223. package/dist/skills/read-tool.js +24 -0
  224. package/dist/skills/scanner.d.ts +6 -0
  225. package/dist/skills/scanner.js +73 -0
  226. package/dist/skills/types.d.ts +15 -0
  227. package/dist/skills/types.js +1 -0
  228. package/dist/tasks/inline-attachment-content.d.ts +9 -0
  229. package/dist/tasks/inline-attachment-content.js +64 -0
  230. package/dist/tasks/store.d.ts +112 -0
  231. package/dist/tasks/store.js +519 -0
  232. package/dist/tasks/types.d.ts +129 -0
  233. package/dist/tasks/types.js +80 -0
  234. package/dist/teams/status-config.d.ts +8 -0
  235. package/dist/teams/status-config.js +40 -0
  236. package/dist/teams/store.d.ts +111 -0
  237. package/dist/teams/store.js +671 -0
  238. package/dist/teams/types.d.ts +30 -0
  239. package/dist/teams/types.js +1 -0
  240. package/dist/tools/bash.d.ts +18 -0
  241. package/dist/tools/bash.js +64 -0
  242. package/dist/tools/channel-history.d.ts +10 -0
  243. package/dist/tools/channel-history.js +43 -0
  244. package/dist/tools/delegate.d.ts +20 -0
  245. package/dist/tools/delegate.js +299 -0
  246. package/dist/tools/fs.d.ts +4 -0
  247. package/dist/tools/fs.js +335 -0
  248. package/dist/tools/integration-toggle.d.ts +14 -0
  249. package/dist/tools/integration-toggle.js +47 -0
  250. package/dist/tools/memory.d.ts +13 -0
  251. package/dist/tools/memory.js +59 -0
  252. package/dist/tools/prompt-templates.d.ts +7 -0
  253. package/dist/tools/prompt-templates.js +133 -0
  254. package/dist/tools/registry.d.ts +6 -0
  255. package/dist/tools/registry.js +9 -0
  256. package/dist/tools/schedule.d.ts +8 -0
  257. package/dist/tools/schedule.js +219 -0
  258. package/dist/tools/speak.d.ts +10 -0
  259. package/dist/tools/speak.js +56 -0
  260. package/dist/tools/tasks.d.ts +67 -0
  261. package/dist/tools/tasks.js +288 -0
  262. package/dist/tools/teams.d.ts +22 -0
  263. package/dist/tools/teams.js +470 -0
  264. package/dist/tools/web-fetch.d.ts +3 -0
  265. package/dist/tools/web-fetch.js +22 -0
  266. package/dist/tts/edge.d.ts +10 -0
  267. package/dist/tts/edge.js +60 -0
  268. package/dist/tts/speak.d.ts +12 -0
  269. package/dist/tts/speak.js +81 -0
  270. package/dist/tts/transcribe.d.ts +5 -0
  271. package/dist/tts/transcribe.js +40 -0
  272. package/dist/utils.d.ts +5 -0
  273. package/dist/utils.js +22 -0
  274. package/dist/version.d.ts +1 -0
  275. package/dist/version.js +13 -0
  276. package/package.json +102 -0
  277. package/verybot.js +2 -0
@@ -0,0 +1,470 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { DEFAULT_TASK_STATUSES } from "../tasks/types.js";
4
+ import { REQUIRED_DONE_STATUS_KEY, validateStatusConfigs } from "../teams/status-config.js";
5
+ import { emit } from "../events.js";
6
+ const TEAM_COLOR_SCHEMA = z.string().regex(/^#[0-9a-fA-F]{6}$/, "Team color must be a valid hex color (e.g. #ef4444)");
7
+ const STATUS_KEY_PATTERN = /^\w+$/;
8
+ const STATUS_KEY_SCHEMA = z.string().min(1).max(128).regex(STATUS_KEY_PATTERN, "Status key must be alphanumeric/underscore");
9
+ const STATUS_LABEL_SCHEMA = z.string().trim().min(1);
10
+ const STATUS_COLOR_SCHEMA = z.string().regex(/^#[0-9a-fA-F]{6}$/, "Status color must be a valid hex color (e.g. #64748b)");
11
+ const SCOPED_TEAM_MISMATCH_MESSAGE = "This tool is scoped to team";
12
+ const TEAM_WORKER_CRUD_TOOL_NAMES = [
13
+ "worker_create",
14
+ "worker_update",
15
+ "worker_delete",
16
+ ];
17
+ /**
18
+ * Creates AI-facing tools for team and worker management.
19
+ * Available to the default team orchestrator.
20
+ * All mutating tools accept an optional teamId (defaults to the default team).
21
+ */
22
+ export function createTeamManagementTools(teamStore, defaultTeamId, promptTemplateStore, options) {
23
+ const scopedTeamId = options?.scopeTeamId;
24
+ const enabledTools = options?.enabledTools ? new Set(options.enabledTools) : null;
25
+ /** Validates a template ID exists. Returns an error string or null if valid. */
26
+ function validateTemplate(templateId) {
27
+ if (templateId === undefined || templateId === "")
28
+ return null;
29
+ const tpl = promptTemplateStore?.getPromptTemplateById(templateId);
30
+ if (!tpl)
31
+ return `Prompt template not found: ${templateId}`;
32
+ return null;
33
+ }
34
+ function resolveTargetTeamId(teamId) {
35
+ if (scopedTeamId && teamId && teamId !== scopedTeamId) {
36
+ return { targetTeamId: scopedTeamId, scopeError: `${SCOPED_TEAM_MISMATCH_MESSAGE}: ${scopedTeamId}` };
37
+ }
38
+ return { targetTeamId: scopedTeamId ?? teamId ?? defaultTeamId };
39
+ }
40
+ function resolveTeam(teamId) {
41
+ const { targetTeamId, scopeError } = resolveTargetTeamId(teamId);
42
+ if (scopeError)
43
+ return { targetTeamId, team: null, scopeError };
44
+ const team = teamStore.getTeamById(targetTeamId);
45
+ return { targetTeamId, team };
46
+ }
47
+ function isAgentInScope(agentTeamId) {
48
+ return !scopedTeamId || agentTeamId === scopedTeamId;
49
+ }
50
+ function getEffectiveStatuses(statuses) {
51
+ const source = statuses && statuses.length > 0 ? statuses : DEFAULT_TASK_STATUSES;
52
+ return source.map((status) => ({ ...status }));
53
+ }
54
+ function formatStatusLine(status) {
55
+ return `- ${status.key} — ${status.label} (${status.color})`;
56
+ }
57
+ const teamList = tool({
58
+ description: "List all teams and their agents (orchestrators + workers).",
59
+ inputSchema: z.object({}),
60
+ execute: async () => {
61
+ try {
62
+ const teams = teamStore.listTeams().filter((team) => team.id !== defaultTeamId);
63
+ if (teams.length === 0)
64
+ return "No teams found.";
65
+ const lines = teams.map((team) => {
66
+ const agents = teamStore.listAgentsByTeam(team.id);
67
+ const agentLines = agents
68
+ .map((a) => {
69
+ const parts = [`${a.role}`, `model: ${a.model}`, `id: ${a.id}`];
70
+ if (a.templateId)
71
+ parts.push(`template: ${a.templateId}`);
72
+ return ` - ${a.name} (${parts.join(", ")})`;
73
+ })
74
+ .join("\n");
75
+ const colorStr = team.color ? `, color: ${team.color}` : "";
76
+ return `**${team.name}** (id: ${team.id}${colorStr})\n${agentLines || " (no agents)"}`;
77
+ });
78
+ return lines.join("\n\n");
79
+ }
80
+ catch (err) {
81
+ return `Failed to list teams: ${err instanceof Error ? err.message : err}`;
82
+ }
83
+ },
84
+ });
85
+ const teamCreate = tool({
86
+ description: "Create a new team with a unique name. Optionally provide teamId and color.",
87
+ inputSchema: z.object({
88
+ name: z.string().trim().min(1).describe("Team name"),
89
+ teamId: z.string().optional().describe("Optional explicit team ID"),
90
+ color: TEAM_COLOR_SCHEMA.optional().describe("Optional team color hex (e.g. #ef4444)"),
91
+ }),
92
+ execute: async ({ name, teamId, color }) => {
93
+ try {
94
+ const team = teamStore.createTeam({ id: teamId, name, color });
95
+ emit("teamChange", { action: "created", team });
96
+ return `Team created: "${team.name}" (id: ${team.id})`;
97
+ }
98
+ catch (err) {
99
+ return `Failed to create team: ${err instanceof Error ? err.message : err}`;
100
+ }
101
+ },
102
+ });
103
+ const teamUpdate = tool({
104
+ description: "Update a team's name or color. Defaults to the default team if no teamId is provided.",
105
+ inputSchema: z.object({
106
+ teamId: z.string().optional().describe("Team ID to update (defaults to the default team)"),
107
+ name: z.string().optional().describe("New team name"),
108
+ color: TEAM_COLOR_SCHEMA.optional().describe("New hex color (e.g. #ef4444)"),
109
+ }),
110
+ execute: async ({ teamId, name, color }) => {
111
+ try {
112
+ const { targetTeamId, scopeError } = resolveTargetTeamId(teamId);
113
+ if (scopeError)
114
+ return scopeError;
115
+ const team = teamStore.updateTeam(targetTeamId, { name, color });
116
+ if (!team)
117
+ return `Team not found: ${targetTeamId}`;
118
+ emit("teamChange", { action: "updated", team });
119
+ return `Team updated: "${team.name}" (id: ${team.id})`;
120
+ }
121
+ catch (err) {
122
+ return `Failed to update team: ${err instanceof Error ? err.message : err}`;
123
+ }
124
+ },
125
+ });
126
+ const teamStatusList = tool({
127
+ description: "List task statuses for a team. Returns the effective status set (custom statuses if configured, otherwise defaults).",
128
+ inputSchema: z.object({
129
+ teamId: z.string().optional().describe("Team ID (defaults to the default team)"),
130
+ }),
131
+ execute: async ({ teamId }) => {
132
+ try {
133
+ const { targetTeamId, team, scopeError } = resolveTeam(teamId);
134
+ if (scopeError)
135
+ return scopeError;
136
+ if (!team)
137
+ return `Team not found: ${targetTeamId}`;
138
+ const hasCustomStatuses = !!team.statuses && team.statuses.length > 0;
139
+ const statuses = getEffectiveStatuses(team.statuses);
140
+ const source = hasCustomStatuses ? "custom" : "default";
141
+ return [
142
+ `Task statuses for "${team.name}" (id: ${team.id}, source: ${source}):`,
143
+ ...statuses.map(formatStatusLine),
144
+ ].join("\n");
145
+ }
146
+ catch (err) {
147
+ return `Failed to list task statuses: ${err instanceof Error ? err.message : err}`;
148
+ }
149
+ },
150
+ });
151
+ const teamStatusGet = tool({
152
+ description: "Get a single task status by key for a team (reads from custom statuses when configured, otherwise default statuses).",
153
+ inputSchema: z.object({
154
+ teamId: z.string().optional().describe("Team ID (defaults to the default team)"),
155
+ key: STATUS_KEY_SCHEMA.describe("Status key to fetch"),
156
+ }),
157
+ execute: async ({ teamId, key }) => {
158
+ try {
159
+ const { targetTeamId, team, scopeError } = resolveTeam(teamId);
160
+ if (scopeError)
161
+ return scopeError;
162
+ if (!team)
163
+ return `Team not found: ${targetTeamId}`;
164
+ const statuses = getEffectiveStatuses(team.statuses);
165
+ const status = statuses.find((candidate) => candidate.key === key);
166
+ if (!status)
167
+ return `Status not found: ${key}`;
168
+ return [
169
+ `Task status for "${team.name}" (id: ${team.id}):`,
170
+ formatStatusLine(status),
171
+ ].join("\n");
172
+ }
173
+ catch (err) {
174
+ return `Failed to get task status: ${err instanceof Error ? err.message : err}`;
175
+ }
176
+ },
177
+ });
178
+ const teamStatusAdd = tool({
179
+ description: "Add a task status to a team. If the team has no custom statuses yet, defaults are copied first and the new status is appended.",
180
+ inputSchema: z.object({
181
+ teamId: z.string().optional().describe("Team ID (defaults to the default team)"),
182
+ key: STATUS_KEY_SCHEMA.describe("New status key"),
183
+ label: STATUS_LABEL_SCHEMA.describe("Display label"),
184
+ color: STATUS_COLOR_SCHEMA.describe("Hex color"),
185
+ }),
186
+ execute: async ({ teamId, key, label, color }) => {
187
+ try {
188
+ const { targetTeamId, team, scopeError } = resolveTeam(teamId);
189
+ if (scopeError)
190
+ return scopeError;
191
+ if (!team)
192
+ return `Team not found: ${targetTeamId}`;
193
+ const statuses = getEffectiveStatuses(team.statuses);
194
+ if (statuses.some((status) => status.key === key)) {
195
+ return `Status already exists: ${key}`;
196
+ }
197
+ const nextStatuses = [...statuses, { key, label, color }];
198
+ validateStatusConfigs(nextStatuses);
199
+ const updatedTeam = teamStore.updateTeam(targetTeamId, { statuses: nextStatuses });
200
+ if (!updatedTeam)
201
+ return `Team not found: ${targetTeamId}`;
202
+ emit("teamChange", { action: "updated", team: updatedTeam });
203
+ return `Task status added for team "${updatedTeam.name}": ${key}`;
204
+ }
205
+ catch (err) {
206
+ return `Failed to add task status: ${err instanceof Error ? err.message : err}`;
207
+ }
208
+ },
209
+ });
210
+ const teamStatusUpdate = tool({
211
+ description: "Update a team task status by key. The status key is immutable; only label/color can be changed.",
212
+ inputSchema: z.object({
213
+ teamId: z.string().optional().describe("Team ID (defaults to the default team)"),
214
+ key: STATUS_KEY_SCHEMA.describe("Status key to update"),
215
+ label: STATUS_LABEL_SCHEMA.optional().describe("Updated display label"),
216
+ color: STATUS_COLOR_SCHEMA.optional().describe("Updated hex color"),
217
+ }).refine((input) => input.label !== undefined || input.color !== undefined, { message: "At least one of label or color must be provided" }),
218
+ execute: async ({ teamId, key, label, color }) => {
219
+ try {
220
+ const { targetTeamId, team, scopeError } = resolveTeam(teamId);
221
+ if (scopeError)
222
+ return scopeError;
223
+ if (!team)
224
+ return `Team not found: ${targetTeamId}`;
225
+ const statuses = getEffectiveStatuses(team.statuses);
226
+ const statusIndex = statuses.findIndex((status) => status.key === key);
227
+ if (statusIndex < 0)
228
+ return `Status not found: ${key}`;
229
+ const status = statuses[statusIndex];
230
+ const nextStatuses = statuses.map((candidate, index) => index === statusIndex
231
+ ? { ...candidate, label: label ?? status.label, color: color ?? status.color }
232
+ : candidate);
233
+ validateStatusConfigs(nextStatuses);
234
+ const updatedTeam = teamStore.updateTeam(targetTeamId, { statuses: nextStatuses });
235
+ if (!updatedTeam)
236
+ return `Team not found: ${targetTeamId}`;
237
+ emit("teamChange", { action: "updated", team: updatedTeam });
238
+ return `Task status updated for team "${updatedTeam.name}": ${key}`;
239
+ }
240
+ catch (err) {
241
+ return `Failed to update task status: ${err instanceof Error ? err.message : err}`;
242
+ }
243
+ },
244
+ });
245
+ const teamStatusDelete = tool({
246
+ description: `Delete a task status from a team by key. The "${REQUIRED_DONE_STATUS_KEY}" status cannot be deleted.`,
247
+ inputSchema: z.object({
248
+ teamId: z.string().optional().describe("Team ID (defaults to the default team)"),
249
+ key: STATUS_KEY_SCHEMA.describe("Status key to delete"),
250
+ }),
251
+ execute: async ({ teamId, key }) => {
252
+ try {
253
+ if (key === REQUIRED_DONE_STATUS_KEY) {
254
+ return `Cannot delete required status: ${REQUIRED_DONE_STATUS_KEY}`;
255
+ }
256
+ const { targetTeamId, team, scopeError } = resolveTeam(teamId);
257
+ if (scopeError)
258
+ return scopeError;
259
+ if (!team)
260
+ return `Team not found: ${targetTeamId}`;
261
+ const statuses = getEffectiveStatuses(team.statuses);
262
+ if (!statuses.some((status) => status.key === key)) {
263
+ return `Status not found: ${key}`;
264
+ }
265
+ const nextStatuses = statuses.filter((status) => status.key !== key);
266
+ validateStatusConfigs(nextStatuses);
267
+ const updatedTeam = teamStore.updateTeam(targetTeamId, { statuses: nextStatuses });
268
+ if (!updatedTeam)
269
+ return `Team not found: ${targetTeamId}`;
270
+ emit("teamChange", { action: "updated", team: updatedTeam });
271
+ return `Task status deleted for team "${updatedTeam.name}": ${key}`;
272
+ }
273
+ catch (err) {
274
+ return `Failed to delete task status: ${err instanceof Error ? err.message : err}`;
275
+ }
276
+ },
277
+ });
278
+ const orchestratorUpdate = tool({
279
+ description: "Update a team's orchestrator configuration (model, identity, prompt template, tools, etc.). If no orchestrator exists yet, one will be created. Defaults to the default team if no teamId is provided.",
280
+ inputSchema: z.object({
281
+ teamId: z.string().optional().describe("Team ID to update (defaults to the default team)"),
282
+ name: z.string().optional().describe("Orchestrator name"),
283
+ model: z.string().optional().describe("Model string (required when creating a new orchestrator)"),
284
+ identity: z.string().optional().describe("System prompt / identity"),
285
+ templateId: z.string().optional().describe("Prompt template ID to use instead of inline identity (pass empty string to unlink)"),
286
+ tools: z.array(z.string()).optional().describe("List of tool names"),
287
+ maxSteps: z.number().optional().describe("Max inference steps"),
288
+ contextWindow: z.number().optional().describe("Context window size"),
289
+ }),
290
+ execute: async ({ teamId, name, model, identity, templateId, tools, maxSteps, contextWindow }) => {
291
+ try {
292
+ const { targetTeamId, scopeError } = resolveTargetTeamId(teamId);
293
+ if (scopeError)
294
+ return scopeError;
295
+ const team = teamStore.getTeamById(targetTeamId);
296
+ if (!team)
297
+ return `Team not found: ${targetTeamId}`;
298
+ const templateErr = validateTemplate(templateId);
299
+ if (templateErr)
300
+ return templateErr;
301
+ const resolvedTemplateId = templateId === "" ? null : templateId;
302
+ const agents = teamStore.listAgentsByTeam(targetTeamId);
303
+ const orchestrator = agents.find((a) => a.role === "orchestrator");
304
+ if (orchestrator) {
305
+ const agent = teamStore.updateAgent(orchestrator.id, {
306
+ name, model, identity, templateId: resolvedTemplateId,
307
+ tools, maxSteps, contextWindow,
308
+ });
309
+ if (!agent)
310
+ return `Failed to update orchestrator`;
311
+ emit("teamChange", { action: "agentUpdated", agent });
312
+ return `Orchestrator updated: "${agent.name}" (id: ${agent.id})`;
313
+ }
314
+ // No orchestrator exists — create one (model is required)
315
+ if (!model)
316
+ return "model is required when creating a new orchestrator";
317
+ const agent = teamStore.createAgent(targetTeamId, {
318
+ name: name ?? team.name,
319
+ role: "orchestrator",
320
+ model,
321
+ identity,
322
+ templateId: resolvedTemplateId,
323
+ tools,
324
+ maxSteps,
325
+ contextWindow,
326
+ });
327
+ emit("teamChange", { action: "agentCreated", agent });
328
+ return `Orchestrator created: "${agent.name}" (id: ${agent.id})`;
329
+ }
330
+ catch (err) {
331
+ return `Failed to update orchestrator: ${err instanceof Error ? err.message : err}`;
332
+ }
333
+ },
334
+ });
335
+ const workerCreate = tool({
336
+ description: "Add a worker agent to a team. Workers can be delegated tasks by the orchestrator. Defaults to the default team if no teamId is provided.",
337
+ inputSchema: z.object({
338
+ teamId: z.string().optional().describe("Team ID to add the worker to (defaults to the default team)"),
339
+ name: z.string().describe("Worker name (unique within the team)"),
340
+ model: z.string().describe("Model string (e.g. 'anthropic:claude-sonnet-4-20250514')"),
341
+ identity: z.string().optional().describe("System prompt / identity for the worker"),
342
+ templateId: z.string().optional().describe("Prompt template ID to use instead of inline identity"),
343
+ tools: z.array(z.string()).optional().describe("List of tool names to enable"),
344
+ maxSteps: z.number().optional().describe("Max inference steps per run"),
345
+ timeout: z.number().optional().describe("Timeout in seconds for delegated tasks"),
346
+ }),
347
+ execute: async ({ teamId, name, model, identity, templateId, tools, maxSteps, timeout }) => {
348
+ try {
349
+ const { targetTeamId, scopeError } = resolveTargetTeamId(teamId);
350
+ if (scopeError)
351
+ return scopeError;
352
+ const templateErr = validateTemplate(templateId);
353
+ if (templateErr)
354
+ return templateErr;
355
+ const resolvedTemplateId = templateId === "" ? null : templateId;
356
+ const agent = teamStore.createAgent(targetTeamId, {
357
+ name,
358
+ role: "worker",
359
+ model,
360
+ identity,
361
+ templateId: resolvedTemplateId,
362
+ tools,
363
+ maxSteps,
364
+ timeout,
365
+ });
366
+ emit("teamChange", { action: "agentCreated", agent });
367
+ return `Worker created: "${agent.name}" (id: ${agent.id})`;
368
+ }
369
+ catch (err) {
370
+ return `Failed to create worker: ${err instanceof Error ? err.message : err}`;
371
+ }
372
+ },
373
+ });
374
+ const workerUpdate = tool({
375
+ description: "Update a worker's configuration (name, model, identity, tools, etc.).",
376
+ inputSchema: z.object({
377
+ id: z.string().describe("Agent ID to update"),
378
+ name: z.string().optional().describe("New worker name"),
379
+ model: z.string().optional().describe("New model string"),
380
+ identity: z.string().optional().describe("New system prompt / identity"),
381
+ templateId: z.string().optional().describe("Prompt template ID to use instead of inline identity (pass empty string to unlink)"),
382
+ tools: z.array(z.string()).optional().describe("New list of tool names"),
383
+ maxSteps: z.number().optional().describe("New max inference steps"),
384
+ timeout: z.number().optional().describe("New timeout in seconds"),
385
+ }),
386
+ execute: async ({ id, name, model, identity, templateId, tools, maxSteps, timeout }) => {
387
+ try {
388
+ const existing = teamStore.getAgentById(id);
389
+ if (!existing)
390
+ return `Agent not found: ${id}`;
391
+ if (!isAgentInScope(existing.teamId))
392
+ return `Agent not found: ${id}`;
393
+ if (existing.role === "orchestrator") {
394
+ return "Cannot modify an orchestrator via worker_update. Use orchestrator_update instead.";
395
+ }
396
+ const templateErr = validateTemplate(templateId);
397
+ if (templateErr)
398
+ return templateErr;
399
+ const resolvedTemplateId = templateId === "" ? null : templateId;
400
+ const agent = teamStore.updateAgent(id, { name, model, identity, templateId: resolvedTemplateId, tools, maxSteps, timeout });
401
+ if (!agent)
402
+ return `Agent not found: ${id}`;
403
+ emit("teamChange", { action: "agentUpdated", agent });
404
+ return `Worker updated: "${agent.name}" (id: ${agent.id})`;
405
+ }
406
+ catch (err) {
407
+ return `Failed to update worker: ${err instanceof Error ? err.message : err}`;
408
+ }
409
+ },
410
+ });
411
+ const workerDelete = tool({
412
+ description: "Remove a worker from the team. This cannot be undone.",
413
+ inputSchema: z.object({
414
+ id: z.string().describe("Agent ID to delete"),
415
+ }),
416
+ execute: async ({ id }) => {
417
+ try {
418
+ const existing = teamStore.getAgentById(id);
419
+ if (!existing)
420
+ return `Agent not found: ${id}`;
421
+ if (!isAgentInScope(existing.teamId))
422
+ return `Agent not found: ${id}`;
423
+ if (existing.role === "orchestrator") {
424
+ return "Cannot delete an orchestrator via worker_delete. Orchestrators are tied to their team's lifecycle.";
425
+ }
426
+ const deleted = teamStore.deleteAgent(id);
427
+ if (!deleted)
428
+ return `Agent not found: ${id}`;
429
+ emit("teamChange", { action: "agentDeleted", id });
430
+ return `Worker deleted: ${id}`;
431
+ }
432
+ catch (err) {
433
+ return `Failed to delete worker: ${err instanceof Error ? err.message : err}`;
434
+ }
435
+ },
436
+ });
437
+ const allTools = {
438
+ team_list: teamList,
439
+ team_create: teamCreate,
440
+ team_update: teamUpdate,
441
+ team_status_list: teamStatusList,
442
+ team_status_get: teamStatusGet,
443
+ team_status_add: teamStatusAdd,
444
+ team_status_update: teamStatusUpdate,
445
+ team_status_delete: teamStatusDelete,
446
+ orchestrator_update: orchestratorUpdate,
447
+ worker_create: workerCreate,
448
+ worker_update: workerUpdate,
449
+ worker_delete: workerDelete,
450
+ };
451
+ if (!enabledTools)
452
+ return allTools;
453
+ const filteredTools = {};
454
+ for (const toolName of enabledTools) {
455
+ const selected = allTools[toolName];
456
+ if (selected)
457
+ filteredTools[toolName] = selected;
458
+ }
459
+ return filteredTools;
460
+ }
461
+ /**
462
+ * Worker CRUD-only toolset for a single team orchestrator.
463
+ * Exposes worker_create/update/delete and hard-scopes all operations to `teamId`.
464
+ */
465
+ export function createScopedWorkerManagementTools(teamStore, teamId, promptTemplateStore) {
466
+ return createTeamManagementTools(teamStore, teamId, promptTemplateStore, {
467
+ scopeTeamId: teamId,
468
+ enabledTools: TEAM_WORKER_CRUD_TOOL_NAMES,
469
+ });
470
+ }
@@ -0,0 +1,3 @@
1
+ export declare const webFetchTool: import("ai").Tool<{
2
+ url: string;
3
+ }, string>;
@@ -0,0 +1,22 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ // TODO: phase 5 — improve with HTML→markdown conversion
4
+ export const webFetchTool = tool({
5
+ description: "Fetch a URL and return its text content",
6
+ inputSchema: z.object({
7
+ url: z.string().url().describe("The URL to fetch"),
8
+ }),
9
+ execute: async ({ url }) => {
10
+ try {
11
+ const res = await fetch(url, {
12
+ headers: { "User-Agent": "mini-agent/0.1" },
13
+ signal: AbortSignal.timeout(15_000),
14
+ });
15
+ const text = await res.text();
16
+ return text.slice(0, 20_000); // cap size
17
+ }
18
+ catch (err) {
19
+ return `Error fetching ${url}: ${err.message}`;
20
+ }
21
+ },
22
+ });
@@ -0,0 +1,10 @@
1
+ /** Detect dominant language from text content. */
2
+ export declare function detectLanguage(text: string): string;
3
+ /** Pick the right voice for the text language. */
4
+ export declare function resolveVoice(text: string, explicitVoice?: string): string;
5
+ /**
6
+ * Synthesize text to an mp3 file using Microsoft Edge TTS.
7
+ * Free, no API key required — uses the same neural voices as Edge browser.
8
+ * Returns the path to the generated mp3 file (caller must clean up).
9
+ */
10
+ export declare function synthesize(text: string, voice?: string): Promise<string>;
@@ -0,0 +1,60 @@
1
+ import { EdgeTTS } from "node-edge-tts";
2
+ import { join } from "path";
3
+ import { tmpdir } from "os";
4
+ import { randomBytes } from "crypto";
5
+ import { logger } from "../logger.js";
6
+ const OUTPUT_FORMAT = "audio-24khz-96kbitrate-mono-mp3";
7
+ const MAX_TEXT_LENGTH = 2_000;
8
+ const SYNTHESIS_TIMEOUT = 300_000;
9
+ /** Default voices per detected language. */
10
+ const VOICE_MAP = {
11
+ zh: "zh-CN-XiaoxiaoNeural",
12
+ ja: "ja-JP-NanamiNeural",
13
+ ko: "ko-KR-SunHiNeural",
14
+ en: "en-US-AriaNeural",
15
+ };
16
+ const FALLBACK_VOICE = "en-US-AriaNeural";
17
+ // CJK Unicode ranges for language detection
18
+ const CJK_RE = /[\u4e00-\u9fff\u3400-\u4dbf]/;
19
+ const JAPANESE_RE = /[\u3040-\u309f\u30a0-\u30ff]/;
20
+ const KOREAN_RE = /[\uac00-\ud7af\u1100-\u11ff]/;
21
+ /** Detect dominant language from text content. */
22
+ export function detectLanguage(text) {
23
+ // Sample first 200 chars for speed
24
+ const sample = text.slice(0, 200);
25
+ if (JAPANESE_RE.test(sample))
26
+ return "ja";
27
+ if (KOREAN_RE.test(sample))
28
+ return "ko";
29
+ if (CJK_RE.test(sample))
30
+ return "zh";
31
+ return "en";
32
+ }
33
+ /** Pick the right voice for the text language. */
34
+ export function resolveVoice(text, explicitVoice) {
35
+ if (explicitVoice)
36
+ return explicitVoice;
37
+ const lang = detectLanguage(text);
38
+ return VOICE_MAP[lang] ?? FALLBACK_VOICE;
39
+ }
40
+ /**
41
+ * Synthesize text to an mp3 file using Microsoft Edge TTS.
42
+ * Free, no API key required — uses the same neural voices as Edge browser.
43
+ * Returns the path to the generated mp3 file (caller must clean up).
44
+ */
45
+ export async function synthesize(text, voice) {
46
+ const trimmed = text.length > MAX_TEXT_LENGTH
47
+ ? text.slice(0, MAX_TEXT_LENGTH)
48
+ : text;
49
+ const resolvedVoice = resolveVoice(trimmed, voice);
50
+ const outPath = join(tmpdir(), `edge-tts-${randomBytes(6).toString("hex")}.mp3`);
51
+ const tts = new EdgeTTS({
52
+ voice: resolvedVoice,
53
+ lang: resolvedVoice.split("-").slice(0, 2).join("-"),
54
+ outputFormat: OUTPUT_FORMAT,
55
+ timeout: SYNTHESIS_TIMEOUT,
56
+ });
57
+ await tts.ttsPromise(trimmed, outPath);
58
+ logger.info(`TTS synthesized ${trimmed.length} chars with voice=${resolvedVoice}`);
59
+ return outPath;
60
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Start playing an mp3 file in the background (non-blocking).
3
+ * Returns immediately — use stopAudio/pauseAudio to control playback.
4
+ * Cleans up the temp file when playback ends.
5
+ */
6
+ export declare function playAudio(filePath: string): void;
7
+ /** Pause current playback (SIGSTOP). */
8
+ export declare function pauseAudio(): string;
9
+ /** Resume paused playback (SIGCONT). */
10
+ export declare function resumeAudio(): string;
11
+ /** Stop current playback immediately. */
12
+ export declare function stopAudio(): string;
@@ -0,0 +1,81 @@
1
+ import { spawn } from "child_process";
2
+ import { unlink } from "fs/promises";
3
+ import { logger } from "../logger.js";
4
+ const PLAYBACK_TIMEOUT = 300_000;
5
+ let activeChild = null;
6
+ let activeFile = null;
7
+ let paused = false;
8
+ /**
9
+ * Start playing an mp3 file in the background (non-blocking).
10
+ * Returns immediately — use stopAudio/pauseAudio to control playback.
11
+ * Cleans up the temp file when playback ends.
12
+ */
13
+ export function playAudio(filePath) {
14
+ // Stop any existing playback first
15
+ stopAudio();
16
+ activeFile = filePath;
17
+ paused = false;
18
+ const cmd = process.platform === "darwin" ? "afplay" : "ffplay";
19
+ const args = process.platform === "darwin"
20
+ ? [filePath]
21
+ : ["-nodisp", "-autoexit", filePath];
22
+ const child = spawn(cmd, args, { stdio: "ignore" });
23
+ activeChild = child;
24
+ const timer = setTimeout(() => {
25
+ child.kill();
26
+ logger.warn("TTS playback timed out");
27
+ }, PLAYBACK_TIMEOUT);
28
+ const cleanup = () => {
29
+ clearTimeout(timer);
30
+ activeChild = null;
31
+ activeFile = null;
32
+ paused = false;
33
+ unlink(filePath).catch(() => { });
34
+ };
35
+ child.on("close", (code) => {
36
+ cleanup();
37
+ if (code === 0 || code === null) {
38
+ logger.info("TTS playback finished");
39
+ }
40
+ else {
41
+ logger.warn(`TTS player exited with code ${code}`);
42
+ }
43
+ });
44
+ child.on("error", (err) => {
45
+ cleanup();
46
+ logger.error(`TTS player error: ${err.message}`);
47
+ });
48
+ }
49
+ /** Pause current playback (SIGSTOP). */
50
+ export function pauseAudio() {
51
+ if (!activeChild)
52
+ return "Nothing is playing.";
53
+ if (paused)
54
+ return "Already paused.";
55
+ activeChild.kill("SIGSTOP");
56
+ paused = true;
57
+ logger.info("TTS playback paused");
58
+ return "Playback paused.";
59
+ }
60
+ /** Resume paused playback (SIGCONT). */
61
+ export function resumeAudio() {
62
+ if (!activeChild)
63
+ return "Nothing is playing.";
64
+ if (!paused)
65
+ return "Already playing.";
66
+ activeChild.kill("SIGCONT");
67
+ paused = false;
68
+ logger.info("TTS playback resumed");
69
+ return "Playback resumed.";
70
+ }
71
+ /** Stop current playback immediately. */
72
+ export function stopAudio() {
73
+ if (!activeChild)
74
+ return "Nothing is playing.";
75
+ // Resume first if paused, then kill — avoids zombie stopped process
76
+ if (paused)
77
+ activeChild.kill("SIGCONT");
78
+ activeChild.kill();
79
+ logger.info("TTS playback stopped");
80
+ return "Playback stopped.";
81
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Transcribe an audio buffer to text using OpenAI's gpt-4o-mini-transcribe.
3
+ * Accepts ogg/opus (Telegram voice), mp3, wav, m4a, webm, etc.
4
+ */
5
+ export declare function transcribe(audioBuffer: Buffer, filename?: string): Promise<string>;