@swarmclawai/swarmclaw 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/server-cmd.js +1 -1
- package/package.json +2 -2
- package/src/components/schedules/schedule-sheet.tsx +70 -13
- package/src/lib/server/builtin-plugins.ts +0 -1
- package/src/lib/server/runtime/scheduler.ts +57 -29
- package/src/lib/server/schedules/schedule-normalization.ts +29 -4
- package/src/lib/server/session-tools/index.ts +0 -2
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy.ts +0 -1
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/types/index.ts +4 -0
- package/src/lib/server/session-tools/sample-ui.ts +0 -97
package/bin/server-cmd.js
CHANGED
|
@@ -138,7 +138,7 @@ function runBuild() {
|
|
|
138
138
|
// Run Next.js build
|
|
139
139
|
log('Building Next.js application (this may take a minute)...')
|
|
140
140
|
const nextCli = path.join(SWARMCLAW_HOME, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
141
|
-
execFileSync(process.execPath, [nextCli, 'build'], {
|
|
141
|
+
execFileSync(process.execPath, [nextCli, 'build', '--no-turbopack'], {
|
|
142
142
|
cwd: SWARMCLAW_HOME,
|
|
143
143
|
stdio: 'inherit',
|
|
144
144
|
env: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"@huggingface/transformers": "^3.8.1",
|
|
76
76
|
"@langchain/anthropic": "^1.3.18",
|
|
77
77
|
"@langchain/core": "^1.1.31",
|
|
78
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
78
79
|
"@langchain/langgraph": "^1.2.2",
|
|
79
80
|
"@langchain/openai": "^1.2.8",
|
|
80
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
81
81
|
"@multiavatar/multiavatar": "^1.0.7",
|
|
82
82
|
"@playwright/mcp": "^0.0.68",
|
|
83
83
|
"@slack/bolt": "^4.6.0",
|
|
@@ -104,6 +104,8 @@ export function ScheduleSheet() {
|
|
|
104
104
|
const [cron, setCron] = useState('0 * * * *')
|
|
105
105
|
const [intervalMs, setIntervalMs] = useState(3600000)
|
|
106
106
|
const [status, setStatus] = useState<ScheduleStatus>('active')
|
|
107
|
+
const [taskMode, setTaskMode] = useState<'task' | 'wake_only'>('task')
|
|
108
|
+
const [message, setMessage] = useState('')
|
|
107
109
|
const [customCron, setCustomCron] = useState(false)
|
|
108
110
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
109
111
|
const [deleting, setDeleting] = useState(false)
|
|
@@ -131,6 +133,8 @@ export function ScheduleSheet() {
|
|
|
131
133
|
setCron(editing.cron || '0 * * * *')
|
|
132
134
|
setIntervalMs(editing.intervalMs || 3600000)
|
|
133
135
|
setStatus(editing.status)
|
|
136
|
+
setTaskMode(editing.taskMode === 'wake_only' ? 'wake_only' : 'task')
|
|
137
|
+
setMessage(editing.message || '')
|
|
134
138
|
setCustomCron(!CRON_PRESETS.some((p) => p.cron === editing.cron))
|
|
135
139
|
} else if (templatePrefill) {
|
|
136
140
|
// Opened from a quick-start card with pre-filled values
|
|
@@ -155,6 +159,8 @@ export function ScheduleSheet() {
|
|
|
155
159
|
setCron('0 * * * *')
|
|
156
160
|
setIntervalMs(3600000)
|
|
157
161
|
setStatus('active')
|
|
162
|
+
setTaskMode('task')
|
|
163
|
+
setMessage('')
|
|
158
164
|
setCustomCron(false)
|
|
159
165
|
}
|
|
160
166
|
}
|
|
@@ -178,7 +184,9 @@ export function ScheduleSheet() {
|
|
|
178
184
|
const data = {
|
|
179
185
|
name: name.trim(),
|
|
180
186
|
agentId,
|
|
181
|
-
taskPrompt,
|
|
187
|
+
taskPrompt: taskMode === 'wake_only' ? message : taskPrompt,
|
|
188
|
+
taskMode,
|
|
189
|
+
message: taskMode === 'wake_only' ? message : undefined,
|
|
182
190
|
scheduleType,
|
|
183
191
|
cron: scheduleType === 'cron' ? cron : undefined,
|
|
184
192
|
intervalMs: scheduleType === 'interval' ? intervalMs : undefined,
|
|
@@ -217,7 +225,7 @@ export function ScheduleSheet() {
|
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
// Step validation
|
|
220
|
-
const step0Valid = name.trim().length > 0 && agentId.length > 0 && taskPrompt.trim().length > 0
|
|
228
|
+
const step0Valid = name.trim().length > 0 && agentId.length > 0 && (taskMode === 'wake_only' ? message.trim().length > 0 : taskPrompt.trim().length > 0)
|
|
221
229
|
const step1Valid = scheduleType === 'cron' ? cron.trim().length > 0 : intervalMs > 0
|
|
222
230
|
|
|
223
231
|
const selectedAgent = agentId ? agents[agentId] : null
|
|
@@ -333,16 +341,61 @@ export function ScheduleSheet() {
|
|
|
333
341
|
</div>
|
|
334
342
|
|
|
335
343
|
<div className="mb-8">
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
<div className="flex items-center gap-2 mb-3">
|
|
345
|
+
<SectionLabel className="mb-0">Task Mode</SectionLabel>
|
|
346
|
+
<HintTip text="Create task: creates a board task for the agent. Wake agent only: sends a message to the agent without creating a task." />
|
|
347
|
+
</div>
|
|
348
|
+
<div className="grid grid-cols-2 gap-3">
|
|
349
|
+
<button
|
|
350
|
+
onClick={() => setTaskMode('task')}
|
|
351
|
+
className={`py-3 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
|
|
352
|
+
active:scale-[0.97] text-[14px] font-600 border
|
|
353
|
+
${taskMode === 'task'
|
|
354
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
355
|
+
: 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
|
|
356
|
+
style={{ fontFamily: 'inherit' }}
|
|
357
|
+
>
|
|
358
|
+
Create task
|
|
359
|
+
</button>
|
|
360
|
+
<button
|
|
361
|
+
onClick={() => setTaskMode('wake_only')}
|
|
362
|
+
className={`py-3 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
|
|
363
|
+
active:scale-[0.97] text-[14px] font-600 border
|
|
364
|
+
${taskMode === 'wake_only'
|
|
365
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
366
|
+
: 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
|
|
367
|
+
style={{ fontFamily: 'inherit' }}
|
|
368
|
+
>
|
|
369
|
+
Wake agent only
|
|
370
|
+
</button>
|
|
371
|
+
</div>
|
|
345
372
|
</div>
|
|
373
|
+
|
|
374
|
+
{taskMode === 'wake_only' ? (
|
|
375
|
+
<div className="mb-8">
|
|
376
|
+
<SectionLabel>Wake Message</SectionLabel>
|
|
377
|
+
<textarea
|
|
378
|
+
value={message}
|
|
379
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
380
|
+
placeholder="Message to send to the agent when woken"
|
|
381
|
+
rows={4}
|
|
382
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
383
|
+
style={{ fontFamily: 'inherit' }}
|
|
384
|
+
/>
|
|
385
|
+
</div>
|
|
386
|
+
) : (
|
|
387
|
+
<div className="mb-8">
|
|
388
|
+
<SectionLabel>Task Prompt</SectionLabel>
|
|
389
|
+
<textarea
|
|
390
|
+
value={taskPrompt}
|
|
391
|
+
onChange={(e) => setTaskPrompt(e.target.value)}
|
|
392
|
+
placeholder="What should the agent do when triggered?"
|
|
393
|
+
rows={4}
|
|
394
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
395
|
+
style={{ fontFamily: 'inherit' }}
|
|
396
|
+
/>
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
346
399
|
</div>
|
|
347
400
|
)}
|
|
348
401
|
|
|
@@ -498,8 +551,12 @@ export function ScheduleSheet() {
|
|
|
498
551
|
</div>
|
|
499
552
|
)}
|
|
500
553
|
<div>
|
|
501
|
-
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">
|
|
502
|
-
<div className="text-[
|
|
554
|
+
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Mode</span>
|
|
555
|
+
<div className="text-[14px] text-text font-600 mt-0.5">{taskMode === 'wake_only' ? 'Wake agent only' : 'Create task'}</div>
|
|
556
|
+
</div>
|
|
557
|
+
<div>
|
|
558
|
+
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">{taskMode === 'wake_only' ? 'Wake Message' : 'Task'}</span>
|
|
559
|
+
<div className="text-[13px] text-text-2 mt-0.5 whitespace-pre-wrap">{taskMode === 'wake_only' ? message : taskPrompt}</div>
|
|
503
560
|
</div>
|
|
504
561
|
<div className="h-px bg-white/[0.06]" />
|
|
505
562
|
<div>
|
|
@@ -6,7 +6,6 @@ import '@/lib/server/session-tools/memory'
|
|
|
6
6
|
import '@/lib/server/session-tools/platform'
|
|
7
7
|
import '@/lib/server/session-tools/monitor'
|
|
8
8
|
import '@/lib/server/session-tools/discovery'
|
|
9
|
-
import '@/lib/server/session-tools/sample-ui'
|
|
10
9
|
import '@/lib/server/session-tools/git'
|
|
11
10
|
import '@/lib/server/session-tools/wallet'
|
|
12
11
|
import '@/lib/server/session-tools/connector'
|
|
@@ -40,6 +40,8 @@ interface SchedulerScheduleLike {
|
|
|
40
40
|
followupThreadId?: string | null
|
|
41
41
|
followupSenderId?: string | null
|
|
42
42
|
followupSenderName?: string | null
|
|
43
|
+
taskMode?: 'task' | 'wake_only'
|
|
44
|
+
message?: string
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export function startScheduler() {
|
|
@@ -67,7 +69,10 @@ function computeNextRuns() {
|
|
|
67
69
|
if (schedule.status !== 'active') continue
|
|
68
70
|
if (schedule.scheduleType === 'cron' && schedule.cron && !schedule.nextRunAt) {
|
|
69
71
|
try {
|
|
70
|
-
const interval = CronExpressionParser.parse(
|
|
72
|
+
const interval = CronExpressionParser.parse(
|
|
73
|
+
schedule.cron,
|
|
74
|
+
schedule.timezone ? { tz: schedule.timezone } : undefined,
|
|
75
|
+
)
|
|
71
76
|
schedule.nextRunAt = interval.next().getTime()
|
|
72
77
|
changedEntries.push([schedule.id, schedule])
|
|
73
78
|
} catch (err) {
|
|
@@ -157,34 +162,57 @@ async function tick() {
|
|
|
157
162
|
// Compute next run
|
|
158
163
|
advanceSchedule(schedule)
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
165
|
+
if (schedule.taskMode === 'wake_only') {
|
|
166
|
+
// Wake-only: no board task, just heartbeat the agent
|
|
167
|
+
upsertSchedule(schedule.id, schedule)
|
|
168
|
+
|
|
169
|
+
const wakeMessage = schedule.message || `Schedule triggered: ${schedule.name}`
|
|
170
|
+
pushMainLoopEventToMainSessions({
|
|
171
|
+
type: 'schedule_fired',
|
|
172
|
+
text: `Schedule fired (wake-only): "${schedule.name}" (${schedule.id}) run #${schedule.runNumber}`,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
if (schedule.createdInSessionId) {
|
|
176
|
+
enqueueSystemEvent(schedule.createdInSessionId, wakeMessage)
|
|
177
|
+
}
|
|
178
|
+
requestHeartbeatNow({
|
|
179
|
+
agentId: schedule.agentId,
|
|
180
|
+
eventId: `${schedule.id}:${schedule.runNumber}`,
|
|
181
|
+
reason: 'schedule',
|
|
182
|
+
source: `schedule:${schedule.id}`,
|
|
183
|
+
resumeMessage: wakeMessage,
|
|
184
|
+
detail: `Run #${schedule.runNumber} (wake-only).`,
|
|
185
|
+
})
|
|
186
|
+
} else {
|
|
187
|
+
// Default task mode: create a board task
|
|
188
|
+
const { taskId } = prepareScheduledTaskRun({
|
|
189
|
+
schedule,
|
|
190
|
+
tasks,
|
|
191
|
+
now,
|
|
192
|
+
scheduleSignature,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
upsertTask(taskId, tasks[taskId])
|
|
196
|
+
upsertSchedule(schedule.id, schedule)
|
|
197
|
+
|
|
198
|
+
enqueueTask(taskId)
|
|
199
|
+
if (scheduleSignature) inFlightScheduleKeys.add(scheduleSignature)
|
|
200
|
+
pushMainLoopEventToMainSessions({
|
|
201
|
+
type: 'schedule_fired',
|
|
202
|
+
text: `Schedule fired: "${schedule.name}" (${schedule.id}) run #${schedule.runNumber} — task ${taskId}`,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (schedule.createdInSessionId) {
|
|
206
|
+
enqueueSystemEvent(schedule.createdInSessionId, `Schedule triggered: ${schedule.name}`)
|
|
207
|
+
}
|
|
208
|
+
requestHeartbeatNow({
|
|
209
|
+
agentId: schedule.agentId,
|
|
210
|
+
eventId: `${schedule.id}:${schedule.runNumber}`,
|
|
211
|
+
reason: 'schedule',
|
|
212
|
+
source: `schedule:${schedule.id}`,
|
|
213
|
+
resumeMessage: `Schedule triggered: ${schedule.name}`,
|
|
214
|
+
detail: `Run #${schedule.runNumber} queued task ${taskId}.`,
|
|
215
|
+
})
|
|
180
216
|
}
|
|
181
|
-
requestHeartbeatNow({
|
|
182
|
-
agentId: schedule.agentId,
|
|
183
|
-
eventId: `${schedule.id}:${schedule.runNumber}`,
|
|
184
|
-
reason: 'schedule',
|
|
185
|
-
source: `schedule:${schedule.id}`,
|
|
186
|
-
resumeMessage: `Schedule triggered: ${schedule.name}`,
|
|
187
|
-
detail: `Run #${schedule.runNumber} queued task ${taskId}.`,
|
|
188
|
-
})
|
|
189
217
|
}
|
|
190
218
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { CronExpressionParser } from 'cron-parser'
|
|
3
4
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
4
5
|
|
|
5
6
|
type SchedulePayload = Record<string, unknown>
|
|
@@ -212,11 +213,24 @@ export function normalizeSchedulePayload(payload: SchedulePayload, opts: Normali
|
|
|
212
213
|
}
|
|
213
214
|
normalized.agentId = agentId
|
|
214
215
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
// Preserve taskMode and message fields
|
|
217
|
+
const taskMode = normalized.taskMode === 'wake_only' ? 'wake_only' : 'task'
|
|
218
|
+
normalized.taskMode = taskMode
|
|
219
|
+
if (taskMode === 'wake_only') {
|
|
220
|
+
const message = trimString(normalized.message)
|
|
221
|
+
if (!message) {
|
|
222
|
+
return { ok: false, error: 'Error: wake_only schedules require a message.' }
|
|
223
|
+
}
|
|
224
|
+
normalized.message = message
|
|
225
|
+
// wake_only still needs a taskPrompt for display/logging — derive or use message
|
|
226
|
+
normalized.taskPrompt = normalized.taskPrompt ? trimString(normalized.taskPrompt) : message
|
|
227
|
+
} else {
|
|
228
|
+
const taskPrompt = deriveTaskPrompt(normalized)
|
|
229
|
+
if (!taskPrompt) {
|
|
230
|
+
return { ok: false, error: 'Error: schedules require a taskPrompt, command, or action/path payload.' }
|
|
231
|
+
}
|
|
232
|
+
normalized.taskPrompt = taskPrompt
|
|
218
233
|
}
|
|
219
|
-
normalized.taskPrompt = taskPrompt
|
|
220
234
|
|
|
221
235
|
const validationError = validateScheduleArtifacts(normalized, baseDir)
|
|
222
236
|
if (validationError) return { ok: false, error: `Error: ${validationError}` }
|
|
@@ -228,6 +242,17 @@ export function normalizeSchedulePayload(payload: SchedulePayload, opts: Normali
|
|
|
228
242
|
} else if (normalized.scheduleType === 'interval') {
|
|
229
243
|
const intervalMs = normalizePositiveInt(normalized.intervalMs)
|
|
230
244
|
if (intervalMs != null) normalized.nextRunAt = applyStagger(now + intervalMs, normalized.staggerSec as number | null)
|
|
245
|
+
} else if (normalized.scheduleType === 'cron' && normalized.cron) {
|
|
246
|
+
try {
|
|
247
|
+
const cronTimezone = trimString(normalized.timezone)
|
|
248
|
+
const interval = CronExpressionParser.parse(
|
|
249
|
+
normalized.cron as string,
|
|
250
|
+
cronTimezone ? { tz: cronTimezone } : undefined,
|
|
251
|
+
)
|
|
252
|
+
normalized.nextRunAt = applyStagger(interval.next().getTime(), normalized.staggerSec as number | null)
|
|
253
|
+
} catch {
|
|
254
|
+
return { ok: false, error: 'Error: invalid cron expression.' }
|
|
255
|
+
}
|
|
231
256
|
}
|
|
232
257
|
}
|
|
233
258
|
|
|
@@ -32,7 +32,6 @@ import { buildOpenClawNodeTools } from './openclaw-nodes'
|
|
|
32
32
|
import { buildContextTools } from './context-mgmt'
|
|
33
33
|
import { buildDiscoveryTools } from './discovery'
|
|
34
34
|
import { buildMonitorTools } from './monitor'
|
|
35
|
-
import { buildSampleUITools } from './sample-ui'
|
|
36
35
|
import { buildPluginCreatorTools } from './plugin-creator'
|
|
37
36
|
import { buildImageGenTools } from './image-gen'
|
|
38
37
|
import { buildEmailTools } from './email'
|
|
@@ -182,7 +181,6 @@ export async function buildSessionTools(cwd: string, enabledPlugins: string[], c
|
|
|
182
181
|
['context_mgmt', buildContextTools],
|
|
183
182
|
['discovery', buildDiscoveryTools],
|
|
184
183
|
['monitor', buildMonitorTools],
|
|
185
|
-
['sample_ui', buildSampleUITools],
|
|
186
184
|
['plugin_creator', buildPluginCreatorTools],
|
|
187
185
|
['image_gen', buildImageGenTools],
|
|
188
186
|
['email', buildEmailTools],
|
|
@@ -24,7 +24,6 @@ const PLUGIN_ALIAS_GROUPS: string[][] = [
|
|
|
24
24
|
['sandbox', 'sandbox_exec', 'sandbox_list_runtimes'],
|
|
25
25
|
['wallet', 'wallet_tool'],
|
|
26
26
|
['monitor', 'monitor_tool'],
|
|
27
|
-
['sample_ui', 'show_plugin_card'],
|
|
28
27
|
['context_mgmt', 'context_status', 'context_summarize'],
|
|
29
28
|
['openclaw_workspace'],
|
|
30
29
|
['openclaw_nodes'],
|
|
@@ -83,7 +83,6 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
|
|
|
83
83
|
spawn_subagent: { categories: ['delegation', 'platform'], concreteTools: ['spawn_subagent', 'delegate_to_agent'] },
|
|
84
84
|
context_mgmt: { categories: ['memory'], concreteTools: ['context_mgmt', 'context_status', 'context_summarize'] },
|
|
85
85
|
plugin_creator: { categories: ['filesystem', 'execution'], concreteTools: ['plugin_creator', 'plugin_creator_tool'] },
|
|
86
|
-
sample_ui: { categories: ['platform'], concreteTools: ['sample_ui', 'show_plugin_card'] },
|
|
87
86
|
mailbox: { categories: ['network', 'platform', 'outbound'], concreteTools: ['mailbox', 'inbox'] },
|
|
88
87
|
ask_human: { categories: ['platform'], concreteTools: ['ask_human', 'human_loop'] },
|
|
89
88
|
document: { categories: ['filesystem', 'platform'], concreteTools: ['document', 'ocr_document', 'parse_document'] },
|
|
@@ -24,7 +24,6 @@ export const AVAILABLE_TOOLS: ToolDefinition[] = [
|
|
|
24
24
|
{ id: 'wallet', label: 'Wallet', description: 'Manage agent crypto wallet — check balance, send SOL, view transactions' },
|
|
25
25
|
{ id: 'monitor', label: 'Monitor', description: 'System observability: check resource usage, watch logs, and ping endpoints' },
|
|
26
26
|
{ id: 'plugin_creator', label: 'Plugin Creator', description: 'Design focused plugins for durable capabilities and recurring automations' },
|
|
27
|
-
{ id: 'sample_ui', label: 'Sample UI', description: 'Demonstration of dynamic UI injection into Sidebar and Chat Header' },
|
|
28
27
|
{ id: 'image_gen', label: 'Image Generation', description: 'Generate images from text prompts using OpenAI, Stability AI, Replicate, fal.ai, and more' },
|
|
29
28
|
{ id: 'email', label: 'Email', description: 'Send emails via SMTP with plain text and HTML support' },
|
|
30
29
|
{ id: 'calendar', label: 'Calendar', description: 'Manage Google Calendar or Outlook events — list, create, update, delete' },
|
package/src/types/index.ts
CHANGED
|
@@ -986,6 +986,10 @@ export interface Schedule {
|
|
|
986
986
|
agentId: string
|
|
987
987
|
projectId?: string
|
|
988
988
|
taskPrompt: string
|
|
989
|
+
/** 'task' (default) creates a board task; 'wake_only' just wakes the agent with a message */
|
|
990
|
+
taskMode?: 'task' | 'wake_only'
|
|
991
|
+
/** Wake message sent to agent when taskMode is 'wake_only' */
|
|
992
|
+
message?: string
|
|
989
993
|
scheduleType: ScheduleType
|
|
990
994
|
action?: string
|
|
991
995
|
path?: string
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { tool } from '@langchain/core/tools'
|
|
3
|
-
import { getPluginManager } from '../plugins'
|
|
4
|
-
import type { Plugin, PluginHooks } from '@/types'
|
|
5
|
-
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Sample UI Extension Plugin
|
|
9
|
-
* This demonstrates how a plugin can add a sidebar item,
|
|
10
|
-
* a chat header widget, and a custom message type.
|
|
11
|
-
*/
|
|
12
|
-
const SampleUIPlugin: Plugin = {
|
|
13
|
-
name: 'Sample UI',
|
|
14
|
-
description: 'Demonstration of plugin-driven UI: Sidebar, Header, and Chat.',
|
|
15
|
-
ui: {
|
|
16
|
-
sidebarItems: [
|
|
17
|
-
{
|
|
18
|
-
id: 'sample-dashboard',
|
|
19
|
-
label: 'Plugin View',
|
|
20
|
-
href: 'https://openclaw.ai',
|
|
21
|
-
position: 'top'
|
|
22
|
-
}
|
|
23
|
-
],
|
|
24
|
-
headerWidgets: [
|
|
25
|
-
{
|
|
26
|
-
id: 'sample-status',
|
|
27
|
-
label: '🔌 Plugin Active'
|
|
28
|
-
}
|
|
29
|
-
],
|
|
30
|
-
chatInputActions: [
|
|
31
|
-
{
|
|
32
|
-
id: 'sample-action',
|
|
33
|
-
label: 'Quick Scan',
|
|
34
|
-
tooltip: 'Run a sample system scan',
|
|
35
|
-
action: 'message',
|
|
36
|
-
value: 'Please perform a quick system scan and report the health.'
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
},
|
|
40
|
-
hooks: {
|
|
41
|
-
transformInboundMessage: async ({ text }) => {
|
|
42
|
-
console.log('[plugin:sample_ui] Transforming inbound message')
|
|
43
|
-
return text // No-op but demonstrates hook
|
|
44
|
-
},
|
|
45
|
-
transformOutboundMessage: async ({ text }) => {
|
|
46
|
-
console.log('[plugin:sample_ui] Transforming outbound message')
|
|
47
|
-
return text + '\n\n*-- Sent via Sample UI Plugin --*'
|
|
48
|
-
}
|
|
49
|
-
} as PluginHooks,
|
|
50
|
-
tools: [
|
|
51
|
-
{
|
|
52
|
-
name: 'show_plugin_card',
|
|
53
|
-
description: 'Trigger a rich UI card in the chat using the plugin-ui message kind.',
|
|
54
|
-
parameters: {
|
|
55
|
-
type: 'object',
|
|
56
|
-
properties: {
|
|
57
|
-
title: { type: 'string' },
|
|
58
|
-
content: { type: 'string' }
|
|
59
|
-
},
|
|
60
|
-
required: ['title', 'content']
|
|
61
|
-
},
|
|
62
|
-
execute: async (args) => {
|
|
63
|
-
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
64
|
-
const title = normalized.title as string
|
|
65
|
-
const content = normalized.content as string
|
|
66
|
-
// Return a structured payload that the frontend MessageBubble will interpret
|
|
67
|
-
return JSON.stringify({
|
|
68
|
-
kind: 'plugin-ui',
|
|
69
|
-
text: `### ${title}\n\n${content}`,
|
|
70
|
-
actions: [
|
|
71
|
-
{ id: 'view-more', label: 'View Details', href: 'https://openclaw.ai' }
|
|
72
|
-
]
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Auto-register
|
|
80
|
-
getPluginManager().registerBuiltin('sample_ui', SampleUIPlugin)
|
|
81
|
-
|
|
82
|
-
export function buildSampleUITools(bctx: any) {
|
|
83
|
-
if (!bctx.hasPlugin('sample_ui')) return []
|
|
84
|
-
return [
|
|
85
|
-
tool(
|
|
86
|
-
async (args) => SampleUIPlugin.tools![0].execute(args as any, bctx),
|
|
87
|
-
{
|
|
88
|
-
name: 'show_plugin_card',
|
|
89
|
-
description: SampleUIPlugin.tools![0].description,
|
|
90
|
-
schema: z.object({
|
|
91
|
-
title: z.string(),
|
|
92
|
-
content: z.string()
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
]
|
|
97
|
-
}
|