@swarmclawai/swarmclaw 0.7.1 → 0.7.2

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 (119) hide show
  1. package/README.md +85 -139
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/thread/route.ts +1 -2
  4. package/src/app/api/agents/route.ts +1 -1
  5. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  6. package/src/app/api/{sessions → chats}/[id]/main-loop/route.ts +2 -2
  7. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  8. package/src/app/api/{sessions → chats}/[id]/route.ts +4 -52
  9. package/src/app/api/{sessions → chats}/route.ts +5 -7
  10. package/src/app/api/plugins/route.ts +3 -0
  11. package/src/app/api/plugins/settings/route.ts +35 -0
  12. package/src/app/api/usage/route.ts +30 -0
  13. package/src/cli/index.js +35 -33
  14. package/src/cli/index.ts +40 -39
  15. package/src/cli/spec.js +29 -27
  16. package/src/components/agents/agent-card.tsx +1 -1
  17. package/src/components/agents/agent-chat-list.tsx +3 -3
  18. package/src/components/agents/agent-list.tsx +8 -13
  19. package/src/components/agents/agent-sheet.tsx +2 -2
  20. package/src/components/agents/cron-job-form.tsx +3 -3
  21. package/src/components/agents/inspector-panel.tsx +2 -2
  22. package/src/components/auth/setup-wizard.tsx +5 -38
  23. package/src/components/chat/chat-area.tsx +10 -14
  24. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +3 -3
  25. package/src/components/chat/chat-header.tsx +156 -73
  26. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +4 -5
  27. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  28. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  29. package/src/components/chat/message-bubble.tsx +4 -1
  30. package/src/components/chat/message-list.tsx +2 -2
  31. package/src/components/{sessions/new-session-sheet.tsx → chat/new-chat-sheet.tsx} +6 -6
  32. package/src/components/chat/session-debug-panel.tsx +1 -1
  33. package/src/components/chat/tool-request-banner.tsx +3 -3
  34. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  35. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  36. package/src/components/connectors/connector-sheet.tsx +1 -1
  37. package/src/components/home/home-view.tsx +1 -1
  38. package/src/components/layout/app-layout.tsx +23 -2
  39. package/src/components/plugins/plugin-list.tsx +475 -254
  40. package/src/components/plugins/plugin-sheet.tsx +124 -10
  41. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  42. package/src/components/shared/command-palette.tsx +0 -1
  43. package/src/components/shared/settings/section-heartbeat.tsx +1 -1
  44. package/src/components/shared/settings/section-providers.tsx +1 -1
  45. package/src/components/shared/settings/settings-page.tsx +1 -12
  46. package/src/components/usage/metrics-dashboard.tsx +73 -0
  47. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  48. package/src/lib/chat.ts +1 -1
  49. package/src/lib/{sessions.ts → chats.ts} +28 -18
  50. package/src/lib/providers/claude-cli.ts +1 -1
  51. package/src/lib/server/approvals.ts +4 -4
  52. package/src/lib/server/capability-router.ts +10 -8
  53. package/src/lib/server/chat-execution.ts +36 -105
  54. package/src/lib/server/chatroom-helpers.ts +3 -3
  55. package/src/lib/server/connectors/manager.ts +4 -4
  56. package/src/lib/server/cost.ts +34 -1
  57. package/src/lib/server/daemon-state.ts +2 -2
  58. package/src/lib/server/heartbeat-service.ts +1 -1
  59. package/src/lib/server/main-agent-loop.ts +25 -160
  60. package/src/lib/server/main-session.ts +6 -13
  61. package/src/lib/server/orchestrator-lg.ts +3 -3
  62. package/src/lib/server/orchestrator.ts +5 -5
  63. package/src/lib/server/plugins.ts +112 -4
  64. package/src/lib/server/provider-health.ts +5 -3
  65. package/src/lib/server/queue.ts +12 -10
  66. package/src/lib/server/session-run-manager.test.ts +9 -6
  67. package/src/lib/server/session-run-manager.ts +1 -3
  68. package/src/lib/server/session-tools/calendar.ts +376 -0
  69. package/src/lib/server/session-tools/canvas.ts +1 -1
  70. package/src/lib/server/session-tools/chatroom.ts +4 -2
  71. package/src/lib/server/session-tools/connector.ts +5 -2
  72. package/src/lib/server/session-tools/context.ts +7 -3
  73. package/src/lib/server/session-tools/crud.ts +14 -6
  74. package/src/lib/server/session-tools/delegate.ts +95 -8
  75. package/src/lib/server/session-tools/discovery.ts +2 -2
  76. package/src/lib/server/session-tools/edit_file.ts +4 -2
  77. package/src/lib/server/session-tools/email.ts +322 -0
  78. package/src/lib/server/session-tools/file.ts +5 -2
  79. package/src/lib/server/session-tools/git.ts +1 -1
  80. package/src/lib/server/session-tools/http.ts +1 -1
  81. package/src/lib/server/session-tools/image-gen.ts +382 -0
  82. package/src/lib/server/session-tools/index.ts +74 -49
  83. package/src/lib/server/session-tools/memory.ts +139 -2
  84. package/src/lib/server/session-tools/monitor.ts +1 -1
  85. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  86. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  87. package/src/lib/server/session-tools/platform.ts +6 -3
  88. package/src/lib/server/session-tools/plugin-creator.ts +3 -3
  89. package/src/lib/server/session-tools/replicate.ts +303 -0
  90. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  91. package/src/lib/server/session-tools/sandbox.ts +4 -2
  92. package/src/lib/server/session-tools/schedule.ts +4 -2
  93. package/src/lib/server/session-tools/session-info.ts +7 -4
  94. package/src/lib/server/session-tools/shell.ts +5 -2
  95. package/src/lib/server/session-tools/subagent.ts +2 -2
  96. package/src/lib/server/session-tools/wallet.ts +29 -2
  97. package/src/lib/server/session-tools/web.ts +44 -5
  98. package/src/lib/server/storage.ts +29 -9
  99. package/src/lib/server/stream-agent-chat.ts +72 -249
  100. package/src/lib/server/tool-aliases.ts +26 -15
  101. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  102. package/src/lib/server/tool-capability-policy.ts +32 -27
  103. package/src/lib/tool-definitions.ts +4 -0
  104. package/src/lib/validation/schemas.ts +3 -1
  105. package/src/stores/use-app-store.ts +5 -5
  106. package/src/stores/use-chat-store.ts +7 -7
  107. package/src/types/index.ts +65 -3
  108. /package/src/app/api/{sessions → chats}/[id]/browser/route.ts +0 -0
  109. /package/src/app/api/{sessions → chats}/[id]/chat/route.ts +0 -0
  110. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  111. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  112. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  113. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  114. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  115. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  116. /package/src/app/api/{sessions → chats}/[id]/messages/route.ts +0 -0
  117. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  118. /package/src/app/api/{sessions → chats}/[id]/stop/route.ts +0 -0
  119. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -8,13 +8,16 @@ export interface CapabilityPolicyBlock {
8
8
  source: 'safety' | 'policy'
9
9
  }
10
10
 
11
- export interface SessionToolPolicyDecision {
11
+ export interface PluginPolicyDecision {
12
12
  mode: CapabilityPolicyMode
13
- requestedTools: string[]
14
- enabledTools: string[]
15
- blockedTools: CapabilityPolicyBlock[]
13
+ requestedPlugins: string[]
14
+ enabledPlugins: string[]
15
+ blockedPlugins: CapabilityPolicyBlock[]
16
16
  }
17
17
 
18
+ /** @deprecated Use PluginPolicyDecision */
19
+ export type SessionToolPolicyDecision = PluginPolicyDecision
20
+
18
21
  type CapabilityCategory =
19
22
  | 'filesystem'
20
23
  | 'execution'
@@ -47,10 +50,11 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
47
50
  web_search: { categories: ['network'], concreteTools: ['web_search'] },
48
51
  web_fetch: { categories: ['network'], concreteTools: ['web_fetch'] },
49
52
  browser: { categories: ['browser', 'network'], concreteTools: ['browser', 'openclaw_browser'] },
50
- delegate: { categories: ['delegation', 'execution'], concreteTools: ['delegate', 'delegate_to_claude_code', 'delegate_to_codex_cli', 'delegate_to_opencode_cli'] },
53
+ delegate: { categories: ['delegation', 'execution'], concreteTools: ['delegate', 'delegate_to_claude_code', 'delegate_to_codex_cli', 'delegate_to_opencode_cli', 'delegate_to_gemini_cli'] },
51
54
  claude_code: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_claude_code'] },
52
55
  codex_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_codex_cli'] },
53
56
  opencode_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_opencode_cli'] },
57
+ gemini_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_gemini_cli'] },
54
58
  memory: { categories: ['memory'], concreteTools: ['memory', 'memory_tool', 'context_status', 'context_summarize'] },
55
59
  sandbox: { categories: ['execution', 'filesystem'], concreteTools: ['sandbox', 'sandbox_exec', 'sandbox_list_runtimes', 'openclaw_sandbox'] },
56
60
  git: { categories: ['execution', 'filesystem'], concreteTools: ['git'] },
@@ -144,6 +148,7 @@ function safetyMatchesTool(safetyBlocked: Set<string>, toolName: string, descrip
144
148
  if (toolName === 'claude_code' && safetyBlocked.has('delegate_to_claude_code')) return true
145
149
  if (toolName === 'codex_cli' && safetyBlocked.has('delegate_to_codex_cli')) return true
146
150
  if (toolName === 'opencode_cli' && safetyBlocked.has('delegate_to_opencode_cli')) return true
151
+ if (toolName === 'gemini_cli' && safetyBlocked.has('delegate_to_gemini_cli')) return true
147
152
  return false
148
153
  }
149
154
 
@@ -196,51 +201,51 @@ export function resolveSessionToolPolicy(
196
201
  blockedCategories,
197
202
  } = parsePolicyConfig(normalizedSettings)
198
203
 
199
- const requestedTools = Array.isArray(sessionTools)
200
- ? Array.from(new Set(sessionTools.map((tool) => normalizeName(tool)).filter(Boolean)))
204
+ const requestedPlugins = Array.isArray(sessionTools)
205
+ ? Array.from(new Set(sessionTools.map((id) => normalizeName(id)).filter(Boolean)))
201
206
  : []
202
207
 
203
- const enabledTools: string[] = []
204
- const blockedTools: CapabilityPolicyBlock[] = []
208
+ const enabledPlugins: string[] = []
209
+ const blockedPlugins: CapabilityPolicyBlock[] = []
205
210
 
206
- for (const toolName of requestedTools) {
207
- const descriptor = TOOL_DESCRIPTORS[toolName]
211
+ for (const pluginName of requestedPlugins) {
212
+ const descriptor = TOOL_DESCRIPTORS[pluginName]
208
213
 
209
- if (safetyMatchesTool(safetyBlocked, toolName, descriptor)) {
210
- blockedTools.push({ tool: toolName, reason: 'blocked by safety policy', source: 'safety' })
214
+ if (safetyMatchesTool(safetyBlocked, pluginName, descriptor)) {
215
+ blockedPlugins.push({ tool: pluginName, reason: 'blocked by safety policy', source: 'safety' })
211
216
  continue
212
217
  }
213
218
 
214
- if (policyAllowedNames.has(toolName)) {
215
- enabledTools.push(toolName)
219
+ if (policyAllowedNames.has(pluginName)) {
220
+ enabledPlugins.push(pluginName)
216
221
  continue
217
222
  }
218
223
 
219
- if (policyMatchesTool(policyBlockedNames, toolName, descriptor)) {
220
- blockedTools.push({ tool: toolName, reason: 'blocked by explicit policy rule', source: 'policy' })
224
+ if (policyMatchesTool(policyBlockedNames, pluginName, descriptor)) {
225
+ blockedPlugins.push({ tool: pluginName, reason: 'blocked by explicit policy rule', source: 'policy' })
221
226
  continue
222
227
  }
223
228
 
224
229
  const categoryReason = categoryBlockReason(blockedCategories, descriptor)
225
230
  if (categoryReason) {
226
- blockedTools.push({ tool: toolName, reason: categoryReason, source: 'policy' })
231
+ blockedPlugins.push({ tool: pluginName, reason: categoryReason, source: 'policy' })
227
232
  continue
228
233
  }
229
234
 
230
- const modeReason = modeBlocksTool(mode, toolName, descriptor)
235
+ const modeReason = modeBlocksTool(mode, pluginName, descriptor)
231
236
  if (modeReason) {
232
- blockedTools.push({ tool: toolName, reason: modeReason, source: 'policy' })
237
+ blockedPlugins.push({ tool: pluginName, reason: modeReason, source: 'policy' })
233
238
  continue
234
239
  }
235
240
 
236
- enabledTools.push(toolName)
241
+ enabledPlugins.push(pluginName)
237
242
  }
238
243
 
239
244
  return {
240
245
  mode,
241
- requestedTools,
242
- enabledTools,
243
- blockedTools,
246
+ requestedPlugins,
247
+ enabledPlugins,
248
+ blockedPlugins,
244
249
  }
245
250
  }
246
251
 
@@ -270,11 +275,11 @@ export function resolveConcreteToolPolicyBlock(
270
275
  }
271
276
 
272
277
  if (mappedTool) {
273
- const blockedRoot = decision.blockedTools.find((entry) => entry.tool === mappedTool)
278
+ const blockedRoot = decision.blockedPlugins.find((entry) => entry.tool === mappedTool)
274
279
  if (blockedRoot) return blockedRoot.reason
275
280
 
276
- const enabledRoot = decision.enabledTools.includes(mappedTool)
277
- if (!enabledRoot) return `tool family "${mappedTool}" is not enabled for this session`
281
+ const enabledRoot = decision.enabledPlugins.includes(mappedTool)
282
+ if (!enabledRoot) return `plugin family "${mappedTool}" is not enabled for this chat`
278
283
  }
279
284
 
280
285
  return null
@@ -25,6 +25,10 @@ export const AVAILABLE_TOOLS: ToolDefinition[] = [
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, write, and test custom SwarmClaw plugins dynamically' },
27
27
  { id: 'sample_ui', label: 'Sample UI', description: 'Demonstration of dynamic UI injection into Sidebar and Chat Header' },
28
+ { id: 'image_gen', label: 'Image Generation', description: 'Generate images from text prompts using OpenAI, Stability AI, Replicate, fal.ai, and more' },
29
+ { id: 'email', label: 'Email', description: 'Send emails via SMTP with plain text and HTML support' },
30
+ { id: 'calendar', label: 'Calendar', description: 'Manage Google Calendar or Outlook events — list, create, update, delete' },
31
+ { id: 'replicate', label: 'Replicate', description: 'Run any AI model on Replicate — image generation, LLMs, audio, video, and more' },
28
32
  ]
29
33
 
30
34
  /**
@@ -10,7 +10,9 @@ export const AgentCreateSchema = z.object({
10
10
  apiEndpoint: z.string().nullable().optional().default(null),
11
11
  isOrchestrator: z.boolean().optional().default(false),
12
12
  subAgentIds: z.array(z.string()).optional().default([]),
13
- tools: z.array(z.string()).optional().default([]),
13
+ plugins: z.array(z.string()).optional().default([]),
14
+ /** @deprecated Use plugins */
15
+ tools: z.array(z.string()).optional(),
14
16
  capabilities: z.array(z.string()).optional().default([]),
15
17
  thinkingLevel: z.string().optional(),
16
18
  soul: z.string().optional(),
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { create } from 'zustand'
4
4
  import type { Sessions, Session, NetworkInfo, Directory, ProviderInfo, Credentials, Agent, Schedule, AppView, BoardTask, AppSettings, OrchestratorSecret, ProviderConfig, Skill, Connector, Webhook, McpServerConfig, PluginMeta, Project, FleetFilter, ActivityEntry, AppNotification, ApprovalRequest } from '../types'
5
- import { fetchSessions, fetchDirs, fetchProviders, fetchCredentials } from '../lib/sessions'
5
+ import { fetchChats, fetchDirs, fetchProviders, fetchCredentials } from '../lib/chats'
6
6
  import { fetchAgents } from '../lib/agents'
7
7
  import { fetchSchedules } from '../lib/schedules'
8
8
  import { fetchTasks } from '../lib/tasks'
@@ -235,7 +235,7 @@ export const useAppStore = create<AppState>((set, get) => ({
235
235
  currentSessionId: null,
236
236
  loadSessions: async () => {
237
237
  try {
238
- const sessions = await fetchSessions()
238
+ const sessions = await fetchChats()
239
239
  set({ sessions })
240
240
  } catch {
241
241
  // ignore
@@ -249,7 +249,7 @@ export const useAppStore = create<AppState>((set, get) => ({
249
249
  },
250
250
  clearSessions: async (ids) => {
251
251
  if (!ids.length) return
252
- await api('DELETE', '/sessions', { ids })
252
+ await api('DELETE', '/chats', { ids })
253
253
  const sessions = { ...get().sessions }
254
254
  for (const id of ids) delete sessions[id]
255
255
  set({ sessions, currentSessionId: ids.includes(get().currentSessionId!) ? null : get().currentSessionId })
@@ -260,7 +260,7 @@ export const useAppStore = create<AppState>((set, get) => ({
260
260
  sessions[id] = { ...sessions[id], pinned: !sessions[id].pinned }
261
261
  set({ sessions })
262
262
  // Persist to server
263
- void api('PUT', `/sessions/${id}`, { pinned: sessions[id].pinned })
263
+ void api('PUT', `/chats/${id}`, { pinned: sessions[id].pinned })
264
264
  }
265
265
  },
266
266
  updateSessionInStore: (session) => {
@@ -268,7 +268,7 @@ export const useAppStore = create<AppState>((set, get) => ({
268
268
  },
269
269
  forkSession: async (sessionId, messageIndex) => {
270
270
  try {
271
- const forked = await api<Session>('POST', `/sessions/${sessionId}/fork`, { messageIndex })
271
+ const forked = await api<Session>('POST', `/chats/${sessionId}/fork`, { messageIndex })
272
272
  if (!forked?.id) return null
273
273
  await get().loadSessions()
274
274
  set({ currentSessionId: forked.id })
@@ -407,7 +407,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
407
407
  if (!sessionId) return
408
408
  try {
409
409
  const key = getStoredAccessKey()
410
- const res = await fetch(`/api/sessions/${sessionId}/edit-resend`, {
410
+ const res = await fetch(`/api/chats/${sessionId}/edit-resend`, {
411
411
  method: 'POST',
412
412
  headers: {
413
413
  'Content-Type': 'application/json',
@@ -417,7 +417,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
417
417
  })
418
418
  if (!res.ok) return
419
419
  // Reload messages from server (truncated)
420
- const msgsRes = await fetch(`/api/sessions/${sessionId}/messages`, {
420
+ const msgsRes = await fetch(`/api/chats/${sessionId}/messages`, {
421
421
  headers: key ? { 'X-Access-Key': key } : undefined,
422
422
  })
423
423
  if (msgsRes.ok) {
@@ -437,7 +437,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
437
437
  if (!sessionId) return
438
438
  try {
439
439
  const key = getStoredAccessKey()
440
- const res = await fetch(`/api/sessions/${sessionId}/retry`, {
440
+ const res = await fetch(`/api/chats/${sessionId}/retry`, {
441
441
  method: 'POST',
442
442
  headers: key ? { 'X-Access-Key': key } : undefined,
443
443
  })
@@ -445,7 +445,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
445
445
  const { message, imagePath } = await res.json()
446
446
  if (!message) return
447
447
  // Reload messages from server (without the popped ones)
448
- const msgsRes = await fetch(`/api/sessions/${sessionId}/messages`, {
448
+ const msgsRes = await fetch(`/api/chats/${sessionId}/messages`, {
449
449
  headers: key ? { 'X-Access-Key': key } : undefined,
450
450
  })
451
451
  if (msgsRes.ok) {
@@ -546,7 +546,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
546
546
  set((s) => ({ messages: [...s.messages, marker] }))
547
547
  try {
548
548
  const key = getStoredAccessKey()
549
- await fetch(`/api/sessions/${sessionId}/messages`, {
549
+ await fetch(`/api/chats/${sessionId}/messages`, {
550
550
  method: 'POST',
551
551
  headers: { 'Content-Type': 'application/json', ...(key ? { 'X-Access-Key': key } : {}) },
552
552
  body: JSON.stringify({ kind: 'context-clear' }),
@@ -569,7 +569,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
569
569
  const key = getStoredAccessKey()
570
570
  // Find the earliest message's original index (startIndex tracked on initial load)
571
571
  const currentStartIndex = totalMessages - messages.length
572
- const res = await fetch(`/api/sessions/${sessionId}/messages?limit=100&before=${currentStartIndex}`, {
572
+ const res = await fetch(`/api/chats/${sessionId}/messages?limit=100&before=${currentStartIndex}`, {
573
573
  headers: key ? { 'X-Access-Key': key } : undefined,
574
574
  })
575
575
  if (res.ok) {
@@ -593,7 +593,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
593
593
  if (sessionId) {
594
594
  try {
595
595
  const key = getStoredAccessKey()
596
- await fetch(`/api/sessions/${sessionId}/stop`, {
596
+ await fetch(`/api/chats/${sessionId}/stop`, {
597
597
  method: 'POST',
598
598
  headers: key ? { 'X-Access-Key': key } : undefined,
599
599
  })
@@ -63,15 +63,17 @@ export interface Session {
63
63
  claudeCode?: string | null
64
64
  codex?: string | null
65
65
  opencode?: string | null
66
+ gemini?: string | null
66
67
  }
67
68
  messages: Message[]
68
69
  createdAt: number
69
70
  lastActiveAt: number
70
71
  active?: boolean
71
- mainSession?: boolean
72
72
  sessionType?: SessionType
73
73
  agentId?: string | null
74
74
  parentSessionId?: string | null
75
+ plugins?: string[]
76
+ /** @deprecated Use `plugins` instead. Kept for backward compat with stored data. */
75
77
  tools?: string[]
76
78
  heartbeatEnabled?: boolean | null
77
79
  heartbeatIntervalSec?: number | null
@@ -167,6 +169,18 @@ export interface ApprovalRequest {
167
169
 
168
170
  export type Approvals = Record<string, ApprovalRequest>
169
171
 
172
+ export interface PluginInvocationRecord {
173
+ pluginId: string
174
+ toolName: string
175
+ inputTokens: number
176
+ outputTokens: number
177
+ }
178
+
179
+ export interface PluginDefinitionCost {
180
+ pluginId: string
181
+ estimatedTokens: number
182
+ }
183
+
170
184
  export interface UsageRecord {
171
185
  sessionId: string
172
186
  messageIndex: number
@@ -178,6 +192,8 @@ export interface UsageRecord {
178
192
  estimatedCost: number
179
193
  timestamp: number
180
194
  durationMs?: number
195
+ pluginDefinitionCosts?: PluginDefinitionCost[]
196
+ pluginInvocations?: PluginInvocationRecord[]
181
197
  }
182
198
 
183
199
  // --- Plugin System ---
@@ -186,9 +202,12 @@ export interface PluginHooks {
186
202
  beforeAgentStart?: (ctx: { session: Session; message: string }) => Promise<void> | void
187
203
  afterAgentComplete?: (ctx: { session: Session; response: string }) => Promise<void> | void
188
204
  beforeToolExec?: (ctx: { toolName: string; input: Record<string, unknown> | null }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void
189
- afterToolExec?: (ctx: { toolName: string; input: Record<string, unknown> | null; output: string }) => Promise<void> | void
205
+ afterToolExec?: (ctx: { session: Session; toolName: string; input: Record<string, unknown> | null; output: string }) => Promise<void> | void
190
206
  onMessage?: (ctx: { session: Session; message: Message }) => Promise<void> | void
191
207
 
208
+ // Post-turn hook — fires after a full chat exchange (user message → agent response)
209
+ afterChatTurn?: (ctx: { session: Session; message: string; response: string; source: string; internal: boolean }) => Promise<void> | void
210
+
192
211
  // Orchestration & Swarm Hooks
193
212
  onTaskComplete?: (ctx: { taskId: string; result: unknown }) => Promise<void> | void
194
213
  onAgentDelegation?: (ctx: { sourceAgentId: string; targetAgentId: string; task: string }) => Promise<void> | void
@@ -196,6 +215,15 @@ export interface PluginHooks {
196
215
  // Chat Middleware (Transform messages)
197
216
  transformInboundMessage?: (ctx: { session: Session; text: string }) => Promise<string> | string
198
217
  transformOutboundMessage?: (ctx: { session: Session; text: string }) => Promise<string> | string
218
+
219
+ // Context injection — return a markdown string to inject into the agent's state modifier, or null/undefined to skip
220
+ getAgentContext?: (ctx: { session: Session; enabledPlugins: string[]; message: string; history: Message[] }) => Promise<string | null | undefined> | string | null | undefined
221
+
222
+ // Self-description — returns a capability line for the system prompt (e.g., "I can remember things across conversations")
223
+ getCapabilityDescription?: () => string | null | undefined
224
+
225
+ // Operating guidance — returns operational hints for the agent when this plugin is active
226
+ getOperatingGuidance?: () => string | string[] | null | undefined
199
227
  }
200
228
 
201
229
  export interface PluginToolDef {
@@ -205,6 +233,17 @@ export interface PluginToolDef {
205
233
  execute: (args: Record<string, unknown>, ctx: { session: Session; message: string }) => Promise<string | object> | string | object
206
234
  }
207
235
 
236
+ export interface PluginSettingsField {
237
+ key: string
238
+ label: string
239
+ type: 'text' | 'number' | 'boolean' | 'select' | 'secret'
240
+ placeholder?: string
241
+ help?: string
242
+ options?: Array<{ value: string; label: string }>
243
+ defaultValue?: string | number | boolean
244
+ required?: boolean
245
+ }
246
+
208
247
  export interface PluginUIExtension {
209
248
  sidebarItems?: Array<{
210
249
  id: string
@@ -226,6 +265,22 @@ export interface PluginUIExtension {
226
265
  action: 'message' | 'link' | 'tool'
227
266
  value: string
228
267
  }>
268
+ /** Settings fields declared by the plugin, rendered in the plugin settings panel */
269
+ settingsFields?: PluginSettingsField[]
270
+ /** Chat panels the plugin provides (e.g., browser view, terminal) */
271
+ chatPanels?: Array<{
272
+ id: string
273
+ label: string
274
+ icon?: string
275
+ /** WS topic to subscribe to for updates (e.g., 'browser:{sessionId}') */
276
+ wsTopic?: string
277
+ }>
278
+ /** Badges to show on agent cards when this plugin is enabled */
279
+ agentBadges?: Array<{
280
+ id: string
281
+ label: string
282
+ icon?: string
283
+ }>
229
284
  }
230
285
 
231
286
  export interface PluginProviderExtension {
@@ -252,6 +307,7 @@ export interface Plugin {
252
307
  name: string
253
308
  version?: string
254
309
  description?: string
310
+ enabledByDefault?: boolean
255
311
  hooks?: PluginHooks
256
312
  tools?: PluginToolDef[]
257
313
  ui?: PluginUIExtension
@@ -279,6 +335,7 @@ export interface PluginMeta {
279
335
  providerCount?: number
280
336
  connectorCount?: number
281
337
  createdByAgentId?: string | null
338
+ settingsFields?: PluginSettingsField[]
282
339
  }
283
340
  export interface MarketplacePlugin {
284
341
  id: string
@@ -350,7 +407,9 @@ export interface Agent {
350
407
  apiEndpoint?: string | null
351
408
  isOrchestrator?: boolean
352
409
  subAgentIds?: string[]
353
- tools?: string[] // e.g. ['browser'] — available tool integrations
410
+ plugins?: string[] // e.g. ['browser', 'memory'] — enabled plugin IDs
411
+ /** @deprecated Use `plugins` instead. Kept for backward compat with stored data. */
412
+ tools?: string[]
354
413
  skills?: string[] // e.g. ['frontend-design'] — Claude Code skills to use
355
414
  skillIds?: string[] // IDs of uploaded skills from the Skills manager
356
415
  mcpServerIds?: string[] // IDs of configured MCP servers to inject tools from
@@ -784,6 +843,8 @@ export interface AppSettings {
784
843
  taskQualityGateRequireReport?: boolean
785
844
  // Integrity monitor
786
845
  integrityMonitorEnabled?: boolean
846
+ // Per-plugin settings (keyed by pluginId)
847
+ pluginSettings?: Record<string, Record<string, unknown>>
787
848
  }
788
849
 
789
850
  // --- Orchestrator Secrets ---
@@ -977,6 +1038,7 @@ export interface BoardTask {
977
1038
  claudeResumeId?: string | null
978
1039
  codexResumeId?: string | null
979
1040
  opencodeResumeId?: string | null
1041
+ geminiResumeId?: string | null
980
1042
  checkpoint?: {
981
1043
  lastRunId?: string | null
982
1044
  lastSessionId?: string | null