@swarmclawai/swarmclaw 0.4.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/next.config.ts +8 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +20 -21
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +10 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +2 -2
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +2 -2
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +28 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +2 -0
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +116 -14
- package/src/components/chat/chat-area.tsx +27 -4
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/tool-call-bubble.tsx +9 -3
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +2 -1
- package/src/components/tasks/task-list.tsx +5 -2
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +38 -4
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +392 -3
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +6 -6
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +22 -10
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +2 -2
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +3 -3
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +33 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage.ts +12 -0
- package/src/lib/server/stream-agent-chat.ts +29 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +9 -1
- package/src/types/index.ts +27 -2
|
@@ -9,8 +9,42 @@ import { toast } from 'sonner'
|
|
|
9
9
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
10
10
|
import type { ProviderType, ClaudeSkill } from '@/types'
|
|
11
11
|
import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
|
|
12
|
+
import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
+
const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
|
|
15
|
+
|
|
16
|
+
function formatHbDuration(sec: number): string {
|
|
17
|
+
if (sec >= 3600) {
|
|
18
|
+
const h = Math.floor(sec / 3600)
|
|
19
|
+
const m = Math.floor((sec % 3600) / 60)
|
|
20
|
+
return m > 0 ? `${h}h${m}m` : `${h}h`
|
|
21
|
+
}
|
|
22
|
+
if (sec >= 60) return `${Math.floor(sec / 60)}m`
|
|
23
|
+
return `${sec}s`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Parse a stored heartbeatInterval string or heartbeatIntervalSec number to a select-friendly string of seconds */
|
|
27
|
+
function parseDurationToSec(interval: string | number | null | undefined, intervalSec: number | null | undefined): string {
|
|
28
|
+
if (intervalSec != null && Number.isFinite(intervalSec) && intervalSec > 0) {
|
|
29
|
+
// Snap to nearest preset if close, otherwise use raw value
|
|
30
|
+
const closest = HB_PRESETS.find((p) => p === Math.round(intervalSec))
|
|
31
|
+
if (closest) return String(closest)
|
|
32
|
+
}
|
|
33
|
+
if (typeof interval === 'number' && Number.isFinite(interval) && interval > 0) {
|
|
34
|
+
return String(Math.round(interval))
|
|
35
|
+
}
|
|
36
|
+
if (interval != null && typeof interval === 'string' && interval.trim()) {
|
|
37
|
+
const t = interval.trim().toLowerCase()
|
|
38
|
+
const n = Number(t)
|
|
39
|
+
if (Number.isFinite(n) && n > 0) return String(Math.round(n))
|
|
40
|
+
const m = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/)
|
|
41
|
+
if (m && (m[1] || m[2] || m[3])) {
|
|
42
|
+
const total = (m[1] ? parseInt(m[1]) * 3600 : 0) + (m[2] ? parseInt(m[2]) * 60 : 0) + (m[3] ? parseInt(m[3]) : 0)
|
|
43
|
+
if (total > 0) return String(total)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return '' // default
|
|
47
|
+
}
|
|
14
48
|
|
|
15
49
|
export function AgentSheet() {
|
|
16
50
|
const open = useAppStore((s) => s.agentSheetOpen)
|
|
@@ -19,6 +53,8 @@ export function AgentSheet() {
|
|
|
19
53
|
const setEditingId = useAppStore((s) => s.setEditingAgentId)
|
|
20
54
|
const agents = useAppStore((s) => s.agents)
|
|
21
55
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
56
|
+
const projects = useAppStore((s) => s.projects)
|
|
57
|
+
const loadProjects = useAppStore((s) => s.loadProjects)
|
|
22
58
|
const providers = useAppStore((s) => s.providers)
|
|
23
59
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
24
60
|
const credentials = useAppStore((s) => s.credentials)
|
|
@@ -60,9 +96,12 @@ export function AgentSheet() {
|
|
|
60
96
|
const [capInput, setCapInput] = useState('')
|
|
61
97
|
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
62
98
|
const [openclawEnabled, setOpenclawEnabled] = useState(false)
|
|
99
|
+
const [projectId, setProjectId] = useState<string | undefined>(undefined)
|
|
100
|
+
const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
|
|
63
101
|
const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
|
|
64
|
-
const [
|
|
102
|
+
const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
|
|
65
103
|
const [heartbeatModel, setHeartbeatModel] = useState('')
|
|
104
|
+
const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
|
|
66
105
|
const [addingKey, setAddingKey] = useState(false)
|
|
67
106
|
const [newKeyName, setNewKeyName] = useState('')
|
|
68
107
|
const [newKeyValue, setNewKeyValue] = useState('')
|
|
@@ -105,6 +144,7 @@ export function AgentSheet() {
|
|
|
105
144
|
loadProviders()
|
|
106
145
|
loadCredentials()
|
|
107
146
|
loadSkills()
|
|
147
|
+
loadProjects()
|
|
108
148
|
loadClaudeSkills()
|
|
109
149
|
setTestStatus('idle')
|
|
110
150
|
setTestMessage('')
|
|
@@ -130,9 +170,12 @@ export function AgentSheet() {
|
|
|
130
170
|
setCapInput('')
|
|
131
171
|
setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
|
|
132
172
|
setOpenclawEnabled(editing.provider === 'openclaw')
|
|
173
|
+
setProjectId(editing.projectId)
|
|
174
|
+
setThinkingLevel(editing.thinkingLevel || '')
|
|
133
175
|
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
134
|
-
|
|
176
|
+
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
135
177
|
setHeartbeatModel(editing.heartbeatModel || '')
|
|
178
|
+
setHeartbeatPrompt(editing.heartbeatPrompt || '')
|
|
136
179
|
} else {
|
|
137
180
|
setName('')
|
|
138
181
|
setDescription('')
|
|
@@ -154,9 +197,12 @@ export function AgentSheet() {
|
|
|
154
197
|
setCapInput('')
|
|
155
198
|
setOllamaMode('local')
|
|
156
199
|
setOpenclawEnabled(false)
|
|
200
|
+
setProjectId(undefined)
|
|
201
|
+
setThinkingLevel('')
|
|
157
202
|
setHeartbeatEnabled(false)
|
|
158
|
-
|
|
203
|
+
setHeartbeatIntervalSec('')
|
|
159
204
|
setHeartbeatModel('')
|
|
205
|
+
setHeartbeatPrompt('')
|
|
160
206
|
}
|
|
161
207
|
}
|
|
162
208
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -244,9 +290,13 @@ export function AgentSheet() {
|
|
|
244
290
|
fallbackCredentialIds,
|
|
245
291
|
platformAssignScope,
|
|
246
292
|
capabilities,
|
|
293
|
+
projectId: projectId || undefined,
|
|
294
|
+
thinkingLevel: thinkingLevel || undefined,
|
|
247
295
|
heartbeatEnabled,
|
|
248
|
-
heartbeatInterval:
|
|
296
|
+
heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
|
|
297
|
+
heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
|
|
249
298
|
heartbeatModel: heartbeatModel.trim() || null,
|
|
299
|
+
heartbeatPrompt: heartbeatPrompt.trim() || null,
|
|
250
300
|
}
|
|
251
301
|
if (editing) {
|
|
252
302
|
await updateAgent(editing.id, data)
|
|
@@ -330,8 +380,7 @@ export function AgentSheet() {
|
|
|
330
380
|
|
|
331
381
|
// Whether this provider needs a connection test before saving.
|
|
332
382
|
// Only CLI providers (no remote connection) skip the test.
|
|
333
|
-
const
|
|
334
|
-
const needsTest = !providerNeedsKey && !CLI_ONLY_PROVIDERS.has(provider)
|
|
383
|
+
const needsTest = !providerNeedsKey && !NON_LANGGRAPH_PROVIDER_IDS.has(provider)
|
|
335
384
|
|
|
336
385
|
const [saving, setSaving] = useState(false)
|
|
337
386
|
|
|
@@ -451,6 +500,46 @@ export function AgentSheet() {
|
|
|
451
500
|
<p className="text-[11px] text-text-3/70 mt-1.5">Press Enter or comma to add. Other agents see these when deciding delegation.</p>
|
|
452
501
|
</div>}
|
|
453
502
|
|
|
503
|
+
{/* Project */}
|
|
504
|
+
{Object.keys(projects).length > 0 && (
|
|
505
|
+
<div className="mb-8">
|
|
506
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
507
|
+
Project <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
508
|
+
</label>
|
|
509
|
+
<select
|
|
510
|
+
value={projectId || ''}
|
|
511
|
+
onChange={(e) => setProjectId(e.target.value || undefined)}
|
|
512
|
+
className={inputClass}
|
|
513
|
+
style={{ fontFamily: 'inherit' }}
|
|
514
|
+
>
|
|
515
|
+
<option value="">None</option>
|
|
516
|
+
{Object.values(projects).map((p) => (
|
|
517
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
518
|
+
))}
|
|
519
|
+
</select>
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
|
|
523
|
+
{/* Thinking Level */}
|
|
524
|
+
<div className="mb-8">
|
|
525
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
526
|
+
Thinking Level <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
527
|
+
</label>
|
|
528
|
+
<select
|
|
529
|
+
value={thinkingLevel}
|
|
530
|
+
onChange={(e) => setThinkingLevel(e.target.value as typeof thinkingLevel)}
|
|
531
|
+
className={inputClass}
|
|
532
|
+
style={{ fontFamily: 'inherit' }}
|
|
533
|
+
>
|
|
534
|
+
<option value="">None (default)</option>
|
|
535
|
+
<option value="minimal">Minimal — Direct and concise</option>
|
|
536
|
+
<option value="low">Low — Brief reasoning</option>
|
|
537
|
+
<option value="medium">Medium — Moderate analysis</option>
|
|
538
|
+
<option value="high">High — Deep, thorough reasoning</option>
|
|
539
|
+
</select>
|
|
540
|
+
<p className="text-[11px] text-text-3/70 mt-1.5">Controls reasoning depth. Anthropic models use extended thinking; OpenAI o-series uses reasoning_effort. Others get system prompt guidance.</p>
|
|
541
|
+
</div>
|
|
542
|
+
|
|
454
543
|
{/* Heartbeat Configuration */}
|
|
455
544
|
<div className="mb-8">
|
|
456
545
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -467,14 +556,16 @@ export function AgentSheet() {
|
|
|
467
556
|
<div className="space-y-4 mt-3">
|
|
468
557
|
<div>
|
|
469
558
|
<label className="block text-[12px] text-text-3/70 mb-1.5">Interval</label>
|
|
470
|
-
<
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
onChange={(e) => setHeartbeatInterval(e.target.value)}
|
|
474
|
-
placeholder="30s, 5m, 1h (default: 30m)"
|
|
559
|
+
<select
|
|
560
|
+
value={heartbeatIntervalSec}
|
|
561
|
+
onChange={(e) => setHeartbeatIntervalSec(e.target.value)}
|
|
475
562
|
className={inputClass}
|
|
476
|
-
|
|
477
|
-
|
|
563
|
+
>
|
|
564
|
+
<option value="">Default (30m)</option>
|
|
565
|
+
{HB_PRESETS.map((sec) => (
|
|
566
|
+
<option key={sec} value={String(sec)}>{formatHbDuration(sec)}</option>
|
|
567
|
+
))}
|
|
568
|
+
</select>
|
|
478
569
|
</div>
|
|
479
570
|
<div>
|
|
480
571
|
<label className="block text-[12px] text-text-3/70 mb-1.5">Model override <span className="text-text-3/50">(optional, cheaper model)</span></label>
|
|
@@ -487,6 +578,17 @@ export function AgentSheet() {
|
|
|
487
578
|
style={{ fontFamily: 'inherit' }}
|
|
488
579
|
/>
|
|
489
580
|
</div>
|
|
581
|
+
<div>
|
|
582
|
+
<label className="block text-[12px] text-text-3/70 mb-1.5">Instructions <span className="text-text-3/50">(what to do each tick)</span></label>
|
|
583
|
+
<textarea
|
|
584
|
+
value={heartbeatPrompt}
|
|
585
|
+
onChange={(e) => setHeartbeatPrompt(e.target.value)}
|
|
586
|
+
placeholder="Describe what this agent should do during heartbeat ticks..."
|
|
587
|
+
rows={4}
|
|
588
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
589
|
+
style={{ fontFamily: 'inherit' }}
|
|
590
|
+
/>
|
|
591
|
+
</div>
|
|
490
592
|
</div>
|
|
491
593
|
)}
|
|
492
594
|
<p className="text-[11px] text-text-3/70 mt-1.5">Periodic check-in runs on idle sessions using this agent. Processes pending events and monitors status.</p>
|
|
@@ -12,13 +12,15 @@ import { ChatHeader } from './chat-header'
|
|
|
12
12
|
import { DevServerBar } from './dev-server-bar'
|
|
13
13
|
import { MessageList } from './message-list'
|
|
14
14
|
import { SessionDebugPanel } from './session-debug-panel'
|
|
15
|
+
import { VoiceOverlay } from './voice-overlay'
|
|
16
|
+
import { useVoiceConversation } from '@/hooks/use-voice-conversation'
|
|
15
17
|
import { ChatInput } from '@/components/input/chat-input'
|
|
16
18
|
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
17
19
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
18
20
|
import { speak } from '@/lib/tts'
|
|
19
21
|
|
|
20
22
|
const PROMPT_SUGGESTIONS = [
|
|
21
|
-
{ text: '
|
|
23
|
+
{ text: 'What can you help me with?', icon: 'book', gradient: 'from-[#6366F1]/10 to-[#818CF8]/5' },
|
|
22
24
|
{ text: 'Help me set up a new connector', icon: 'link', gradient: 'from-[#EC4899]/10 to-[#F472B6]/5' },
|
|
23
25
|
{ text: 'Create a new agent for me', icon: 'bot', gradient: 'from-[#34D399]/10 to-[#6EE7B7]/5' },
|
|
24
26
|
{ text: 'Schedule a recurring task', icon: 'check', gradient: 'from-[#F59E0B]/10 to-[#FBBF24]/5' },
|
|
@@ -43,6 +45,12 @@ export function ChatArea() {
|
|
|
43
45
|
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
|
|
44
46
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
45
47
|
|
|
48
|
+
const voice = useVoiceConversation()
|
|
49
|
+
const handleVoiceToggle = useCallback(() => {
|
|
50
|
+
if (voice.active) voice.stop()
|
|
51
|
+
else voice.start()
|
|
52
|
+
}, [voice])
|
|
53
|
+
|
|
46
54
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
47
55
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
48
56
|
const [confirmClear, setConfirmClear] = useState(false)
|
|
@@ -251,6 +259,9 @@ export function ChatArea() {
|
|
|
251
259
|
onBack={handleBack}
|
|
252
260
|
browserActive={browserActive}
|
|
253
261
|
onStopBrowser={handleStopBrowser}
|
|
262
|
+
voiceActive={voice.active}
|
|
263
|
+
voiceSupported={voice.supported}
|
|
264
|
+
onVoiceToggle={handleVoiceToggle}
|
|
254
265
|
/>
|
|
255
266
|
)}
|
|
256
267
|
{!isDesktop && (
|
|
@@ -262,6 +273,9 @@ export function ChatArea() {
|
|
|
262
273
|
mobile
|
|
263
274
|
browserActive={browserActive}
|
|
264
275
|
onStopBrowser={handleStopBrowser}
|
|
276
|
+
voiceActive={voice.active}
|
|
277
|
+
voiceSupported={voice.supported}
|
|
278
|
+
onVoiceToggle={handleVoiceToggle}
|
|
265
279
|
/>
|
|
266
280
|
)}
|
|
267
281
|
<DevServerBar status={devServerStatus} onStop={handleStopDevServer} />
|
|
@@ -320,6 +334,15 @@ export function ChatArea() {
|
|
|
320
334
|
<MessageList messages={messages} streaming={streamingForThisSession} />
|
|
321
335
|
)}
|
|
322
336
|
|
|
337
|
+
{voice.active && (
|
|
338
|
+
<VoiceOverlay
|
|
339
|
+
state={voice.state}
|
|
340
|
+
interimText={voice.interimText}
|
|
341
|
+
transcript={voice.transcript}
|
|
342
|
+
onStop={voice.stop}
|
|
343
|
+
/>
|
|
344
|
+
)}
|
|
345
|
+
|
|
323
346
|
<SessionDebugPanel
|
|
324
347
|
messages={messages}
|
|
325
348
|
open={debugOpen}
|
|
@@ -348,7 +371,7 @@ export function ChatArea() {
|
|
|
348
371
|
)}
|
|
349
372
|
{!isMainChat && (
|
|
350
373
|
<DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDelete(true) }}>
|
|
351
|
-
Delete
|
|
374
|
+
Delete Chat
|
|
352
375
|
</DropdownItem>
|
|
353
376
|
)}
|
|
354
377
|
</Dropdown>
|
|
@@ -356,7 +379,7 @@ export function ChatArea() {
|
|
|
356
379
|
<ConfirmDialog
|
|
357
380
|
open={confirmClear}
|
|
358
381
|
title="Clear History"
|
|
359
|
-
message="This will delete all messages in this
|
|
382
|
+
message="This will delete all messages in this chat. This cannot be undone."
|
|
360
383
|
confirmLabel="Clear"
|
|
361
384
|
danger
|
|
362
385
|
onConfirm={handleClear}
|
|
@@ -364,7 +387,7 @@ export function ChatArea() {
|
|
|
364
387
|
/>
|
|
365
388
|
<ConfirmDialog
|
|
366
389
|
open={confirmDelete}
|
|
367
|
-
title="Delete
|
|
390
|
+
title="Delete Chat"
|
|
368
391
|
message={`Delete "${session.name}"? This cannot be undone.`}
|
|
369
392
|
confirmLabel="Delete"
|
|
370
393
|
danger
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useMemo } from 'react'
|
|
3
|
+
import { useEffect, useState, useMemo, useRef } from 'react'
|
|
4
4
|
import type { Session } from '@/types'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
@@ -18,6 +18,16 @@ function shortPath(p: string): string {
|
|
|
18
18
|
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function formatDuration(sec: number): string {
|
|
22
|
+
if (sec >= 3600) {
|
|
23
|
+
const h = Math.floor(sec / 3600)
|
|
24
|
+
const m = Math.floor((sec % 3600) / 60)
|
|
25
|
+
return m > 0 ? `${h}h${m}m` : `${h}h`
|
|
26
|
+
}
|
|
27
|
+
if (sec >= 60) return `${Math.floor(sec / 60)}m`
|
|
28
|
+
return `${sec}s`
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const PROVIDER_LABELS: Record<string, string> = {
|
|
22
32
|
'claude-cli': 'CLI',
|
|
23
33
|
openai: 'OpenAI',
|
|
@@ -34,9 +44,12 @@ interface Props {
|
|
|
34
44
|
mobile?: boolean
|
|
35
45
|
browserActive?: boolean
|
|
36
46
|
onStopBrowser?: () => void
|
|
47
|
+
onVoiceToggle?: () => void
|
|
48
|
+
voiceActive?: boolean
|
|
49
|
+
voiceSupported?: boolean
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser }: Props) {
|
|
52
|
+
export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported }: Props) {
|
|
40
53
|
const ttsEnabled = useChatStore((s) => s.ttsEnabled)
|
|
41
54
|
const toggleTts = useChatStore((s) => s.toggleTts)
|
|
42
55
|
const debugOpen = useChatStore((s) => s.debugOpen)
|
|
@@ -49,6 +62,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
49
62
|
const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
|
|
50
63
|
const appSettings = useAppStore((s) => s.appSettings)
|
|
51
64
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
65
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
52
66
|
const connectors = useAppStore((s) => s.connectors)
|
|
53
67
|
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
54
68
|
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
@@ -58,6 +72,8 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
58
72
|
const modelName = session.model || agent?.model || ''
|
|
59
73
|
const [copied, setCopied] = useState(false)
|
|
60
74
|
const [heartbeatSaving, setHeartbeatSaving] = useState(false)
|
|
75
|
+
const [hbDropdownOpen, setHbDropdownOpen] = useState(false)
|
|
76
|
+
const hbDropdownRef = useRef<HTMLDivElement>(null)
|
|
61
77
|
const [mainLoopSaving, setMainLoopSaving] = useState(false)
|
|
62
78
|
const [mainLoopError, setMainLoopError] = useState('')
|
|
63
79
|
const [mainLoopNotice, setMainLoopNotice] = useState('')
|
|
@@ -102,11 +118,53 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
102
118
|
setTimeout(() => setCopied(false), 2000)
|
|
103
119
|
}
|
|
104
120
|
|
|
105
|
-
const heartbeatEnabled = session.heartbeatEnabled !== false
|
|
106
121
|
const heartbeatSupported = (session.tools?.length ?? 0) > 0
|
|
107
122
|
const loopIsOngoing = appSettings.loopMode === 'ongoing'
|
|
108
|
-
const
|
|
109
|
-
|
|
123
|
+
const { heartbeatEnabled, heartbeatIntervalSec, heartbeatExplicitOptIn } = useMemo(() => {
|
|
124
|
+
// Resolve through the same cascade as the backend: settings → agent → session
|
|
125
|
+
const parseDur = (v: unknown): number | null => {
|
|
126
|
+
if (v === null || v === undefined) return null
|
|
127
|
+
if (typeof v === 'number') return Number.isFinite(v) ? Math.max(0, Math.min(86400, Math.trunc(v))) : null
|
|
128
|
+
if (typeof v !== 'string') return null
|
|
129
|
+
const t = v.trim().toLowerCase()
|
|
130
|
+
if (!t) return null
|
|
131
|
+
const n = Number(t)
|
|
132
|
+
if (Number.isFinite(n)) return Math.max(0, Math.min(86400, Math.trunc(n)))
|
|
133
|
+
const m = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/)
|
|
134
|
+
if (!m || (!m[1] && !m[2] && !m[3])) return null
|
|
135
|
+
const total = (m[1] ? parseInt(m[1]) * 3600 : 0) + (m[2] ? parseInt(m[2]) * 60 : 0) + (m[3] ? parseInt(m[3]) : 0)
|
|
136
|
+
return Math.max(0, Math.min(86400, total))
|
|
137
|
+
}
|
|
138
|
+
const resolveFrom = (obj: Record<string, any>): number | null => {
|
|
139
|
+
const dur = parseDur(obj.heartbeatInterval)
|
|
140
|
+
if (dur !== null) return dur
|
|
141
|
+
const sec = parseDur(obj.heartbeatIntervalSec)
|
|
142
|
+
if (sec !== null) return sec
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
// Global defaults
|
|
146
|
+
let sec = resolveFrom(appSettings as Record<string, any>) ?? 1800
|
|
147
|
+
let enabled = sec > 0
|
|
148
|
+
let explicitOptIn = false
|
|
149
|
+
// Agent layer
|
|
150
|
+
if (agent) {
|
|
151
|
+
if (agent.heartbeatEnabled === false) enabled = false
|
|
152
|
+
if (agent.heartbeatEnabled === true) { enabled = true; explicitOptIn = true }
|
|
153
|
+
sec = resolveFrom(agent as Record<string, any>) ?? sec
|
|
154
|
+
}
|
|
155
|
+
// Session layer — only applies for non-agent chats (agent chats save directly to agent)
|
|
156
|
+
if (!agent) {
|
|
157
|
+
if (session.heartbeatEnabled === false) enabled = false
|
|
158
|
+
if (session.heartbeatEnabled === true) { enabled = true; explicitOptIn = true }
|
|
159
|
+
sec = resolveFrom(session as Record<string, any>) ?? sec
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
heartbeatEnabled: enabled && sec > 0,
|
|
163
|
+
heartbeatIntervalSec: sec,
|
|
164
|
+
heartbeatExplicitOptIn: explicitOptIn,
|
|
165
|
+
}
|
|
166
|
+
}, [appSettings, agent, session])
|
|
167
|
+
const heartbeatWillRun = heartbeatEnabled && (loopIsOngoing || heartbeatExplicitOptIn)
|
|
110
168
|
const isMainSession = session.name === '__main__'
|
|
111
169
|
const missionState = session.mainLoopState || {}
|
|
112
170
|
const missionPaused = missionState.paused === true
|
|
@@ -119,23 +177,40 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
119
177
|
if (!heartbeatSupported || heartbeatSaving) return
|
|
120
178
|
setHeartbeatSaving(true)
|
|
121
179
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
180
|
+
const next = !heartbeatEnabled
|
|
181
|
+
if (session.agentId) {
|
|
182
|
+
await api('PUT', `/agents/${session.agentId}`, { heartbeatEnabled: next })
|
|
183
|
+
// Clear any stale session-level override so the agent value wins
|
|
184
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: null })
|
|
185
|
+
await Promise.all([loadAgents(), loadSessions()])
|
|
186
|
+
} else {
|
|
187
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: next })
|
|
188
|
+
await loadSessions()
|
|
189
|
+
}
|
|
124
190
|
} finally {
|
|
125
191
|
setHeartbeatSaving(false)
|
|
126
192
|
}
|
|
127
193
|
}
|
|
128
194
|
|
|
129
|
-
const
|
|
195
|
+
const handleSelectHeartbeatInterval = async (sec: number) => {
|
|
130
196
|
if (!heartbeatSupported || heartbeatSaving) return
|
|
131
|
-
|
|
132
|
-
const current = heartbeatIntervalSec
|
|
133
|
-
const idx = presets.indexOf(current)
|
|
134
|
-
const next = idx === -1 ? 120 : presets[(idx + 1) % presets.length]
|
|
197
|
+
setHbDropdownOpen(false)
|
|
135
198
|
setHeartbeatSaving(true)
|
|
136
199
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
200
|
+
if (session.agentId) {
|
|
201
|
+
// Save to agent with both formats so the cascade resolves correctly
|
|
202
|
+
await api('PUT', `/agents/${session.agentId}`, {
|
|
203
|
+
heartbeatInterval: formatDuration(sec),
|
|
204
|
+
heartbeatIntervalSec: sec,
|
|
205
|
+
heartbeatEnabled: true,
|
|
206
|
+
})
|
|
207
|
+
// Clear stale session-level overrides
|
|
208
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatIntervalSec: null, heartbeatEnabled: null })
|
|
209
|
+
await Promise.all([loadAgents(), loadSessions()])
|
|
210
|
+
} else {
|
|
211
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatIntervalSec: sec, heartbeatEnabled: true })
|
|
212
|
+
await loadSessions()
|
|
213
|
+
}
|
|
139
214
|
} finally {
|
|
140
215
|
setHeartbeatSaving(false)
|
|
141
216
|
}
|
|
@@ -193,6 +268,15 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
193
268
|
void postMainLoopAction('clear_events')
|
|
194
269
|
}
|
|
195
270
|
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!hbDropdownOpen) return
|
|
273
|
+
const handler = (e: MouseEvent) => {
|
|
274
|
+
if (hbDropdownRef.current && !hbDropdownRef.current.contains(e.target as Node)) setHbDropdownOpen(false)
|
|
275
|
+
}
|
|
276
|
+
document.addEventListener('mousedown', handler)
|
|
277
|
+
return () => document.removeEventListener('mousedown', handler)
|
|
278
|
+
}, [hbDropdownOpen])
|
|
279
|
+
|
|
196
280
|
useEffect(() => {
|
|
197
281
|
if (session.name.startsWith('connector:')) {
|
|
198
282
|
void loadConnectors()
|
|
@@ -298,7 +382,16 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
298
382
|
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
299
383
|
</svg>
|
|
300
384
|
</IconButton>
|
|
301
|
-
|
|
385
|
+
{voiceSupported && onVoiceToggle && (
|
|
386
|
+
<IconButton onClick={onVoiceToggle} active={voiceActive} aria-label="Toggle voice conversation">
|
|
387
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
388
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
|
389
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
390
|
+
<line x1="12" x2="12" y1="19" y2="22" />
|
|
391
|
+
</svg>
|
|
392
|
+
</IconButton>
|
|
393
|
+
)}
|
|
394
|
+
<IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} aria-label="Chat menu">
|
|
302
395
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
303
396
|
<circle cx="12" cy="6" r="1" />
|
|
304
397
|
<circle cx="12" cy="12" r="1" />
|
|
@@ -320,25 +413,44 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
320
413
|
onClick={handleToggleHeartbeat}
|
|
321
414
|
disabled={heartbeatSaving}
|
|
322
415
|
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
323
|
-
${
|
|
324
|
-
title={
|
|
416
|
+
${heartbeatWillRun ? 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400' : 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'}`}
|
|
417
|
+
title={heartbeatWillRun ? 'Toggle heartbeat' : !heartbeatEnabled ? 'Heartbeat disabled — click to enable' : 'Heartbeat enabled but paused (bounded loop mode, no explicit opt-in)'}
|
|
325
418
|
>
|
|
326
|
-
<span className={`w-1.5 h-1.5 rounded-full ${
|
|
419
|
+
<span className={`w-1.5 h-1.5 rounded-full ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/40'}`} />
|
|
327
420
|
<span className="text-[11px] font-600">
|
|
328
|
-
HB {
|
|
421
|
+
HB {heartbeatWillRun ? 'On' : 'Off'}
|
|
329
422
|
</span>
|
|
330
|
-
{!loopIsOngoing && (
|
|
423
|
+
{heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
|
|
331
424
|
<span className="text-[10px] text-text-3/50">(bounded)</span>
|
|
332
425
|
)}
|
|
333
426
|
</button>
|
|
334
|
-
<
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
427
|
+
<div className="relative" ref={hbDropdownRef}>
|
|
428
|
+
<button
|
|
429
|
+
onClick={() => setHbDropdownOpen((o) => !o)}
|
|
430
|
+
disabled={heartbeatSaving}
|
|
431
|
+
className="flex items-center gap-1 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
|
|
432
|
+
title="Set heartbeat interval"
|
|
433
|
+
>
|
|
434
|
+
<span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
|
|
435
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/50">
|
|
436
|
+
<polyline points="6 9 12 15 18 9" />
|
|
437
|
+
</svg>
|
|
438
|
+
</button>
|
|
439
|
+
{hbDropdownOpen && (
|
|
440
|
+
<div className="absolute top-full left-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[80px]">
|
|
441
|
+
{[30, 60, 120, 300, 600, 1800, 3600].map((sec) => (
|
|
442
|
+
<button
|
|
443
|
+
key={sec}
|
|
444
|
+
onClick={() => handleSelectHeartbeatInterval(sec)}
|
|
445
|
+
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
|
|
446
|
+
${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
|
|
447
|
+
>
|
|
448
|
+
{formatDuration(sec)}
|
|
449
|
+
</button>
|
|
450
|
+
))}
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
453
|
+
</div>
|
|
342
454
|
</>
|
|
343
455
|
)}
|
|
344
456
|
{isMainSession && (
|
|
@@ -467,7 +579,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
467
579
|
<button
|
|
468
580
|
onClick={onStopBrowser}
|
|
469
581
|
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#3B82F6]/10 hover:bg-[#F43F5E]/15 transition-colors cursor-pointer group"
|
|
470
|
-
title="Stop browser
|
|
582
|
+
title="Stop browser"
|
|
471
583
|
>
|
|
472
584
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-[#3B82F6] group-hover:text-[#F43F5E]">
|
|
473
585
|
<rect x="3" y="3" width="18" height="14" rx="2" />
|
|
@@ -14,6 +14,8 @@ const TOOL_COLORS: Record<string, string> = {
|
|
|
14
14
|
delete_file: '#EF4444',
|
|
15
15
|
edit_file: '#10B981',
|
|
16
16
|
send_file: '#10B981',
|
|
17
|
+
create_document: '#10B981',
|
|
18
|
+
create_spreadsheet: '#10B981',
|
|
17
19
|
web_search: '#3B82F6',
|
|
18
20
|
web_fetch: '#3B82F6',
|
|
19
21
|
delegate_to_agent: '#6366F1',
|
|
@@ -60,6 +62,8 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
60
62
|
delete_file: 'Delete File',
|
|
61
63
|
edit_file: 'Edit File',
|
|
62
64
|
send_file: 'Send File',
|
|
65
|
+
create_document: 'Create Document',
|
|
66
|
+
create_spreadsheet: 'Create Spreadsheet',
|
|
63
67
|
web_search: 'Web Search',
|
|
64
68
|
web_fetch: 'Web Fetch',
|
|
65
69
|
claude_code: 'Claude Code',
|
|
@@ -79,7 +83,7 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
79
83
|
manage_documents: 'Documents',
|
|
80
84
|
manage_webhooks: 'Webhooks',
|
|
81
85
|
manage_connectors: 'Connectors',
|
|
82
|
-
manage_sessions: '
|
|
86
|
+
manage_sessions: 'Chats',
|
|
83
87
|
memory: 'Memory',
|
|
84
88
|
browser: 'Browser',
|
|
85
89
|
}
|
|
@@ -94,6 +98,8 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
94
98
|
delete_file: 'Delete files or directories (when explicitly enabled)',
|
|
95
99
|
edit_file: 'Edit existing files with find-and-replace',
|
|
96
100
|
send_file: 'Send files to the user (images, PDFs, videos, documents, etc.)',
|
|
101
|
+
create_document: 'Render markdown content into PDF, HTML, or image',
|
|
102
|
+
create_spreadsheet: 'Create Excel or CSV files from structured data',
|
|
97
103
|
web_search: 'Search the web for information',
|
|
98
104
|
web_fetch: 'Fetch and read web page content',
|
|
99
105
|
claude_code: 'Enable delegation to Claude Code CLI',
|
|
@@ -103,7 +109,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
103
109
|
delegate_to_claude_code: 'Delegate complex coding tasks to Claude Code',
|
|
104
110
|
delegate_to_codex_cli: 'Delegate complex coding tasks to Codex CLI',
|
|
105
111
|
delegate_to_opencode_cli: 'Delegate complex coding tasks to OpenCode CLI',
|
|
106
|
-
whoami_tool: 'Reveal the current
|
|
112
|
+
whoami_tool: 'Reveal the current agent and chat context',
|
|
107
113
|
connector_message_tool: 'Send proactive outbound messages via running connectors',
|
|
108
114
|
search_history_tool: 'Search chat history for relevant prior context',
|
|
109
115
|
manage_tasks: 'Create, update, and manage tasks on the board',
|
|
@@ -113,7 +119,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
113
119
|
manage_documents: 'Upload and search indexed documents',
|
|
114
120
|
manage_webhooks: 'Register and manage inbound webhooks',
|
|
115
121
|
manage_connectors: 'Manage chat platform connectors (Slack, Discord, etc.)',
|
|
116
|
-
manage_sessions: 'Create and manage
|
|
122
|
+
manage_sessions: 'Create and manage agent chats',
|
|
117
123
|
memory: 'Store and recall information across conversations',
|
|
118
124
|
browser: 'Browse the web, take screenshots, and interact with pages',
|
|
119
125
|
}
|