@swarmclawai/swarmclaw 0.6.7 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +24 -6
  2. package/package.json +1 -1
  3. package/src/app/api/agents/route.ts +1 -0
  4. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  5. package/src/app/api/eval/run/route.ts +37 -0
  6. package/src/app/api/eval/scenarios/route.ts +24 -0
  7. package/src/app/api/eval/suite/route.ts +29 -0
  8. package/src/app/api/memory/graph/route.ts +46 -0
  9. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  10. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  11. package/src/app/api/souls/[id]/route.ts +65 -0
  12. package/src/app/api/souls/route.ts +70 -0
  13. package/src/app/api/tasks/[id]/route.ts +5 -0
  14. package/src/app/api/tasks/route.ts +2 -0
  15. package/src/app/api/usage/route.ts +9 -2
  16. package/src/cli/index.js +24 -0
  17. package/src/components/agents/agent-sheet.tsx +27 -6
  18. package/src/components/agents/soul-library-picker.tsx +84 -13
  19. package/src/components/chat/activity-moment.tsx +2 -0
  20. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  21. package/src/components/chat/message-list.tsx +19 -3
  22. package/src/components/chat/session-debug-panel.tsx +106 -84
  23. package/src/components/chat/task-approval-card.tsx +78 -0
  24. package/src/components/chat/tool-call-bubble.tsx +3 -0
  25. package/src/components/connectors/connector-sheet.tsx +8 -1
  26. package/src/components/home/home-view.tsx +39 -15
  27. package/src/components/layout/app-layout.tsx +18 -2
  28. package/src/components/memory/memory-browser.tsx +73 -45
  29. package/src/components/memory/memory-graph-view.tsx +203 -0
  30. package/src/components/plugins/plugin-list.tsx +1 -1
  31. package/src/components/schedules/schedule-sheet.tsx +9 -2
  32. package/src/components/shared/hint-tip.tsx +31 -0
  33. package/src/components/shared/settings/section-runtime-loop.tsx +5 -4
  34. package/src/components/tasks/approvals-panel.tsx +120 -0
  35. package/src/components/usage/metrics-dashboard.tsx +25 -3
  36. package/src/lib/server/chat-execution.ts +96 -12
  37. package/src/lib/server/chatroom-helpers.ts +63 -5
  38. package/src/lib/server/chatroom-orchestration.ts +74 -0
  39. package/src/lib/server/context-manager.ts +132 -50
  40. package/src/lib/server/daemon-state.ts +70 -1
  41. package/src/lib/server/eval/runner.ts +126 -0
  42. package/src/lib/server/eval/scenarios.ts +218 -0
  43. package/src/lib/server/eval/scorer.ts +96 -0
  44. package/src/lib/server/eval/store.ts +37 -0
  45. package/src/lib/server/eval/types.ts +48 -0
  46. package/src/lib/server/execution-log.ts +12 -8
  47. package/src/lib/server/guardian.ts +34 -0
  48. package/src/lib/server/heartbeat-service.ts +53 -1
  49. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  50. package/src/lib/server/link-understanding.ts +55 -0
  51. package/src/lib/server/main-agent-loop.ts +114 -15
  52. package/src/lib/server/memory-db.ts +18 -7
  53. package/src/lib/server/mmr.ts +73 -0
  54. package/src/lib/server/orchestrator-lg.ts +3 -0
  55. package/src/lib/server/plugins.ts +44 -22
  56. package/src/lib/server/query-expansion.ts +57 -0
  57. package/src/lib/server/queue.ts +27 -0
  58. package/src/lib/server/session-run-manager.ts +21 -1
  59. package/src/lib/server/session-tools/http.ts +19 -9
  60. package/src/lib/server/session-tools/index.ts +34 -0
  61. package/src/lib/server/session-tools/memory.ts +39 -11
  62. package/src/lib/server/session-tools/schedule.ts +43 -0
  63. package/src/lib/server/session-tools/web.ts +35 -11
  64. package/src/lib/server/storage.ts +12 -0
  65. package/src/lib/server/stream-agent-chat.ts +57 -8
  66. package/src/lib/server/tool-capability-policy.ts +1 -0
  67. package/src/lib/server/tool-retry.ts +62 -0
  68. package/src/lib/server/transcript-repair.ts +72 -0
  69. package/src/lib/setup-defaults.ts +1 -0
  70. package/src/lib/tool-definitions.ts +1 -0
  71. package/src/lib/validation/schemas.ts +1 -0
  72. package/src/lib/view-routes.ts +1 -0
  73. package/src/types/index.ts +34 -3
@@ -153,6 +153,8 @@ const COLLECTIONS = [
153
153
  'wallet_balance_history',
154
154
  'moderation_logs',
155
155
  'connector_health',
156
+ 'souls',
157
+ 'benchmarks',
156
158
  ] as const
157
159
 
158
160
  for (const table of COLLECTIONS) {
@@ -624,6 +626,16 @@ export function saveSchedules(s: Record<string, any>) {
624
626
  saveCollection('schedules', s)
625
627
  }
626
628
 
629
+ // --- Souls ---
630
+ export const loadSouls = () => loadCollection('souls')
631
+ export const saveSouls = (s: Parameters<typeof saveCollection>[1]) => saveCollection('souls', s)
632
+ export const deleteSoul = (id: string) => deleteCollectionItem('souls', id)
633
+
634
+ // --- Benchmarks ---
635
+ export const loadBenchmarks = () => loadCollection('benchmarks')
636
+ export const saveBenchmarks = (b: Parameters<typeof saveCollection>[1]) => saveCollection('benchmarks', b)
637
+ export const deleteBenchmark = (id: string) => deleteCollectionItem('benchmarks', id)
638
+
627
639
  // --- Tasks ---
628
640
  export function loadTasks(): Record<string, any> {
629
641
  return loadCollection('tasks')
@@ -59,6 +59,7 @@ function buildToolCapabilityLines(enabledTools: string[], opts?: { platformAssig
59
59
  if (enabledTools.includes('manage_agents')) lines.push('- I can create and configure other agents (`manage_agents`) — spin up specialists when a task calls for it.')
60
60
  if (enabledTools.includes('manage_tasks')) lines.push('- I can manage tasks (`manage_tasks`) — create plans, track progress, and stay organized over time.')
61
61
  if (enabledTools.includes('manage_schedules')) lines.push('- I can set up schedules (`manage_schedules`) for recurring work or future follow-ups.')
62
+ if (enabledTools.includes('schedule_wake')) lines.push('- I can set a conversational timer (`schedule_wake`) to remind myself to check back on something later in this chat.')
62
63
  if (enabledTools.includes('manage_documents')) lines.push('- I can store and search documents (`manage_documents`) for long-term knowledge and reference.')
63
64
  if (enabledTools.includes('manage_webhooks')) lines.push('- I can register webhooks (`manage_webhooks`) so external events can trigger my work automatically.')
64
65
  if (enabledTools.includes('manage_skills')) lines.push('- I can manage reusable skills (`manage_skills`) — building blocks I can learn and apply.')
@@ -77,12 +78,36 @@ function buildToolCapabilityLines(enabledTools: string[], opts?: { platformAssig
77
78
  return lines
78
79
  }
79
80
 
81
+ /** Detect whether a user message is a broad, high-level goal that benefits from decomposition. */
82
+ function isBroadGoal(text: string): boolean {
83
+ if (text.length < 50) return false
84
+ // Messages with code fences, file paths, or numbered steps are already structured
85
+ if (/```/.test(text)) return false
86
+ if (/\/(src|lib|app|pages|components|api)\//.test(text)) return false
87
+ if (/^\s*\d+[.)]\s/m.test(text)) return false
88
+ // Short direct questions aren't broad goals
89
+ if (text.length < 80 && text.endsWith('?')) return false
90
+ return true
91
+ }
92
+
93
+ const GOAL_DECOMPOSITION_BLOCK = [
94
+ '## Goal Decomposition',
95
+ 'When you receive a broad, open-ended goal:',
96
+ '1. Break it into 3-7 concrete, sequentially-executable subtasks before taking action.',
97
+ '2. If manage_tasks is available, create a task for each subtask to track progress.',
98
+ '3. Output your plan in a [MAIN_LOOP_PLAN] JSON line: {"steps":["step1","step2",...],"current_step":"step1"}',
99
+ '4. Execute the first subtask immediately — do not stop after planning.',
100
+ '5. After each subtask, update progress and move to the next.',
101
+ ].join('\n')
102
+
80
103
  function buildAgenticExecutionPolicy(opts: {
81
104
  enabledTools: string[]
82
105
  loopMode: 'bounded' | 'ongoing'
83
106
  heartbeatPrompt: string
84
107
  heartbeatIntervalSec: number
85
108
  platformAssignScope?: 'self' | 'all'
109
+ userMessage?: string
110
+ hasExistingPlan?: boolean
86
111
  }) {
87
112
  const hasTooling = opts.enabledTools.length > 0
88
113
  const toolLines = buildToolCapabilityLines(opts.enabledTools, { platformAssignScope: opts.platformAssignScope })
@@ -192,6 +217,8 @@ function buildAgenticExecutionPolicy(opts: {
192
217
  ? `Expected heartbeat cadence is roughly every ${opts.heartbeatIntervalSec} seconds while ongoing work is active.`
193
218
  : '',
194
219
  toolLines.length ? 'What I can do:\n' + toolLines.join('\n') : '',
220
+ // Inject goal decomposition instructions for broad goals without existing plans
221
+ (opts.userMessage && !opts.hasExistingPlan && isBroadGoal(opts.userMessage)) ? GOAL_DECOMPOSITION_BLOCK : '',
195
222
  ].filter(Boolean).join('\n')
196
223
  }
197
224
 
@@ -204,6 +231,7 @@ export interface StreamAgentChatResult {
204
231
  }
205
232
 
206
233
  export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
234
+ const startTs = Date.now()
207
235
  const { session, message, imagePath, attachedFiles, apiKey, systemPrompt, write, history, fallbackCredentialIds, signal } = opts
208
236
  const sessionToolsWithImplicitProcess = Array.from(new Set([
209
237
  ...(session.tools || []),
@@ -385,15 +413,17 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
385
413
  '- When I learn something that corrects old knowledge, update or remove the old memory',
386
414
  ].join('\n'))
387
415
 
388
- // Pre-compaction memory flush: nudge agent to persist learnings when conversation is long
416
+ // Pre-compaction memory flush & Personality Evolution: nudge agent to reflect when conversation is long
389
417
  const msgCount = history.filter(m => m.role === 'user' || m.role === 'assistant').length
390
418
  if (msgCount > 20) {
419
+ const canEditSelf = (session.tools || []).includes('manage_agents')
391
420
  stateModifierParts.push([
392
- '## Memory Flush Reminder',
393
- 'This conversation is getting long and I might lose older context soon. I should save anything',
394
- 'important I\'ve learned, decided, or discovered to my memory now things I\'d want to recall',
395
- 'in future conversations. Only what matters, not every detail. If there\'s nothing worth saving, carry on.',
396
- ].join('\n'))
421
+ '## Reflection & Consolidation Reminder',
422
+ 'This conversation is getting long and I might lose older context soon.',
423
+ '1. **Memory:** I should save anything important I\'ve learned, decided, or discovered to my memory now. Only what matters, not every detail.',
424
+ canEditSelf ? `2. **Personality Evolution:** I should reflect on this conversation. Have my boundaries, tone, or relationship with the user evolved? If so, I MUST use \`manage_agents\` (action: update, id: "${session.agentId}") to update my \`soul\` field with these new learnings.` : '',
425
+ 'If there\'s nothing worth saving or updating, carry on.',
426
+ ].filter(Boolean).join('\n'))
397
427
  }
398
428
  } catch {
399
429
  // If memory context fails to load, continue without blocking the run.
@@ -449,7 +479,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
449
479
  'web_search', 'web_fetch', 'browser', 'memory',
450
480
  'claude_code', 'codex_cli', 'opencode_cli',
451
481
  'sandbox', 'create_document', 'create_spreadsheet', 'http_request', 'git', 'wallet',
452
- 'manage_agents', 'manage_tasks', 'manage_schedules', 'manage_skills',
482
+ 'manage_agents', 'manage_tasks', 'manage_schedules', 'schedule_wake', 'manage_skills',
453
483
  'manage_documents', 'manage_webhooks', 'manage_connectors', 'manage_sessions', 'manage_secrets',
454
484
  ]
455
485
  const disabled = allToolIds.filter((t) => !enabledSet.has(t))
@@ -475,6 +505,9 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
475
505
  )
476
506
  }
477
507
 
508
+ // Check for existing plan in mainLoopState to skip decomposition injection
509
+ const hasExistingPlan = Array.isArray(session.mainLoopState?.planSteps) && session.mainLoopState.planSteps.length > 0
510
+
478
511
  stateModifierParts.push(
479
512
  buildAgenticExecutionPolicy({
480
513
  enabledTools: sessionToolsWithImplicitProcess,
@@ -482,10 +515,12 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
482
515
  heartbeatPrompt,
483
516
  heartbeatIntervalSec,
484
517
  platformAssignScope: agentPlatformAssignScope,
518
+ userMessage: message,
519
+ hasExistingPlan,
485
520
  }),
486
521
  )
487
522
 
488
- const stateModifier = stateModifierParts.join('\n\n')
523
+ let stateModifier = stateModifierParts.join('\n\n')
489
524
 
490
525
  const { tools, cleanup } = await buildSessionTools(session.cwd, sessionToolsWithImplicitProcess, {
491
526
  agentId: session.agentId,
@@ -613,6 +648,19 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
613
648
  // Context manager failure — continue with full history
614
649
  }
615
650
 
651
+ // Context degradation warning: prepend warning to system prompt when nearing limits
652
+ try {
653
+ const { getContextDegradationWarning, estimateTokens: estTokens } = await import('./context-manager')
654
+ const sysTokens = estTokens(stateModifier)
655
+ const warning = getContextDegradationWarning(effectiveHistory, sysTokens, session.provider, session.model)
656
+ if (warning) {
657
+ stateModifierParts.unshift(warning)
658
+ stateModifier = stateModifierParts.join('\n\n')
659
+ }
660
+ } catch {
661
+ // Warning failure is non-critical
662
+ }
663
+
616
664
  // Apply context-clear boundary: slice from most recent context-clear marker
617
665
  let contextStart = 0
618
666
  for (let i = effectiveHistory.length - 1; i >= 0; i--) {
@@ -836,6 +884,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
836
884
  totalTokens,
837
885
  estimatedCost: cost,
838
886
  timestamp: Date.now(),
887
+ durationMs: Date.now() - startTs,
839
888
  }
840
889
  appendUsage(session.id, usageRecord)
841
890
  // Send usage metadata to client
@@ -53,6 +53,7 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
53
53
  manage_agents: { categories: ['platform'], concreteTools: ['manage_agents'] },
54
54
  manage_tasks: { categories: ['platform'], concreteTools: ['manage_tasks'] },
55
55
  manage_schedules: { categories: ['platform'], concreteTools: ['manage_schedules'] },
56
+ schedule_wake: { categories: ['platform'], concreteTools: ['schedule_wake'] },
56
57
  manage_skills: { categories: ['platform'], concreteTools: ['manage_skills'] },
57
58
  manage_documents: { categories: ['platform'], concreteTools: ['manage_documents'] },
58
59
  manage_webhooks: { categories: ['platform', 'network'], concreteTools: ['manage_webhooks'] },
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Structured retry with exponential backoff for transient tool failures.
3
+ */
4
+
5
+ export interface RetryOptions {
6
+ maxAttempts?: number
7
+ backoffMs?: number
8
+ retryable?: RegExp[]
9
+ }
10
+
11
+ const DEFAULT_RETRYABLE: RegExp[] = [
12
+ /timeout/i,
13
+ /ECONNRESET/i,
14
+ /ENOTFOUND/i,
15
+ /429/,
16
+ /503/,
17
+ /rate.?limit/i,
18
+ ]
19
+
20
+ const DEFAULT_MAX_ATTEMPTS = 3
21
+ const DEFAULT_BACKOFF_MS = 2000
22
+
23
+ function isRetryableError(error: string, patterns: RegExp[]): boolean {
24
+ return patterns.some((p) => p.test(error))
25
+ }
26
+
27
+ function sleep(ms: number): Promise<void> {
28
+ return new Promise((resolve) => setTimeout(resolve, ms))
29
+ }
30
+
31
+ /**
32
+ * Wraps a tool handler function with retry logic for transient failures.
33
+ * The wrapped function must return a string (tool output).
34
+ * Retries only when the returned string matches a retryable pattern
35
+ * (tool handlers typically return error strings rather than throwing).
36
+ */
37
+ export async function withRetry<TArgs>(
38
+ fn: (args: TArgs) => Promise<string>,
39
+ args: TArgs,
40
+ opts?: RetryOptions,
41
+ ): Promise<string> {
42
+ const maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS
43
+ const backoffMs = opts?.backoffMs ?? DEFAULT_BACKOFF_MS
44
+ const retryable = opts?.retryable ?? DEFAULT_RETRYABLE
45
+
46
+ let lastResult = ''
47
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
48
+ lastResult = await fn(args)
49
+
50
+ // Only retry if the result looks like a retryable error
51
+ if (attempt < maxAttempts && isRetryableError(lastResult, retryable)) {
52
+ const delay = backoffMs * Math.pow(2, attempt - 1)
53
+ console.warn(
54
+ `[tool-retry] Attempt ${attempt}/${maxAttempts} matched retryable pattern, retrying in ${delay}ms`,
55
+ )
56
+ await sleep(delay)
57
+ continue
58
+ }
59
+ return lastResult
60
+ }
61
+ return lastResult
62
+ }
@@ -0,0 +1,72 @@
1
+ import type { Message } from '@/types'
2
+
3
+ /**
4
+ * Repairs a conversation transcript by ensuring that tool events remain associated
5
+ * with their parent assistant messages during pruning or manipulation.
6
+ *
7
+ * In SwarmClaw, toolEvents are nested within the Message object, so "orphaning"
8
+ * is less of a structural risk than in OpenClaw, but we still need to ensure
9
+ * consistency during context management.
10
+ */
11
+ export function repairTranscriptConsistency(messages: Message[]): Message[] {
12
+ // SwarmClaw specific: ensure that 'system' messages like [Context Summary]
13
+ // are preserved correctly and that nested toolEvents are valid.
14
+ return messages.map(m => {
15
+ if (m.role === 'assistant' && m.toolEvents) {
16
+ // Filter out empty or malformed tool events that might cause LLM confusion
17
+ const validTools = m.toolEvents.filter(t => t.name && t.input)
18
+ if (validTools.length !== m.toolEvents.length) {
19
+ return { ...m, toolEvents: validTools }
20
+ }
21
+ }
22
+ return m
23
+ })
24
+ }
25
+
26
+ /**
27
+ * Checks for and repairs common transcript issues that cause LLM provider errors.
28
+ * (e.g. consecutive user messages, trailing assistant messages without text).
29
+ */
30
+ export function finalProviderTranscriptSanityCheck(messages: Message[]): Message[] {
31
+ if (messages.length === 0) return []
32
+
33
+ const out: Message[] = []
34
+ for (let i = 0; i < messages.length; i++) {
35
+ const m = messages[i]
36
+
37
+ // 1. Skip messages marked as suppressed
38
+ if (m.suppressed) continue
39
+
40
+ // 2. Prevent consecutive messages of same role (some providers are strict)
41
+ const prev = out.at(-1)
42
+ if (prev && prev.role === m.role) {
43
+ if (m.role === 'user') {
44
+ // Merge consecutive user messages
45
+ prev.text = `${prev.text}\n\n${m.text}`
46
+ if (m.imagePath) prev.imagePath = m.imagePath
47
+ if (m.imageUrl) prev.imageUrl = m.imageUrl
48
+ continue
49
+ } else {
50
+ // Assistant consecutive? Keep the one with tool events or the longer one
51
+ const mTools = m.toolEvents?.length || 0
52
+ const pTools = prev.toolEvents?.length || 0
53
+ if (mTools > pTools || m.text.length > prev.text.length) {
54
+ out[out.length - 1] = m
55
+ }
56
+ continue
57
+ }
58
+ }
59
+
60
+ out.push(m)
61
+ }
62
+
63
+ // 3. Ensure the transcript doesn't end with an empty assistant message
64
+ if (out.length > 0 && out.at(-1)?.role === 'assistant') {
65
+ const last = out.at(-1)!
66
+ if (!last.text.trim() && (!last.toolEvents || last.toolEvents.length === 0)) {
67
+ out.pop()
68
+ }
69
+ }
70
+
71
+ return out
72
+ }
@@ -157,6 +157,7 @@ export const STARTER_AGENT_TOOLS = [
157
157
  'manage_agents',
158
158
  'manage_tasks',
159
159
  'manage_schedules',
160
+ 'schedule_wake',
160
161
  'manage_skills',
161
162
  'manage_connectors',
162
163
  'manage_sessions',
@@ -31,6 +31,7 @@ export const PLATFORM_TOOLS: ToolDefinition[] = [
31
31
  { id: 'manage_agents', label: 'Agents', description: 'Create, edit, and delete agents' },
32
32
  { id: 'manage_tasks', label: 'Tasks', description: 'Create, edit, and delete tasks' },
33
33
  { id: 'manage_schedules', label: 'Schedules', description: 'Create, edit, and delete schedules' },
34
+ { id: 'schedule_wake', label: 'Reminders', description: 'Schedule a proactive wake event in the current chat' },
34
35
  { id: 'manage_skills', label: 'Skills', description: 'Create, edit, and delete skills' },
35
36
  { id: 'manage_documents', label: 'Documents', description: 'Upload, search, and delete indexed documents' },
36
37
  { id: 'manage_webhooks', label: 'Webhooks', description: 'Register webhooks that trigger agent workflows' },
@@ -14,6 +14,7 @@ export const AgentCreateSchema = z.object({
14
14
  capabilities: z.array(z.string()).optional().default([]),
15
15
  thinkingLevel: z.string().optional(),
16
16
  soul: z.string().optional(),
17
+ autoRecovery: z.boolean().optional().default(false),
17
18
  })
18
19
 
19
20
  export const ConnectorCreateSchema = z.object({
@@ -9,6 +9,7 @@ export const VIEW_TO_PATH: Record<AppView, string> = {
9
9
  schedules: '/schedules',
10
10
  memory: '/memory',
11
11
  tasks: '/tasks',
12
+ approvals: '/approvals',
12
13
  secrets: '/secrets',
13
14
  providers: '/providers',
14
15
  skills: '/skills',
@@ -75,6 +75,8 @@ export interface Session {
75
75
  heartbeatIntervalSec?: number | null
76
76
  heartbeatTarget?: 'last' | 'none' | string | null
77
77
  lastAutoMemoryAt?: number | null
78
+ lastHeartbeatText?: string | null
79
+ lastHeartbeatSentAt?: number | null
78
80
  mainLoopState?: {
79
81
  goal?: string | null
80
82
  goalContract?: GoalContract | null
@@ -102,6 +104,8 @@ export interface Session {
102
104
  note: string
103
105
  status?: 'idle' | 'progress' | 'blocked' | 'ok'
104
106
  }>
107
+ missionTokens?: number
108
+ missionCostUsd?: number
105
109
  followupChainCount?: number
106
110
  metaMissCount?: number
107
111
  workingMemoryNotes?: string[]
@@ -116,6 +120,11 @@ export interface Session {
116
120
  queuedCount?: number
117
121
  currentRunId?: string | null
118
122
  conversationTone?: string
123
+ emoji?: string
124
+ creature?: string
125
+ vibe?: string
126
+ theme?: string
127
+ avatar?: string
119
128
  canvasContent?: string | null
120
129
  }
121
130
 
@@ -148,6 +157,7 @@ export interface UsageRecord {
148
157
  totalTokens: number
149
158
  estimatedCost: number
150
159
  timestamp: number
160
+ durationMs?: number
151
161
  }
152
162
 
153
163
  // --- Plugin System ---
@@ -155,15 +165,27 @@ export interface UsageRecord {
155
165
  export interface PluginHooks {
156
166
  beforeAgentStart?: (ctx: { session: Session; message: string }) => Promise<void> | void
157
167
  afterAgentComplete?: (ctx: { session: Session; response: string }) => Promise<void> | void
158
- beforeToolExec?: (ctx: { toolName: string; input: any }) => Promise<any> | any
159
- afterToolExec?: (ctx: { toolName: string; input: any; output: string }) => Promise<void> | void
168
+ beforeToolExec?: (ctx: { toolName: string; input: Record<string, unknown> | null }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void
169
+ afterToolExec?: (ctx: { toolName: string; input: Record<string, unknown> | null; output: string }) => Promise<void> | void
160
170
  onMessage?: (ctx: { session: Session; message: Message }) => Promise<void> | void
171
+
172
+ // Orchestration & Swarm Hooks
173
+ onTaskComplete?: (ctx: { taskId: string; result: unknown }) => Promise<void> | void
174
+ onAgentDelegation?: (ctx: { sourceAgentId: string; targetAgentId: string; task: string }) => Promise<void> | void
175
+ }
176
+
177
+ export interface PluginToolDef {
178
+ name: string
179
+ description: string
180
+ parameters: Record<string, unknown>
181
+ execute: (args: Record<string, unknown>, ctx: { session: Session; message: string }) => Promise<string | object> | string | object
161
182
  }
162
183
 
163
184
  export interface Plugin {
164
185
  name: string
165
186
  description?: string
166
187
  hooks: PluginHooks
188
+ tools?: PluginToolDef[]
167
189
  }
168
190
 
169
191
  export interface PluginMeta {
@@ -233,6 +255,11 @@ export interface Agent {
233
255
  name: string
234
256
  description: string
235
257
  soul?: string
258
+ emoji?: string
259
+ creature?: string
260
+ vibe?: string
261
+ theme?: string
262
+ avatar?: string
236
263
  systemPrompt: string
237
264
  provider: ProviderType
238
265
  model: string
@@ -273,9 +300,12 @@ export interface Agent {
273
300
  openclawAllowedSkills?: string[]
274
301
  walletId?: string | null
275
302
  monthlyBudget?: number | null
303
+ autoRecovery?: boolean
304
+
276
305
  budgetAction?: 'warn' | 'block'
277
306
  /** Runtime-enriched: current month's spend. Populated by GET /api/agents when monthlyBudget is set. */
278
307
  monthlySpend?: number
308
+ maxFollowupChain?: number
279
309
  createdAt: number
280
310
  updatedAt: number
281
311
  }
@@ -408,7 +438,7 @@ export interface MemoryEntry {
408
438
  }
409
439
 
410
440
  export type SessionType = 'human' | 'orchestrated'
411
- export type AppView = 'home' | 'agents' | 'chatrooms' | 'schedules' | 'memory' | 'tasks' | 'secrets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'plugins' | 'usage' | 'wallets' | 'runs' | 'logs' | 'settings' | 'projects' | 'activity'
441
+ export type AppView = 'home' | 'agents' | 'chatrooms' | 'schedules' | 'memory' | 'tasks' | 'approvals' | 'secrets' | 'providers' | 'skills' | 'connectors' | 'webhooks' | 'mcp_servers' | 'knowledge' | 'plugins' | 'usage' | 'wallets' | 'runs' | 'logs' | 'settings' | 'projects' | 'activity'
412
442
 
413
443
  // --- Chatrooms ---
414
444
 
@@ -581,6 +611,7 @@ export interface AppSettings {
581
611
  legacyOrchestratorMaxTurns?: number
582
612
  ongoingLoopMaxIterations?: number
583
613
  ongoingLoopMaxRuntimeMinutes?: number
614
+ maxFollowupChain?: number
584
615
  shellCommandTimeoutSec?: number
585
616
  claudeCodeTimeoutSec?: number
586
617
  cliProcessTimeoutSec?: number