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,219 @@
1
+ import { randomUUID } from "crypto";
2
+ import { tool } from "ai";
3
+ import { z } from "zod";
4
+ import { Cron } from "croner";
5
+ /**
6
+ * Create schedule management tools scoped to a team.
7
+ * These let the LLM create, list, delete, pause, resume schedules
8
+ * and set the team's timezone.
9
+ */
10
+ export function createScheduleTools(store, teamId, availableIntegrations) {
11
+ const integrationsList = availableIntegrations.length > 0
12
+ ? `Available integrations: ${availableIntegrations.join(", ")}.`
13
+ : "No integrations available.";
14
+ const createSchedule = tool({
15
+ description: `Create a scheduled task. Create IMMEDIATELY when user asks — do NOT ask for confirmation. ` +
16
+ `One-shot: provide runAt (ISO 8601). Recurring: provide cron (e.g. "0 9 * * *"). ` +
17
+ `Convert relative times to absolute timestamps using current time. ` +
18
+ `${integrationsList}`,
19
+ inputSchema: z.object({
20
+ prompt: z.string().describe("The exact message or instruction to execute when this task fires. " +
21
+ "For reminders: write the actual reminder text the user should see (e.g. 'Time to call Sarah!' not 'Reminder from Assistant'). " +
22
+ "For data tasks: write a clear instruction (e.g. 'Check BTC price and report current value')."),
23
+ type: z.enum(["one_shot", "recurring"]).describe("one_shot for reminders, recurring for repeating tasks"),
24
+ cron: z.string().optional().describe("Cron expression for recurring tasks (e.g. '0 9 * * *')"),
25
+ runAt: z.string().optional().describe("ISO 8601 timestamp for one-shot tasks (e.g. '2026-02-08T15:30:00')"),
26
+ timezone: z.string().optional().describe("IANA timezone (e.g. 'America/New_York'). Uses saved team timezone if omitted."),
27
+ integrations: z.string().optional().describe("Comma-separated integration names to enable for this task"),
28
+ conditional: z.boolean().optional().describe("If true, the agent can skip delivery if nothing noteworthy happened"),
29
+ }),
30
+ execute: async (params) => {
31
+ // Resolve timezone: user param > saved setting > UTC default
32
+ const tz = params.timezone || store.getTimezone(teamId) || "UTC";
33
+ // Validate timezone
34
+ try {
35
+ Intl.DateTimeFormat(undefined, { timeZone: tz });
36
+ }
37
+ catch {
38
+ return `Invalid timezone "${tz}". Use IANA format like "America/New_York" or "Asia/Shanghai".`;
39
+ }
40
+ const type = params.type;
41
+ // Validate cron for recurring
42
+ if (type === "recurring") {
43
+ if (!params.cron) {
44
+ return "Recurring schedules require a cron expression.";
45
+ }
46
+ try {
47
+ new Cron(params.cron);
48
+ }
49
+ catch (err) {
50
+ return `Invalid cron expression "${params.cron}": ${err instanceof Error ? err.message : err}`;
51
+ }
52
+ }
53
+ // Validate runAt for one-shot
54
+ if (type === "one_shot") {
55
+ if (!params.runAt) {
56
+ return "One-shot schedules require a runAt timestamp.";
57
+ }
58
+ const runDate = new Date(params.runAt);
59
+ if (isNaN(runDate.getTime())) {
60
+ return `Invalid timestamp "${params.runAt}". Use ISO 8601 format.`;
61
+ }
62
+ if (runDate.getTime() <= Date.now()) {
63
+ return `The time "${params.runAt}" is in the past. Provide a future time.`;
64
+ }
65
+ }
66
+ // Compute nextRun
67
+ let nextRun = null;
68
+ if (type === "one_shot" && params.runAt) {
69
+ nextRun = new Date(params.runAt).toISOString();
70
+ }
71
+ else if (type === "recurring" && params.cron) {
72
+ try {
73
+ const job = new Cron(params.cron, { timezone: tz });
74
+ const next = job.nextRun();
75
+ nextRun = next ? next.toISOString() : null;
76
+ }
77
+ catch {
78
+ return "Failed to compute next run time from cron expression.";
79
+ }
80
+ }
81
+ const now = new Date().toISOString();
82
+ const id = randomUUID();
83
+ store.create({
84
+ id,
85
+ teamId,
86
+ prompt: params.prompt,
87
+ type,
88
+ cron: params.cron ?? null,
89
+ runAt: params.runAt ?? null,
90
+ timezone: tz,
91
+ integrations: params.integrations ?? "",
92
+ conditional: params.conditional ?? false,
93
+ status: "active",
94
+ nextRun,
95
+ lastRun: null,
96
+ failCount: 0,
97
+ createdAt: now,
98
+ updatedAt: now,
99
+ });
100
+ const nextRunFormatted = nextRun
101
+ ? new Date(nextRun).toLocaleString("en-US", { timeZone: tz })
102
+ : "unknown";
103
+ return `Schedule created (${type}). ID: ${id}\nNext run: ${nextRunFormatted} (${tz})`;
104
+ },
105
+ });
106
+ const listSchedules = tool({
107
+ description: "List all scheduled tasks for the current team.",
108
+ inputSchema: z.object({
109
+ status: z.enum(["active", "paused", "completed", "failed"]).optional()
110
+ .describe("Filter by status. Omit to show all."),
111
+ }),
112
+ execute: async ({ status }) => {
113
+ const schedules = store.listByTeam(teamId, status);
114
+ if (schedules.length === 0) {
115
+ return status ? `No ${status} schedules found.` : "No schedules found.";
116
+ }
117
+ const tz = store.getTimezone(teamId) ?? "UTC";
118
+ const lines = schedules.map((s) => {
119
+ const nextStr = s.nextRun
120
+ ? new Date(s.nextRun).toLocaleString("en-US", { timeZone: tz })
121
+ : "—";
122
+ const typeStr = s.type === "recurring" ? `recurring (${s.cron})` : "one-shot";
123
+ const condStr = s.conditional ? " [conditional]" : "";
124
+ return `- **${s.id.slice(0, 8)}** [${s.status}] ${typeStr}${condStr}\n "${s.prompt.slice(0, 80)}"\n Next: ${nextStr}`;
125
+ });
126
+ return lines.join("\n\n");
127
+ },
128
+ });
129
+ const deleteSchedule = tool({
130
+ description: "Delete a scheduled task by its ID.",
131
+ inputSchema: z.object({
132
+ id: z.string().describe("Schedule ID (full or first 8 chars)"),
133
+ }),
134
+ execute: async ({ id }) => {
135
+ const schedule = resolveScheduleId(store, teamId, id);
136
+ if (!schedule)
137
+ return `Schedule "${id}" not found or does not belong to this team.`;
138
+ store.delete(schedule.id);
139
+ return `Schedule "${schedule.id.slice(0, 8)}" deleted.`;
140
+ },
141
+ });
142
+ const pauseSchedule = tool({
143
+ description: "Pause an active scheduled task. It will not fire until resumed.",
144
+ inputSchema: z.object({
145
+ id: z.string().describe("Schedule ID (full or first 8 chars)"),
146
+ }),
147
+ execute: async ({ id }) => {
148
+ const schedule = resolveScheduleId(store, teamId, id);
149
+ if (!schedule)
150
+ return `Schedule "${id}" not found or does not belong to this team.`;
151
+ if (schedule.status !== "active")
152
+ return `Schedule is not active (status: ${schedule.status}).`;
153
+ store.update(schedule.id, { status: "paused" });
154
+ return `Schedule "${schedule.id.slice(0, 8)}" paused.`;
155
+ },
156
+ });
157
+ const resumeSchedule = tool({
158
+ description: "Resume a paused scheduled task.",
159
+ inputSchema: z.object({
160
+ id: z.string().describe("Schedule ID (full or first 8 chars)"),
161
+ }),
162
+ execute: async ({ id }) => {
163
+ const schedule = resolveScheduleId(store, teamId, id);
164
+ if (!schedule)
165
+ return `Schedule "${id}" not found or does not belong to this team.`;
166
+ if (schedule.status !== "paused")
167
+ return `Schedule is not paused (status: ${schedule.status}).`;
168
+ // Recompute next run
169
+ let nextRun = null;
170
+ if (schedule.type === "recurring" && schedule.cron) {
171
+ nextRun = store.computeNextRun(schedule);
172
+ }
173
+ else if (schedule.type === "one_shot" && schedule.runAt) {
174
+ // For one-shots, use original runAt if still in the future
175
+ const runDate = new Date(schedule.runAt);
176
+ nextRun = runDate.getTime() > Date.now() ? runDate.toISOString() : new Date().toISOString();
177
+ }
178
+ store.update(schedule.id, { status: "active", nextRun });
179
+ return `Schedule "${schedule.id.slice(0, 8)}" resumed. Next run: ${nextRun ?? "now"}`;
180
+ },
181
+ });
182
+ const setTimezone = tool({
183
+ description: `Set your timezone for scheduled tasks. Use IANA timezone names like "America/New_York", "Asia/Shanghai", "Europe/London".`,
184
+ inputSchema: z.object({
185
+ timezone: z.string().describe('IANA timezone name (e.g. "America/New_York")'),
186
+ }),
187
+ execute: async ({ timezone }) => {
188
+ try {
189
+ Intl.DateTimeFormat(undefined, { timeZone: timezone });
190
+ }
191
+ catch {
192
+ return `Invalid timezone "${timezone}". Use IANA format like "America/New_York" or "Asia/Shanghai".`;
193
+ }
194
+ store.setTimezone(teamId, timezone);
195
+ return `Timezone set to "${timezone}".`;
196
+ },
197
+ });
198
+ return {
199
+ create_schedule: createSchedule,
200
+ list_schedules: listSchedules,
201
+ delete_schedule: deleteSchedule,
202
+ pause_schedule: pauseSchedule,
203
+ resume_schedule: resumeSchedule,
204
+ set_timezone: setTimezone,
205
+ };
206
+ }
207
+ /** Resolve a schedule ID (full or short) that belongs to the given team. */
208
+ function resolveScheduleId(store, teamId, id) {
209
+ // Try exact match first
210
+ const exact = store.getById(id);
211
+ if (exact && exact.teamId === teamId)
212
+ return exact;
213
+ // Try short ID prefix match
214
+ const all = store.listByTeam(teamId);
215
+ const matches = all.filter((s) => s.id.startsWith(id));
216
+ if (matches.length === 1)
217
+ return matches[0];
218
+ return null;
219
+ }
@@ -0,0 +1,10 @@
1
+ import { type ToolSet } from "ai";
2
+ export interface TTSConfig {
3
+ enabled: boolean;
4
+ voice: string;
5
+ }
6
+ /**
7
+ * Create TTS tools: speak (synthesize + play) and speech_control (pause/resume/stop).
8
+ * Returns null when TTS is disabled.
9
+ */
10
+ export declare function createSpeakTools(config: TTSConfig): ToolSet | null;
@@ -0,0 +1,56 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { synthesize } from "../tts/edge.js";
4
+ import { playAudio, pauseAudio, resumeAudio, stopAudio } from "../tts/speak.js";
5
+ import { logger } from "../logger.js";
6
+ const PREVIEW_LENGTH = 80;
7
+ /**
8
+ * Create TTS tools: speak (synthesize + play) and speech_control (pause/resume/stop).
9
+ * Returns null when TTS is disabled.
10
+ */
11
+ export function createSpeakTools(config) {
12
+ if (!config.enabled)
13
+ return null;
14
+ const speak = tool({
15
+ description: "Speak text aloud through the computer's speakers. " +
16
+ "Keep spoken text concise and conversational. " +
17
+ "Do NOT speak code blocks, URLs, or long technical output — use text for those.",
18
+ inputSchema: z.object({
19
+ text: z.string().describe("The text to speak aloud"),
20
+ voice: z
21
+ .string()
22
+ .optional()
23
+ .describe("Edge TTS voice name (e.g. en-US-GuyNeural). Defaults to config voice."),
24
+ }),
25
+ execute: async ({ text, voice }) => {
26
+ try {
27
+ // voice param overrides; otherwise auto-detect from text content
28
+ const audioPath = await synthesize(text, voice);
29
+ playAudio(audioPath);
30
+ const preview = text.length > PREVIEW_LENGTH
31
+ ? `${text.slice(0, PREVIEW_LENGTH)}...`
32
+ : text;
33
+ return `Spoke: "${preview}"`;
34
+ }
35
+ catch (err) {
36
+ const msg = err instanceof Error ? err.message : String(err);
37
+ logger.error(`TTS failed: ${msg}`);
38
+ return `TTS error: ${msg}`;
39
+ }
40
+ },
41
+ });
42
+ const speechControl = tool({
43
+ description: "Control ongoing speech playback. Use this to pause, resume, or stop the current TTS audio.",
44
+ inputSchema: z.object({
45
+ action: z.enum(["pause", "resume", "stop"]).describe("The playback control action"),
46
+ }),
47
+ execute: async ({ action }) => {
48
+ switch (action) {
49
+ case "pause": return pauseAudio();
50
+ case "resume": return resumeAudio();
51
+ case "stop": return stopAudio();
52
+ }
53
+ },
54
+ });
55
+ return { speak, speech_control: speechControl };
56
+ }
@@ -0,0 +1,67 @@
1
+ import type { TaskStore } from "../tasks/store.js";
2
+ import { type TaskStatusConfig } from "../tasks/types.js";
3
+ export interface CreateTaskToolsOptions {
4
+ /**
5
+ * Whether status changes should clear task claim ownership immediately.
6
+ * Default true (legacy behavior).
7
+ */
8
+ clearClaimOnStatusChange?: boolean;
9
+ /**
10
+ * If set, status updates are only allowed when the task is currently
11
+ * claimed by this agent id. Intended for strict subscription pipelines.
12
+ */
13
+ requireClaimedByForStatusChange?: string;
14
+ /** Actor label used for task_create/task_update writes from this tool set. */
15
+ updatedBy?: string;
16
+ }
17
+ /**
18
+ * Creates AI-facing tools for task management.
19
+ * Non-default teams are scoped to their own tasks only.
20
+ * The default team has access to all tasks across all teams.
21
+ *
22
+ * @param statuses Custom task statuses for the team (undefined = defaults).
23
+ */
24
+ export declare function createTaskTools(taskStore: TaskStore, teamId?: string, statuses?: TaskStatusConfig[], options?: CreateTaskToolsOptions): {
25
+ task_create: import("ai").Tool<{
26
+ title: string;
27
+ description?: string | undefined;
28
+ assignee?: string | undefined;
29
+ priority?: "low" | "medium" | "high" | undefined;
30
+ status?: string | undefined;
31
+ needsHumanReview?: boolean | undefined;
32
+ }, string>;
33
+ task_update: import("ai").Tool<{
34
+ id: string;
35
+ status?: string | undefined;
36
+ title?: string | undefined;
37
+ description?: string | null | undefined;
38
+ assignee?: string | null | undefined;
39
+ priority?: "low" | "medium" | "high" | undefined;
40
+ needsHumanReview?: boolean | undefined;
41
+ }, string>;
42
+ task_list: import("ai").Tool<{
43
+ status?: string | undefined;
44
+ team?: string | undefined;
45
+ needsHumanReview?: boolean | undefined;
46
+ }, string>;
47
+ task_get: import("ai").Tool<{
48
+ id: string;
49
+ }, string>;
50
+ task_delete: import("ai").Tool<{
51
+ id: string;
52
+ }, string>;
53
+ task_comment_list: import("ai").Tool<{
54
+ taskId: string;
55
+ }, string>;
56
+ task_comment_add: import("ai").Tool<{
57
+ taskId: string;
58
+ content: string;
59
+ }, string>;
60
+ task_comment_update: import("ai").Tool<{
61
+ id: string;
62
+ content: string;
63
+ }, string>;
64
+ task_comment_delete: import("ai").Tool<{
65
+ id: string;
66
+ }, string>;
67
+ };
@@ -0,0 +1,288 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { TASK_PRIORITIES, DEFAULT_TASK_STATUSES, MAX_TASK_COMMENT_LENGTH, } from "../tasks/types.js";
4
+ import { DEFAULT_TEAM_ID } from "../config/agent-config.js";
5
+ import { emit } from "../events.js";
6
+ function formatClaimedAt(claimedAt) {
7
+ return claimedAt === null ? "none" : new Date(claimedAt).toISOString();
8
+ }
9
+ function formatUpdatedAt(updatedAt) {
10
+ return new Date(updatedAt).toISOString();
11
+ }
12
+ function formatTaskListLine(task) {
13
+ const assigned = task.assignee ? `, assigned: ${task.assignee}` : "";
14
+ const claimedBy = task.claimedBy ?? "none";
15
+ const claimedAt = formatClaimedAt(task.claimedAt);
16
+ const updatedBy = task.updatedBy ?? "none";
17
+ const updatedAt = formatUpdatedAt(task.updatedAt);
18
+ const needsHumanReview = task.needsHumanReview ? "yes" : "no";
19
+ return `[${task.id}] ${task.title} — ${task.status} (${task.priority}, team: ${task.teamId}${assigned}, needs_human_review: ${needsHumanReview}, claimed_by: ${claimedBy}, claimed_at: ${claimedAt}, updated_by: ${updatedBy}, updated_at: ${updatedAt})`;
20
+ }
21
+ function formatTaskCommentLine(comment) {
22
+ const createdAt = new Date(comment.createdAt).toISOString();
23
+ if (comment.updatedAt > comment.createdAt) {
24
+ const editedAt = new Date(comment.updatedAt).toISOString();
25
+ return `[${comment.id}] ${comment.createdBy} @ ${createdAt} (edited by ${comment.updatedBy} @ ${editedAt}): ${comment.content}`;
26
+ }
27
+ return `[${comment.id}] ${comment.createdBy} @ ${createdAt}: ${comment.content}`;
28
+ }
29
+ function formatTaskAttachmentLine(attachment) {
30
+ const createdAt = new Date(attachment.createdAt).toISOString();
31
+ return `[${attachment.id}] ${attachment.name} (${attachment.type}, ${attachment.size} bytes, created_at: ${createdAt})`;
32
+ }
33
+ function formatTaskDetail(task, comments) {
34
+ const assignee = task.assignee ?? "none";
35
+ const claimedBy = task.claimedBy ?? "none";
36
+ const claimedAt = formatClaimedAt(task.claimedAt);
37
+ const updatedBy = task.updatedBy ?? "none";
38
+ const updatedAt = formatUpdatedAt(task.updatedAt);
39
+ const needsHumanReview = task.needsHumanReview ? "yes" : "no";
40
+ const normalizedDescription = task.description?.trim()
41
+ ? task.description
42
+ : "(none)";
43
+ const attachmentLines = task.attachments.length > 0
44
+ ? task.attachments.map(formatTaskAttachmentLine)
45
+ : ["(none)"];
46
+ const commentLines = comments.length > 0
47
+ ? comments.map(formatTaskCommentLine)
48
+ : ["(none)"];
49
+ return [
50
+ `Task: [${task.id}] ${task.title}`,
51
+ `Team: ${task.teamId}`,
52
+ `Status: ${task.status}`,
53
+ `Priority: ${task.priority}`,
54
+ `Assignee: ${assignee}`,
55
+ `Needs human review: ${needsHumanReview}`,
56
+ `Claimed by: ${claimedBy}`,
57
+ `Claimed at: ${claimedAt}`,
58
+ `Updated by: ${updatedBy}`,
59
+ `Updated at: ${updatedAt}`,
60
+ "Description:",
61
+ normalizedDescription,
62
+ "Attachments:",
63
+ ...attachmentLines,
64
+ "Comments:",
65
+ ...commentLines,
66
+ ].join("\n");
67
+ }
68
+ /**
69
+ * Creates AI-facing tools for task management.
70
+ * Non-default teams are scoped to their own tasks only.
71
+ * The default team has access to all tasks across all teams.
72
+ *
73
+ * @param statuses Custom task statuses for the team (undefined = defaults).
74
+ */
75
+ export function createTaskTools(taskStore, teamId, statuses, options = {}) {
76
+ /** Non-default teams can only access their own tasks. */
77
+ const scoped = !!teamId && teamId !== DEFAULT_TEAM_ID;
78
+ const DEFAULT_CREATE_STATUS_KEY = "todo";
79
+ // Build dynamic status keys from team config or fall back to global defaults.
80
+ // Defensive fallback: some persisted teams may still have empty status lists.
81
+ const configuredStatuses = statuses && statuses.length > 0 ? statuses : DEFAULT_TASK_STATUSES;
82
+ const createStatusKeys = configuredStatuses.map((s) => s.key);
83
+ const listAndUpdateStatusKeys = [...createStatusKeys, "archived"];
84
+ const defaultCreateStatus = createStatusKeys.includes(DEFAULT_CREATE_STATUS_KEY)
85
+ ? DEFAULT_CREATE_STATUS_KEY
86
+ : configuredStatuses[0]?.key
87
+ ?? DEFAULT_TASK_STATUSES[0].key;
88
+ const createStatusDesc = createStatusKeys.join(", ");
89
+ const statusDesc = listAndUpdateStatusKeys.join(", ");
90
+ const createStatusEnum = z.enum(createStatusKeys);
91
+ const statusEnum = z.enum(listAndUpdateStatusKeys);
92
+ const commentContentSchema = z.string().trim().min(1).max(MAX_TASK_COMMENT_LENGTH);
93
+ const actor = options.updatedBy ?? "assistant";
94
+ function getScopedTask(taskId) {
95
+ const task = taskStore.getById(taskId);
96
+ if (!task)
97
+ return null;
98
+ if (scoped && task.teamId !== teamId)
99
+ return null;
100
+ return task;
101
+ }
102
+ const createTask = tool({
103
+ description: "Create a new task for the team. Returns the created task with its ID.",
104
+ inputSchema: z.object({
105
+ title: z.string().describe("Short task title"),
106
+ description: z.string().optional().describe("Optional detailed description"),
107
+ assignee: z.string().optional().describe("Agent ID or 'user' to assign to"),
108
+ priority: z.enum(TASK_PRIORITIES).optional().describe("Task priority: low, medium, or high"),
109
+ status: createStatusEnum.optional().describe(`Initial status: ${createStatusDesc}`),
110
+ needsHumanReview: z.boolean().optional().describe("Whether this task is waiting on a human review"),
111
+ }),
112
+ execute: async ({ title, description, assignee, priority, status, needsHumanReview }) => {
113
+ const task = taskStore.create({
114
+ title,
115
+ description,
116
+ teamId,
117
+ assignee,
118
+ priority,
119
+ status: status ?? defaultCreateStatus,
120
+ needsHumanReview,
121
+ }, { updatedBy: actor });
122
+ emit("taskChange", { action: "created", task });
123
+ return `Task created: [${task.id}] ${task.title} (${task.priority}, ${task.status})`;
124
+ },
125
+ });
126
+ const updateTask = tool({
127
+ description: `Update an existing task. Available statuses: ${statusDesc}.`,
128
+ inputSchema: z.object({
129
+ id: z.string().describe("Task ID to update"),
130
+ status: statusEnum.optional().describe(`New status: ${statusDesc}`),
131
+ title: z.string().optional().describe("New title"),
132
+ description: z.string().nullable().optional().describe("New description (null to clear)"),
133
+ assignee: z.string().nullable().optional().describe("New assignee (agent ID, 'user', or null to unassign)"),
134
+ priority: z.enum(TASK_PRIORITIES).optional().describe("New priority"),
135
+ needsHumanReview: z.boolean().optional().describe("Set whether this task is waiting on a human review"),
136
+ }),
137
+ execute: async ({ id, status, title, description, assignee, priority, needsHumanReview }) => {
138
+ const existing = taskStore.getById(id);
139
+ if (!existing || (scoped && existing.teamId !== teamId))
140
+ return `Task not found: ${id}`;
141
+ const statusChanged = status !== undefined && status !== existing.status;
142
+ const requiredClaimOwner = options.requireClaimedByForStatusChange;
143
+ if (statusChanged && requiredClaimOwner && existing.claimedBy !== requiredClaimOwner) {
144
+ const claimOwner = existing.claimedBy ?? "none";
145
+ return `Task status update blocked: claimed by ${claimOwner}`;
146
+ }
147
+ const task = taskStore.update(id, { status, title, description, assignee, priority, needsHumanReview }, {
148
+ clearClaimOnStatusChange: options.clearClaimOnStatusChange,
149
+ updatedBy: actor,
150
+ });
151
+ if (!task)
152
+ return `Task not found: ${id}`;
153
+ emit("taskChange", { action: "updated", task });
154
+ return `Task updated: [${task.id}] ${task.title} — ${task.status} (${task.priority})`;
155
+ },
156
+ });
157
+ const listTasks = tool({
158
+ description: "List tasks. ALWAYS call fresh — never rely on cached results. " +
159
+ "Only include filters the user explicitly requests. " +
160
+ "Output includes human-review metadata (needs_human_review), claim metadata (claimed_by, claimed_at), and update metadata (updated_by, updated_at). " +
161
+ "Returns all tasks when called with no parameters.",
162
+ inputSchema: z.object({
163
+ status: statusEnum.optional().describe(`Filter by status: ${statusDesc}`),
164
+ team: z.string().optional().describe("Filter by team ID"),
165
+ needsHumanReview: z.boolean().optional().describe("Filter tasks waiting for human review"),
166
+ }),
167
+ execute: async ({ status, team, needsHumanReview }) => {
168
+ const effectiveTeamId = scoped ? teamId : team;
169
+ const tasks = taskStore.list({ teamId: effectiveTeamId, status, needsHumanReview });
170
+ if (tasks.length === 0)
171
+ return "No tasks found.";
172
+ return tasks.map(formatTaskListLine).join("\n");
173
+ },
174
+ });
175
+ const getTask = tool({
176
+ description: "Get full details for a task by ID, including description, attachments, and comments.",
177
+ inputSchema: z.object({
178
+ id: z.string().describe("Task ID to read"),
179
+ }),
180
+ execute: async ({ id }) => {
181
+ const task = getScopedTask(id);
182
+ if (!task)
183
+ return `Task not found: ${id}`;
184
+ const comments = taskStore.listComments(id);
185
+ return formatTaskDetail(task, comments);
186
+ },
187
+ });
188
+ const deleteTask = tool({
189
+ description: "Permanently delete a task by ID.",
190
+ inputSchema: z.object({
191
+ id: z.string().describe("Task ID to delete"),
192
+ }),
193
+ execute: async ({ id }) => {
194
+ if (scoped) {
195
+ const existing = taskStore.getById(id);
196
+ if (!existing || existing.teamId !== teamId)
197
+ return `Task not found: ${id}`;
198
+ }
199
+ const deleted = taskStore.delete(id);
200
+ if (!deleted)
201
+ return `Task not found: ${id}`;
202
+ emit("taskChange", { action: "deleted", id });
203
+ return `Task deleted: ${id}`;
204
+ },
205
+ });
206
+ const listTaskComments = tool({
207
+ description: "List comments for a task by task ID.",
208
+ inputSchema: z.object({
209
+ taskId: z.string().describe("Task ID to read comments from"),
210
+ }),
211
+ execute: async ({ taskId }) => {
212
+ const task = getScopedTask(taskId);
213
+ if (!task)
214
+ return `Task not found: ${taskId}`;
215
+ const comments = taskStore.listComments(taskId);
216
+ if (comments.length === 0)
217
+ return `No comments found for task: ${taskId}`;
218
+ return comments.map(formatTaskCommentLine).join("\n");
219
+ },
220
+ });
221
+ const addTaskComment = tool({
222
+ description: "Add a comment to a task.",
223
+ inputSchema: z.object({
224
+ taskId: z.string().describe("Task ID to comment on"),
225
+ content: commentContentSchema.describe("Comment content"),
226
+ }),
227
+ execute: async ({ taskId, content }) => {
228
+ const task = getScopedTask(taskId);
229
+ if (!task)
230
+ return `Task not found: ${taskId}`;
231
+ const comment = taskStore.addComment(taskId, content, { actor });
232
+ if (!comment)
233
+ return `Task not found: ${taskId}`;
234
+ emit("taskChange", { action: "commentAdded", taskId, comment });
235
+ return `Comment added: [${comment.id}] on task ${taskId}`;
236
+ },
237
+ });
238
+ const updateTaskComment = tool({
239
+ description: "Edit an existing task comment by comment ID.",
240
+ inputSchema: z.object({
241
+ id: z.string().describe("Comment ID to edit"),
242
+ content: commentContentSchema.describe("Updated comment content"),
243
+ }),
244
+ execute: async ({ id, content }) => {
245
+ const existing = taskStore.getCommentById(id);
246
+ if (!existing)
247
+ return `Comment not found: ${id}`;
248
+ const task = getScopedTask(existing.taskId);
249
+ if (!task)
250
+ return `Comment not found: ${id}`;
251
+ const comment = taskStore.updateComment(id, content, { actor });
252
+ if (!comment)
253
+ return `Comment not found: ${id}`;
254
+ emit("taskChange", { action: "commentUpdated", taskId: comment.taskId, comment });
255
+ return `Comment updated: [${comment.id}]`;
256
+ },
257
+ });
258
+ const deleteTaskComment = tool({
259
+ description: "Delete a task comment by comment ID.",
260
+ inputSchema: z.object({
261
+ id: z.string().describe("Comment ID to delete"),
262
+ }),
263
+ execute: async ({ id }) => {
264
+ const existing = taskStore.getCommentById(id);
265
+ if (!existing)
266
+ return `Comment not found: ${id}`;
267
+ const task = getScopedTask(existing.taskId);
268
+ if (!task)
269
+ return `Comment not found: ${id}`;
270
+ const comment = taskStore.deleteComment(id, { actor });
271
+ if (!comment)
272
+ return `Comment not found: ${id}`;
273
+ emit("taskChange", { action: "commentDeleted", taskId: comment.taskId, id: comment.id });
274
+ return `Comment deleted: ${id}`;
275
+ },
276
+ });
277
+ return {
278
+ task_create: createTask,
279
+ task_update: updateTask,
280
+ task_list: listTasks,
281
+ task_get: getTask,
282
+ task_delete: deleteTask,
283
+ task_comment_list: listTaskComments,
284
+ task_comment_add: addTaskComment,
285
+ task_comment_update: updateTaskComment,
286
+ task_comment_delete: deleteTaskComment,
287
+ };
288
+ }
@@ -0,0 +1,22 @@
1
+ import { type ToolSet } from "ai";
2
+ import type { TeamStore } from "../teams/store.js";
3
+ import type { PromptTemplateStore } from "../prompt-templates/store.js";
4
+ type TeamManagementToolName = "team_list" | "team_create" | "team_update" | "team_status_list" | "team_status_get" | "team_status_add" | "team_status_update" | "team_status_delete" | "orchestrator_update" | "worker_create" | "worker_update" | "worker_delete";
5
+ interface TeamManagementToolOptions {
6
+ /** Optional hard scope: all team-aware operations are constrained to this team. */
7
+ scopeTeamId?: string;
8
+ /** Optional subset to expose from the full team management toolset. */
9
+ enabledTools?: TeamManagementToolName[];
10
+ }
11
+ /**
12
+ * Creates AI-facing tools for team and worker management.
13
+ * Available to the default team orchestrator.
14
+ * All mutating tools accept an optional teamId (defaults to the default team).
15
+ */
16
+ export declare function createTeamManagementTools(teamStore: TeamStore, defaultTeamId: string, promptTemplateStore?: PromptTemplateStore, options?: TeamManagementToolOptions): ToolSet;
17
+ /**
18
+ * Worker CRUD-only toolset for a single team orchestrator.
19
+ * Exposes worker_create/update/delete and hard-scopes all operations to `teamId`.
20
+ */
21
+ export declare function createScopedWorkerManagementTools(teamStore: TeamStore, teamId: string, promptTemplateStore?: PromptTemplateStore): ToolSet;
22
+ export {};