@swarmclawai/swarmclaw 0.7.1 → 0.7.3

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 (237) hide show
  1. package/README.md +155 -150
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/route.ts +26 -0
  4. package/src/app/api/agents/[id]/thread/route.ts +37 -9
  5. package/src/app/api/agents/route.ts +13 -2
  6. package/src/app/api/auth/route.ts +76 -7
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
  8. package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
  9. package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
  10. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  11. package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
  12. package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
  13. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  14. package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
  15. package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
  16. package/src/app/api/{sessions → chats}/route.ts +21 -7
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  18. package/src/app/api/connectors/doctor/route.ts +13 -0
  19. package/src/app/api/files/open/route.ts +16 -14
  20. package/src/app/api/memory/maintenance/route.ts +11 -1
  21. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  22. package/src/app/api/openclaw/skills/route.ts +11 -3
  23. package/src/app/api/plugins/dependencies/route.ts +24 -0
  24. package/src/app/api/plugins/install/route.ts +15 -92
  25. package/src/app/api/plugins/route.ts +6 -26
  26. package/src/app/api/plugins/settings/route.ts +40 -0
  27. package/src/app/api/plugins/ui/route.ts +1 -0
  28. package/src/app/api/settings/route.ts +49 -7
  29. package/src/app/api/tasks/[id]/route.ts +15 -6
  30. package/src/app/api/tasks/bulk/route.ts +2 -2
  31. package/src/app/api/tasks/route.ts +9 -4
  32. package/src/app/api/usage/route.ts +30 -0
  33. package/src/app/api/webhooks/[id]/route.ts +8 -1
  34. package/src/app/page.tsx +9 -2
  35. package/src/cli/index.js +39 -33
  36. package/src/cli/index.ts +43 -49
  37. package/src/cli/spec.js +29 -27
  38. package/src/components/agents/agent-card.tsx +16 -13
  39. package/src/components/agents/agent-chat-list.tsx +104 -4
  40. package/src/components/agents/agent-list.tsx +54 -22
  41. package/src/components/agents/agent-sheet.tsx +209 -18
  42. package/src/components/agents/cron-job-form.tsx +3 -3
  43. package/src/components/agents/inspector-panel.tsx +110 -50
  44. package/src/components/auth/access-key-gate.tsx +36 -97
  45. package/src/components/auth/setup-wizard.tsx +5 -38
  46. package/src/components/chat/chat-area.tsx +39 -27
  47. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
  48. package/src/components/chat/chat-header.tsx +299 -314
  49. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
  50. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  51. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  52. package/src/components/chat/message-bubble.tsx +4 -1
  53. package/src/components/chat/message-list.tsx +5 -3
  54. package/src/components/chat/session-debug-panel.tsx +1 -1
  55. package/src/components/chat/tool-request-banner.tsx +3 -3
  56. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  57. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  58. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  59. package/src/components/connectors/connector-list.tsx +265 -127
  60. package/src/components/connectors/connector-sheet.tsx +218 -1
  61. package/src/components/home/home-view.tsx +129 -5
  62. package/src/components/layout/app-layout.tsx +392 -182
  63. package/src/components/layout/mobile-header.tsx +26 -8
  64. package/src/components/plugins/plugin-list.tsx +487 -254
  65. package/src/components/plugins/plugin-sheet.tsx +236 -13
  66. package/src/components/projects/project-detail.tsx +183 -0
  67. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  68. package/src/components/shared/agent-picker-list.tsx +2 -2
  69. package/src/components/shared/command-palette.tsx +111 -25
  70. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  71. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +78 -1
  73. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  74. package/src/components/shared/settings/section-providers.tsx +1 -1
  75. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  76. package/src/components/shared/settings/section-secrets.tsx +6 -6
  77. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  78. package/src/components/shared/settings/section-voice.tsx +5 -1
  79. package/src/components/shared/settings/section-web-search.tsx +10 -2
  80. package/src/components/shared/settings/settings-page.tsx +244 -56
  81. package/src/components/tasks/approvals-panel.tsx +205 -18
  82. package/src/components/tasks/task-board.tsx +242 -46
  83. package/src/components/usage/metrics-dashboard.tsx +147 -1
  84. package/src/components/wallets/wallet-panel.tsx +17 -5
  85. package/src/components/webhooks/webhook-sheet.tsx +8 -8
  86. package/src/lib/auth.ts +17 -0
  87. package/src/lib/chat-streaming-state.test.ts +108 -0
  88. package/src/lib/chat-streaming-state.ts +108 -0
  89. package/src/lib/chat.ts +1 -1
  90. package/src/lib/{sessions.ts → chats.ts} +28 -18
  91. package/src/lib/openclaw-agent-id.test.ts +14 -0
  92. package/src/lib/openclaw-agent-id.ts +31 -0
  93. package/src/lib/providers/claude-cli.ts +1 -1
  94. package/src/lib/server/agent-assignment.test.ts +112 -0
  95. package/src/lib/server/agent-assignment.ts +169 -0
  96. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  97. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  98. package/src/lib/server/approvals.ts +483 -75
  99. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  100. package/src/lib/server/browser-state.test.ts +118 -0
  101. package/src/lib/server/browser-state.ts +123 -0
  102. package/src/lib/server/build-llm.test.ts +36 -0
  103. package/src/lib/server/build-llm.ts +11 -4
  104. package/src/lib/server/builtin-plugins.ts +34 -0
  105. package/src/lib/server/capability-router.ts +10 -8
  106. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  107. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  108. package/src/lib/server/chat-execution.ts +285 -165
  109. package/src/lib/server/chatroom-health.test.ts +26 -0
  110. package/src/lib/server/chatroom-health.ts +2 -3
  111. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  112. package/src/lib/server/chatroom-helpers.ts +48 -8
  113. package/src/lib/server/connectors/discord.ts +175 -11
  114. package/src/lib/server/connectors/doctor.test.ts +80 -0
  115. package/src/lib/server/connectors/doctor.ts +116 -0
  116. package/src/lib/server/connectors/manager.ts +948 -112
  117. package/src/lib/server/connectors/policy.test.ts +222 -0
  118. package/src/lib/server/connectors/policy.ts +452 -0
  119. package/src/lib/server/connectors/slack.ts +188 -9
  120. package/src/lib/server/connectors/telegram.ts +65 -15
  121. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  122. package/src/lib/server/connectors/thread-context.ts +72 -0
  123. package/src/lib/server/connectors/types.ts +41 -11
  124. package/src/lib/server/cost.ts +34 -1
  125. package/src/lib/server/daemon-state.ts +61 -3
  126. package/src/lib/server/data-dir.ts +13 -0
  127. package/src/lib/server/delegation-jobs.test.ts +140 -0
  128. package/src/lib/server/delegation-jobs.ts +248 -0
  129. package/src/lib/server/document-utils.test.ts +47 -0
  130. package/src/lib/server/document-utils.ts +397 -0
  131. package/src/lib/server/heartbeat-service.ts +14 -40
  132. package/src/lib/server/heartbeat-source.test.ts +22 -0
  133. package/src/lib/server/heartbeat-source.ts +7 -0
  134. package/src/lib/server/identity-continuity.test.ts +77 -0
  135. package/src/lib/server/identity-continuity.ts +127 -0
  136. package/src/lib/server/mailbox-utils.ts +347 -0
  137. package/src/lib/server/main-agent-loop.ts +28 -1103
  138. package/src/lib/server/memory-db.ts +4 -6
  139. package/src/lib/server/memory-tiers.ts +40 -0
  140. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  141. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  142. package/src/lib/server/openclaw-exec-config.ts +5 -6
  143. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  144. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  145. package/src/lib/server/openclaw-sync.ts +3 -2
  146. package/src/lib/server/orchestrator-lg.ts +20 -9
  147. package/src/lib/server/orchestrator.ts +7 -7
  148. package/src/lib/server/playwright-proxy.mjs +27 -3
  149. package/src/lib/server/plugins.test.ts +207 -0
  150. package/src/lib/server/plugins.ts +927 -66
  151. package/src/lib/server/provider-health.ts +38 -6
  152. package/src/lib/server/queue.ts +13 -28
  153. package/src/lib/server/scheduler.ts +2 -0
  154. package/src/lib/server/session-archive-memory.test.ts +85 -0
  155. package/src/lib/server/session-archive-memory.ts +230 -0
  156. package/src/lib/server/session-mailbox.ts +8 -18
  157. package/src/lib/server/session-reset-policy.test.ts +99 -0
  158. package/src/lib/server/session-reset-policy.ts +311 -0
  159. package/src/lib/server/session-run-manager.ts +33 -82
  160. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  161. package/src/lib/server/session-tools/calendar.ts +366 -0
  162. package/src/lib/server/session-tools/canvas.ts +1 -1
  163. package/src/lib/server/session-tools/chatroom.ts +4 -2
  164. package/src/lib/server/session-tools/connector.ts +114 -10
  165. package/src/lib/server/session-tools/context.ts +21 -5
  166. package/src/lib/server/session-tools/crawl.ts +447 -0
  167. package/src/lib/server/session-tools/crud.ts +74 -28
  168. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  169. package/src/lib/server/session-tools/delegate.ts +497 -24
  170. package/src/lib/server/session-tools/discovery.ts +24 -6
  171. package/src/lib/server/session-tools/document.ts +283 -0
  172. package/src/lib/server/session-tools/edit_file.ts +4 -2
  173. package/src/lib/server/session-tools/email.ts +320 -0
  174. package/src/lib/server/session-tools/extract.ts +137 -0
  175. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  176. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  177. package/src/lib/server/session-tools/file.ts +241 -25
  178. package/src/lib/server/session-tools/git.ts +1 -1
  179. package/src/lib/server/session-tools/http.ts +1 -1
  180. package/src/lib/server/session-tools/human-loop.ts +227 -0
  181. package/src/lib/server/session-tools/image-gen.ts +380 -0
  182. package/src/lib/server/session-tools/index.ts +130 -50
  183. package/src/lib/server/session-tools/mailbox.ts +276 -0
  184. package/src/lib/server/session-tools/memory.ts +172 -3
  185. package/src/lib/server/session-tools/monitor.ts +151 -8
  186. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  187. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  188. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  189. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  190. package/src/lib/server/session-tools/platform.ts +148 -7
  191. package/src/lib/server/session-tools/plugin-creator.ts +89 -26
  192. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  193. package/src/lib/server/session-tools/replicate.ts +301 -0
  194. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  195. package/src/lib/server/session-tools/sandbox.ts +4 -2
  196. package/src/lib/server/session-tools/schedule.ts +24 -12
  197. package/src/lib/server/session-tools/session-info.ts +43 -7
  198. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  199. package/src/lib/server/session-tools/shell.ts +5 -2
  200. package/src/lib/server/session-tools/subagent.ts +194 -28
  201. package/src/lib/server/session-tools/table.ts +587 -0
  202. package/src/lib/server/session-tools/wallet.ts +42 -12
  203. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  204. package/src/lib/server/session-tools/web.ts +926 -91
  205. package/src/lib/server/storage.ts +255 -16
  206. package/src/lib/server/stream-agent-chat.ts +116 -268
  207. package/src/lib/server/structured-extract.test.ts +72 -0
  208. package/src/lib/server/structured-extract.ts +373 -0
  209. package/src/lib/server/task-mention.test.ts +16 -2
  210. package/src/lib/server/task-mention.ts +61 -10
  211. package/src/lib/server/tool-aliases.ts +66 -18
  212. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  213. package/src/lib/server/tool-capability-policy.ts +38 -27
  214. package/src/lib/server/tool-retry.ts +2 -0
  215. package/src/lib/server/watch-jobs.test.ts +173 -0
  216. package/src/lib/server/watch-jobs.ts +532 -0
  217. package/src/lib/server/ws-hub.ts +5 -3
  218. package/src/lib/tool-definitions.ts +4 -0
  219. package/src/lib/validation/schemas.test.ts +26 -0
  220. package/src/lib/validation/schemas.ts +10 -1
  221. package/src/lib/ws-client.ts +14 -12
  222. package/src/proxy.ts +5 -5
  223. package/src/stores/use-app-store.ts +5 -11
  224. package/src/stores/use-chat-store.ts +38 -9
  225. package/src/types/index.ts +352 -47
  226. package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
  227. package/src/components/sessions/new-session-sheet.tsx +0 -253
  228. package/src/lib/server/main-session.ts +0 -24
  229. package/src/lib/server/session-run-manager.test.ts +0 -23
  230. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  231. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  232. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  233. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  234. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  235. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  236. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  237. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -0,0 +1,301 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import type { Plugin, PluginHooks } from '@/types'
4
+ import { getPluginManager } from '../plugins'
5
+ import { normalizeToolInputArgs } from './normalize-tool-args'
6
+ import type { ToolBuildContext } from './context'
7
+
8
+ interface ReplicateConfig {
9
+ apiToken: string
10
+ defaultModel: string
11
+ pollingIntervalMs: number
12
+ timeoutMs: number
13
+ }
14
+
15
+ function getConfig(): ReplicateConfig {
16
+ const ps = getPluginManager().getPluginSettings('replicate')
17
+ return {
18
+ apiToken: (ps.apiToken as string) || '',
19
+ defaultModel: (ps.defaultModel as string) || '',
20
+ pollingIntervalMs: Number(ps.pollingIntervalMs) || 2000,
21
+ timeoutMs: Number(ps.timeoutMs) || 120000,
22
+ }
23
+ }
24
+
25
+ const API_BASE = 'https://api.replicate.com/v1'
26
+
27
+ async function replicateRequest(
28
+ method: string,
29
+ path: string,
30
+ token: string,
31
+ body?: unknown,
32
+ extraHeaders?: Record<string, string>,
33
+ ): Promise<{ ok: boolean; data?: unknown; error?: string }> {
34
+ const headers: Record<string, string> = {
35
+ Authorization: `Bearer ${token}`,
36
+ 'Content-Type': 'application/json',
37
+ ...extraHeaders,
38
+ }
39
+ const init: RequestInit = {
40
+ method,
41
+ headers,
42
+ signal: AbortSignal.timeout(15_000),
43
+ }
44
+ if (body && method !== 'GET' && method !== 'DELETE') {
45
+ init.body = JSON.stringify(body)
46
+ }
47
+ const res = await fetch(`${API_BASE}${path}`, init)
48
+ if (!res.ok) {
49
+ const errText = await res.text().catch(() => '')
50
+ return { ok: false, error: `Replicate ${res.status}: ${errText.slice(0, 400)}` }
51
+ }
52
+ const data = await res.json()
53
+ return { ok: true, data }
54
+ }
55
+
56
+ function formatPrediction(p: Record<string, unknown>): Record<string, unknown> {
57
+ return {
58
+ id: p.id,
59
+ model: p.model,
60
+ status: p.status,
61
+ output: p.output,
62
+ error: p.error,
63
+ logs: typeof p.logs === 'string' ? p.logs.slice(-500) : undefined,
64
+ metrics: p.metrics,
65
+ created_at: p.created_at,
66
+ started_at: p.started_at,
67
+ completed_at: p.completed_at,
68
+ }
69
+ }
70
+
71
+ async function pollPrediction(token: string, predictionId: string, cfg: ReplicateConfig): Promise<Record<string, unknown>> {
72
+ const deadline = Date.now() + cfg.timeoutMs
73
+ while (Date.now() < deadline) {
74
+ await new Promise((r) => setTimeout(r, cfg.pollingIntervalMs))
75
+ const r = await replicateRequest('GET', `/predictions/${predictionId}`, token)
76
+ if (!r.ok) return { status: 'failed', error: r.error }
77
+ const prediction = r.data as Record<string, unknown>
78
+ if (prediction.status === 'succeeded' || prediction.status === 'failed' || prediction.status === 'canceled') {
79
+ return prediction
80
+ }
81
+ }
82
+ return { status: 'failed', error: 'Prediction timed out.' }
83
+ }
84
+
85
+ async function executeReplicate(args: Record<string, unknown>): Promise<string> {
86
+ const normalized = normalizeToolInputArgs(args)
87
+ const action = String(normalized.action || 'run')
88
+ const cfg = getConfig()
89
+
90
+ if (!cfg.apiToken) {
91
+ return 'Error: Replicate API token not configured. Ask the user to add it in Plugin Settings > Replicate.'
92
+ }
93
+
94
+ try {
95
+ switch (action) {
96
+ case 'run': {
97
+ const model = String(normalized.model || cfg.defaultModel || '').trim()
98
+ if (!model) return 'Error: "model" is required (e.g. "stability-ai/sdxl", "meta/llama-2-70b-chat").'
99
+
100
+ const input = (normalized.input as Record<string, unknown>) || {}
101
+ if (typeof normalized.prompt === 'string') {
102
+ input.prompt = normalized.prompt
103
+ }
104
+
105
+ const version = typeof normalized.version === 'string' ? normalized.version.trim() : undefined
106
+
107
+ // Build request body
108
+ const body: Record<string, unknown> = { input }
109
+ if (version) {
110
+ body.version = version
111
+ } else {
112
+ body.model = model
113
+ }
114
+
115
+ // Try sync mode first (Prefer: wait blocks up to 60s)
116
+ const r = await replicateRequest('POST', '/predictions', cfg.apiToken, body, { Prefer: 'wait' })
117
+ if (!r.ok) return `Error: ${r.error}`
118
+
119
+ let prediction = r.data as Record<string, unknown>
120
+
121
+ // If sync didn't complete, poll
122
+ if (prediction.status !== 'succeeded' && prediction.status !== 'failed' && prediction.status !== 'canceled') {
123
+ prediction = await pollPrediction(cfg.apiToken, String(prediction.id), cfg)
124
+ }
125
+
126
+ if (prediction.status === 'failed') {
127
+ return `Prediction failed: ${prediction.error || 'unknown error'}`
128
+ }
129
+
130
+ return JSON.stringify(formatPrediction(prediction))
131
+ }
132
+
133
+ case 'get': {
134
+ const predictionId = String(normalized.predictionId || normalized.id || '').trim()
135
+ if (!predictionId) return 'Error: "predictionId" is required.'
136
+
137
+ const r = await replicateRequest('GET', `/predictions/${predictionId}`, cfg.apiToken)
138
+ if (!r.ok) return `Error: ${r.error}`
139
+ return JSON.stringify(formatPrediction(r.data as Record<string, unknown>))
140
+ }
141
+
142
+ case 'cancel': {
143
+ const predictionId = String(normalized.predictionId || normalized.id || '').trim()
144
+ if (!predictionId) return 'Error: "predictionId" is required.'
145
+
146
+ const r = await replicateRequest('POST', `/predictions/${predictionId}/cancel`, cfg.apiToken)
147
+ if (!r.ok) return `Error: ${r.error}`
148
+ return `Prediction ${predictionId} canceled.`
149
+ }
150
+
151
+ case 'get_model': {
152
+ const model = String(normalized.model || '').trim()
153
+ if (!model) return 'Error: "model" is required (e.g. "stability-ai/sdxl").'
154
+
155
+ const r = await replicateRequest('GET', `/models/${model}`, cfg.apiToken)
156
+ if (!r.ok) return `Error: ${r.error}`
157
+ const data = r.data as Record<string, unknown>
158
+ return JSON.stringify({
159
+ owner: data.owner,
160
+ name: data.name,
161
+ description: data.description,
162
+ visibility: data.visibility,
163
+ url: data.url,
164
+ latest_version: data.latest_version ? {
165
+ id: (data.latest_version as Record<string, unknown>).id,
166
+ created_at: (data.latest_version as Record<string, unknown>).created_at,
167
+ } : null,
168
+ run_count: data.run_count,
169
+ })
170
+ }
171
+
172
+ case 'search': {
173
+ const query = String(normalized.query || normalized.search || '').trim()
174
+ const cursor = typeof normalized.cursor === 'string' ? normalized.cursor : undefined
175
+ const params = new URLSearchParams()
176
+ if (query) params.set('query', query)
177
+ if (cursor) params.set('cursor', cursor)
178
+ const suffix = params.toString() ? `?${params}` : ''
179
+ const r = await replicateRequest('GET', `/models${suffix}`, cfg.apiToken)
180
+ if (!r.ok) return `Error: ${r.error}`
181
+ const data = r.data as Record<string, unknown>
182
+ const results = (data.results as Record<string, unknown>[]) ?? []
183
+ const items = results.slice(0, 20).map((m) => ({
184
+ owner: m.owner,
185
+ name: m.name,
186
+ description: typeof m.description === 'string' ? m.description.slice(0, 120) : '',
187
+ run_count: m.run_count,
188
+ url: m.url,
189
+ }))
190
+ return JSON.stringify({
191
+ models: items,
192
+ next_cursor: data.next ? (data.next as string).split('cursor=')[1]?.split('&')[0] : null,
193
+ })
194
+ }
195
+
196
+ case 'status': {
197
+ return JSON.stringify({
198
+ configured: true,
199
+ hasToken: !!cfg.apiToken,
200
+ defaultModel: cfg.defaultModel || '(none)',
201
+ timeoutMs: cfg.timeoutMs,
202
+ })
203
+ }
204
+
205
+ default:
206
+ return `Error: Unknown action "${action}". Use: run, get, cancel, get_model, search, status.`
207
+ }
208
+ } catch (err: unknown) {
209
+ return `Error: ${err instanceof Error ? err.message : String(err)}`
210
+ }
211
+ }
212
+
213
+ const ReplicatePlugin: Plugin = {
214
+ name: 'Replicate',
215
+ enabledByDefault: false,
216
+ description: 'Run any AI model on Replicate — image generation, LLMs, audio, video, and more. Search models, create predictions, check status.',
217
+ hooks: {
218
+ getCapabilityDescription: () =>
219
+ 'I can run any AI model on Replicate using `replicate`. This includes image generation (SDXL, Flux), language models, audio/video processing, and thousands more. I can search for models, run predictions, and check their status.',
220
+ } as PluginHooks,
221
+ tools: [
222
+ {
223
+ name: 'replicate',
224
+ description: 'Run AI models on Replicate. Actions: run (create and wait for prediction), get (check prediction status), cancel (stop a prediction), get_model (model details), search (find models), status (check config).',
225
+ parameters: {
226
+ type: 'object',
227
+ properties: {
228
+ action: { type: 'string', enum: ['run', 'get', 'cancel', 'get_model', 'search', 'status'], description: 'Action to perform' },
229
+ model: { type: 'string', description: 'Model identifier (e.g. "stability-ai/sdxl", "meta/llama-2-70b-chat"). Required for run/get_model.' },
230
+ version: { type: 'string', description: 'Optional specific model version hash for run.' },
231
+ input: { type: 'object', description: 'Model input parameters as key-value pairs (for run). Varies by model.' },
232
+ prompt: { type: 'string', description: 'Shorthand: sets input.prompt for models that accept a prompt (for run).' },
233
+ predictionId: { type: 'string', description: 'Prediction ID (for get/cancel).' },
234
+ query: { type: 'string', description: 'Search query (for search).' },
235
+ cursor: { type: 'string', description: 'Pagination cursor (for search).' },
236
+ },
237
+ required: ['action'],
238
+ },
239
+ execute: async (args) => executeReplicate(args),
240
+ },
241
+ ],
242
+ ui: {
243
+ settingsFields: [
244
+ {
245
+ key: 'apiToken',
246
+ label: 'API Token',
247
+ type: 'secret',
248
+ required: true,
249
+ placeholder: 'r8_...',
250
+ help: 'Your Replicate API token. Find it at replicate.com/account/api-tokens.',
251
+ },
252
+ {
253
+ key: 'defaultModel',
254
+ label: 'Default Model',
255
+ type: 'text',
256
+ placeholder: 'stability-ai/sdxl',
257
+ help: 'Default model to use when none is specified. Format: owner/model-name.',
258
+ },
259
+ {
260
+ key: 'timeoutMs',
261
+ label: 'Timeout (ms)',
262
+ type: 'number',
263
+ defaultValue: 120000,
264
+ help: 'Maximum time to wait for a prediction (default: 120000ms / 2 minutes).',
265
+ },
266
+ {
267
+ key: 'pollingIntervalMs',
268
+ label: 'Polling Interval (ms)',
269
+ type: 'number',
270
+ defaultValue: 2000,
271
+ help: 'How often to poll for prediction results when sync mode times out (default: 2000ms).',
272
+ },
273
+ ],
274
+ },
275
+ }
276
+
277
+ getPluginManager().registerBuiltin('replicate', ReplicatePlugin)
278
+
279
+ export function buildReplicateTools(bctx: ToolBuildContext): StructuredToolInterface[] {
280
+ if (!bctx.hasPlugin('replicate')) return []
281
+
282
+ return [
283
+ tool(
284
+ async (args) => executeReplicate(args),
285
+ {
286
+ name: 'replicate',
287
+ description: ReplicatePlugin.tools![0].description,
288
+ schema: z.object({
289
+ action: z.enum(['run', 'get', 'cancel', 'get_model', 'search', 'status']).describe('Action to perform'),
290
+ model: z.string().optional().describe('Model identifier (e.g. "stability-ai/sdxl")'),
291
+ version: z.string().optional().describe('Specific model version hash'),
292
+ input: z.record(z.string(), z.unknown()).optional().describe('Model input parameters'),
293
+ prompt: z.string().optional().describe('Shorthand for input.prompt'),
294
+ predictionId: z.string().optional().describe('Prediction ID (for get/cancel)'),
295
+ query: z.string().optional().describe('Search query (for search)'),
296
+ cursor: z.string().optional().describe('Pagination cursor (for search)'),
297
+ }),
298
+ },
299
+ ),
300
+ ]
301
+ }
@@ -80,7 +80,7 @@ const SampleUIPlugin: Plugin = {
80
80
  getPluginManager().registerBuiltin('sample_ui', SampleUIPlugin)
81
81
 
82
82
  export function buildSampleUITools(bctx: any) {
83
- if (!bctx.hasTool('sample_ui')) return []
83
+ if (!bctx.hasPlugin('sample_ui')) return []
84
84
  return [
85
85
  tool(
86
86
  async (args) => SampleUIPlugin.tools![0].execute(args as any, bctx),
@@ -143,7 +143,9 @@ async function executeListRuntimes() {
143
143
  const SandboxPlugin: Plugin = {
144
144
  name: 'Core Sandbox',
145
145
  description: 'Secure isolated code execution for JS, TS, and Python.',
146
- hooks: {} as PluginHooks,
146
+ hooks: {
147
+ getCapabilityDescription: () => 'I can run code in a sandbox (`sandbox_exec`) — JS/TS via Deno or Python, in an isolated environment. I get stdout, stderr, and any files created.',
148
+ } as PluginHooks,
147
149
  tools: [
148
150
  {
149
151
  name: 'sandbox_exec',
@@ -174,7 +176,7 @@ getPluginManager().registerBuiltin('sandbox', SandboxPlugin)
174
176
  * Legacy Bridge
175
177
  */
176
178
  export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterface[] {
177
- if (!bctx.hasTool('sandbox')) return []
179
+ if (!bctx.hasPlugin('sandbox')) return []
178
180
  const tools: StructuredToolInterface[] = []
179
181
 
180
182
  tools.push(
@@ -6,6 +6,7 @@ import type { ToolBuildContext } from './context'
6
6
  import type { Plugin, PluginHooks } from '@/types'
7
7
  import { getPluginManager } from '../plugins'
8
8
  import { normalizeToolInputArgs } from './normalize-tool-args'
9
+ import { createWatchJob } from '../watch-jobs'
9
10
 
10
11
  /**
11
12
  * Core Schedule Execution Logic
@@ -15,7 +16,7 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
15
16
  const delayMinutes = normalized.delayMinutes as number
16
17
  const message = normalized.message as string
17
18
  if (!context.sessionId) return 'Cannot schedule wake: no session context.'
18
- if (delayMinutes < 0 || delayMinutes > 1440) return 'delayMinutes must be between 0 and 1440 (24 hours).'
19
+ if (delayMinutes < 0 || delayMinutes > 43_200) return 'delayMinutes must be between 0 and 43200 (30 days).'
19
20
 
20
21
  if (delayMinutes === 0) {
21
22
  enqueueSystemEvent(context.sessionId, `[Scheduled Wake Event / Reminder] ${message}`)
@@ -23,15 +24,24 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
23
24
  return 'Successfully scheduled an immediate wake event.'
24
25
  }
25
26
 
26
- const delayMs = delayMinutes * 60 * 1000
27
- setTimeout(() => {
28
- if (context.sessionId) {
29
- enqueueSystemEvent(context.sessionId, `[Scheduled Wake Event / Reminder] ${message}`)
30
- requestHeartbeatNow({ sessionId: context.sessionId, reason: 'scheduled_wake' })
31
- }
32
- }, delayMs)
27
+ const runAt = Date.now() + delayMinutes * 60 * 1000
28
+ const watch = await createWatchJob({
29
+ type: 'time',
30
+ sessionId: context.sessionId,
31
+ resumeMessage: message,
32
+ description: `Scheduled wake in ${delayMinutes} minutes`,
33
+ target: { source: 'schedule_wake' },
34
+ condition: {},
35
+ runAt,
36
+ })
33
37
 
34
- return `Successfully scheduled a wake event in ${delayMinutes} minutes.`
38
+ return JSON.stringify({
39
+ ok: true,
40
+ jobId: watch.id,
41
+ delayMinutes,
42
+ runAt,
43
+ message,
44
+ })
35
45
  }
36
46
 
37
47
  /**
@@ -39,8 +49,10 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
39
49
  */
40
50
  const SchedulePlugin: Plugin = {
41
51
  name: 'Core Scheduler',
42
- description: 'Schedule wake events and reminders for agents.',
43
- hooks: {} as PluginHooks,
52
+ description: 'Schedule durable wake events and reminders for agents.',
53
+ hooks: {
54
+ getCapabilityDescription: () => 'I can set a conversational timer (`schedule_wake`) to remind myself to check back on something later in this chat.',
55
+ } as PluginHooks,
44
56
  tools: [
45
57
  {
46
58
  name: 'schedule_wake',
@@ -64,7 +76,7 @@ getPluginManager().registerBuiltin('schedule', SchedulePlugin)
64
76
  * Legacy Bridge
65
77
  */
66
78
  export function buildScheduleTools(bctx: ToolBuildContext): StructuredToolInterface[] {
67
- if (!bctx.hasTool('schedule_wake')) return []
79
+ if (!bctx.hasPlugin('schedule_wake')) return []
68
80
  return [
69
81
  tool(
70
82
  async (args) => executeScheduleWake(args as any, { sessionId: bctx.ctx?.sessionId || undefined }),
@@ -33,6 +33,7 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
33
33
  const limit = normalized.limit as number | undefined
34
34
  const agentId = (normalized.agentId ?? normalized.agent_id) as string | undefined
35
35
  const name = normalized.name as string | undefined
36
+ const updates = normalized.updates as Record<string, unknown> | undefined
36
37
  try {
37
38
  const sessions = loadSessions()
38
39
  if (action === 'list') {
@@ -51,14 +52,45 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
51
52
  const id = genId()
52
53
  const now = Date.now()
53
54
  sessions[id] = {
54
- id, name: (name || `${agent.name} Session`).trim(), cwd: context.cwd, user: 'system',
55
+ id, name: (name || `${agent.name} Chat`).trim(), cwd: context.cwd, user: 'system',
55
56
  provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
56
- messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
57
- agentId: agent.id, parentSessionId: context.sessionId || undefined, tools: agent.tools || [],
57
+ messages: [], createdAt: now, lastActiveAt: now, sessionType: 'human',
58
+ agentId: agent.id, parentSessionId: context.sessionId || undefined, plugins: agent.plugins || agent.tools || [],
58
59
  }
59
60
  saveSessions(sessions)
60
61
  return JSON.stringify({ sessionId: id, name: agent.name })
61
62
  }
63
+ if (action === 'update') {
64
+ const targetId = sessionId || context.sessionId || ''
65
+ if (!targetId) return 'sessionId required.'
66
+ const target = sessions[targetId]
67
+ if (!target) return 'Not found.'
68
+ const allowedKeys = new Set([
69
+ 'thinkingLevel',
70
+ 'connectorThinkLevel',
71
+ 'sessionResetMode',
72
+ 'sessionIdleTimeoutSec',
73
+ 'sessionMaxAgeSec',
74
+ 'sessionDailyResetAt',
75
+ 'sessionResetTimezone',
76
+ 'connectorSessionScope',
77
+ 'connectorReplyMode',
78
+ 'connectorThreadBinding',
79
+ 'connectorGroupPolicy',
80
+ 'connectorIdleTimeoutSec',
81
+ 'connectorMaxAgeSec',
82
+ 'identityState',
83
+ 'provider',
84
+ 'model',
85
+ ])
86
+ const patch = updates && typeof updates === 'object' ? updates : {}
87
+ for (const [key, value] of Object.entries(patch)) {
88
+ if (!allowedKeys.has(key)) continue
89
+ target[key] = value
90
+ }
91
+ saveSessions(sessions)
92
+ return JSON.stringify({ sessionId: targetId, updated: Object.keys(patch).filter((key) => allowedKeys.has(key)) })
93
+ }
62
94
  return `Unknown action "${action}".`
63
95
  } catch (err: any) { return `Error: ${err.message}` }
64
96
  }
@@ -69,7 +101,10 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
69
101
  const SessionInfoPlugin: Plugin = {
70
102
  name: 'Core Session Info',
71
103
  description: 'Identify current session context and manage other agent sessions.',
72
- hooks: {} as PluginHooks,
104
+ hooks: {
105
+ getCapabilityDescription: () => 'I can manage chat sessions (`manage_sessions`, `sessions_tool`, `whoami_tool`, `search_history_tool`) — check my identity, look up past conversations, message other sessions, and coordinate work.',
106
+ getOperatingGuidance: () => 'Inspect existing chats before creating duplicates.',
107
+ } as PluginHooks,
73
108
  tools: [
74
109
  {
75
110
  name: 'whoami_tool',
@@ -83,11 +118,12 @@ const SessionInfoPlugin: Plugin = {
83
118
  parameters: {
84
119
  type: 'object',
85
120
  properties: {
86
- action: { type: 'string', enum: ['list', 'history', 'spawn', 'status', 'stop'] },
121
+ action: { type: 'string', enum: ['list', 'history', 'spawn', 'status', 'stop', 'update'] },
87
122
  sessionId: { type: 'string' },
88
123
  agentId: { type: 'string' },
89
124
  message: { type: 'string' },
90
- limit: { type: 'number' }
125
+ limit: { type: 'number' },
126
+ updates: { type: 'object' },
91
127
  },
92
128
  required: ['action']
93
129
  },
@@ -102,7 +138,7 @@ getPluginManager().registerBuiltin('session_info', SessionInfoPlugin)
102
138
  * Legacy Bridge
103
139
  */
104
140
  export function buildSessionInfoTools(bctx: ToolBuildContext): StructuredToolInterface[] {
105
- if (!bctx.hasTool('manage_sessions')) return []
141
+ if (!bctx.hasPlugin('manage_sessions')) return []
106
142
  return [
107
143
  tool(
108
144
  async () => executeWhoAmI({ sessionId: bctx.ctx?.sessionId || undefined, agentId: bctx.ctx?.agentId || undefined }),
@@ -24,6 +24,21 @@ describe('module exports', () => {
24
24
  const mem = await import('./memory')
25
25
  assert.equal(typeof mem.buildMemoryTools, 'function')
26
26
  })
27
+
28
+ it('primitive tool builders are exported', async () => {
29
+ const document = await import('./document')
30
+ const extract = await import('./extract')
31
+ const table = await import('./table')
32
+ const crawl = await import('./crawl')
33
+ const mailbox = await import('./mailbox')
34
+ const humanLoop = await import('./human-loop')
35
+ assert.equal(typeof document.buildDocumentTools, 'function')
36
+ assert.equal(typeof extract.buildExtractTools, 'function')
37
+ assert.equal(typeof table.buildTableTools, 'function')
38
+ assert.equal(typeof crawl.buildCrawlTools, 'function')
39
+ assert.equal(typeof mailbox.buildMailboxTools, 'function')
40
+ assert.equal(typeof humanLoop.buildHumanLoopTools, 'function')
41
+ })
27
42
  })
28
43
 
29
44
  // ---------------------------------------------------------------------------
@@ -60,46 +75,45 @@ describe('buildSessionTools signature', () => {
60
75
  })
61
76
 
62
77
  // ---------------------------------------------------------------------------
63
- // 4. Memory tool schema — knowledge actions
78
+ // 4. Memory tool schema
64
79
  // buildMemoryTools calls getMemoryDb() eagerly so we cannot invoke it
65
80
  // without a real SQLite DB. Instead we read the source and verify the
66
- // action enum includes the knowledge actions.
81
+ // declared action enum matches the current JSON schema definition.
67
82
  // ---------------------------------------------------------------------------
68
83
  describe('memory tool knowledge actions (source verification)', () => {
69
- it('action enum in memory.ts includes knowledge_store and knowledge_search', async () => {
84
+ it('action enum in memory.ts includes the declared base actions', async () => {
70
85
  const fs = await import('fs')
71
86
  const src = fs.readFileSync(
72
87
  new URL('./memory.ts', import.meta.url).pathname,
73
88
  'utf-8',
74
89
  )
75
90
 
76
- // Find the z.enum([...]) for the action field
77
- const enumMatch = src.match(/z\.enum\(\[([^\]]+)\]\)\.describe\([^)]*action/s)
78
- assert.ok(enumMatch, 'Should find a z.enum() for the action field')
91
+ const enumMatch = src.match(/action:\s*\{\s*type:\s*'string',\s*enum:\s*\[([^\]]+)\]/s)
92
+ assert.ok(enumMatch, 'Should find the action enum in the memory tool schema')
79
93
 
80
94
  const enumBody = enumMatch![1]
81
- assert.ok(enumBody.includes("'knowledge_store'"), 'action enum should include knowledge_store')
82
- assert.ok(enumBody.includes("'knowledge_search'"), 'action enum should include knowledge_search')
95
+ const expectedActions = ['store', 'get', 'search', 'list', 'delete']
96
+ for (const action of expectedActions) {
97
+ assert.ok(
98
+ enumBody.includes(`'${action}'`),
99
+ `action enum should include '${action}'`,
100
+ )
101
+ }
83
102
  })
84
103
 
85
- it('action enum includes all expected base actions', async () => {
104
+ it('action enum does not advertise removed knowledge actions', async () => {
86
105
  const fs = await import('fs')
87
106
  const src = fs.readFileSync(
88
107
  new URL('./memory.ts', import.meta.url).pathname,
89
108
  'utf-8',
90
109
  )
91
110
 
92
- const enumMatch = src.match(/z\.enum\(\[([^\]]+)\]\)/)
111
+ const enumMatch = src.match(/action:\s*\{\s*type:\s*'string',\s*enum:\s*\[([^\]]+)\]/s)
93
112
  assert.ok(enumMatch)
94
113
  const enumBody = enumMatch![1]
95
114
 
96
- const expectedActions = ['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']
97
- for (const action of expectedActions) {
98
- assert.ok(
99
- enumBody.includes(`'${action}'`),
100
- `action enum should include '${action}'`,
101
- )
102
- }
115
+ assert.equal(enumBody.includes("'knowledge_store'"), false)
116
+ assert.equal(enumBody.includes("'knowledge_search'"), false)
103
117
  })
104
118
  })
105
119
 
@@ -162,7 +162,10 @@ async function executeShellAction(args: Record<string, unknown>, bctx: { cwd: st
162
162
  const ShellPlugin: Plugin = {
163
163
  name: 'Core Shell',
164
164
  description: 'Execute shell commands and manage background processes.',
165
- hooks: {} as PluginHooks,
165
+ hooks: {
166
+ getCapabilityDescription: () => 'I can run shell commands (`execute_command`) — servers, installs, scripts, git, builds, anything. I can run things in the background for long-lived processes like dev servers.',
167
+ getOperatingGuidance: () => ['Shell: use `execute_command` for servers, installs, scripts, git. Use `background=true` for long-lived processes.', 'Verify servers with `process_tool` status/log and liveness probes before claiming success.', 'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.'],
168
+ } as PluginHooks,
166
169
  tools: [
167
170
  {
168
171
  name: 'shell',
@@ -185,7 +188,7 @@ const ShellPlugin: Plugin = {
185
188
  getPluginManager().registerBuiltin('shell', ShellPlugin)
186
189
 
187
190
  export function buildShellTools(bctx: ToolBuildContext) {
188
- if (!bctx.hasTool('shell')) return []
191
+ if (!bctx.hasPlugin('shell')) return []
189
192
  return [
190
193
  tool(
191
194
  async (args) => executeShellAction(args, { ...bctx.ctx, cwd: bctx.cwd }),