@swarmclawai/swarmclaw 0.6.8 → 0.7.0

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 (166) hide show
  1. package/README.md +70 -45
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +18 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/clawhub/install/route.ts +2 -2
  8. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  9. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  10. package/src/app/api/memory/route.ts +36 -5
  11. package/src/app/api/notifications/route.ts +3 -0
  12. package/src/app/api/plugins/install/route.ts +57 -5
  13. package/src/app/api/plugins/marketplace/route.ts +73 -22
  14. package/src/app/api/plugins/route.ts +61 -1
  15. package/src/app/api/plugins/ui/route.ts +34 -0
  16. package/src/app/api/settings/route.ts +62 -0
  17. package/src/app/api/setup/doctor/route.ts +22 -5
  18. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  19. package/src/app/api/tasks/[id]/route.ts +11 -3
  20. package/src/app/api/tasks/route.ts +8 -2
  21. package/src/app/globals.css +27 -0
  22. package/src/app/page.tsx +10 -5
  23. package/src/cli/index.js +13 -0
  24. package/src/components/activity/activity-feed.tsx +9 -2
  25. package/src/components/agents/agent-avatar.tsx +5 -1
  26. package/src/components/agents/agent-card.tsx +55 -9
  27. package/src/components/agents/agent-sheet.tsx +86 -29
  28. package/src/components/agents/inspector-panel.tsx +1 -1
  29. package/src/components/auth/access-key-gate.tsx +63 -54
  30. package/src/components/auth/user-picker.tsx +37 -32
  31. package/src/components/chat/chat-area.tsx +11 -0
  32. package/src/components/chat/chat-header.tsx +69 -25
  33. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  34. package/src/components/chat/code-block.tsx +3 -1
  35. package/src/components/chat/exec-approval-card.tsx +8 -1
  36. package/src/components/chat/message-bubble.tsx +164 -4
  37. package/src/components/chat/message-list.tsx +30 -4
  38. package/src/components/chat/session-approval-card.tsx +80 -0
  39. package/src/components/chat/streaming-bubble.tsx +6 -5
  40. package/src/components/chat/thinking-indicator.tsx +48 -12
  41. package/src/components/chat/tool-request-banner.tsx +39 -20
  42. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  43. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  44. package/src/components/connectors/connector-list.tsx +33 -11
  45. package/src/components/connectors/connector-sheet.tsx +29 -6
  46. package/src/components/home/home-view.tsx +20 -14
  47. package/src/components/input/chat-input.tsx +22 -1
  48. package/src/components/knowledge/knowledge-list.tsx +17 -18
  49. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  50. package/src/components/layout/app-layout.tsx +73 -21
  51. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  52. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  53. package/src/components/memory/memory-list.tsx +20 -13
  54. package/src/components/plugins/plugin-list.tsx +213 -59
  55. package/src/components/plugins/plugin-sheet.tsx +119 -24
  56. package/src/components/projects/project-list.tsx +17 -9
  57. package/src/components/providers/provider-list.tsx +21 -6
  58. package/src/components/providers/provider-sheet.tsx +42 -25
  59. package/src/components/runs/run-list.tsx +17 -13
  60. package/src/components/schedules/schedule-card.tsx +10 -3
  61. package/src/components/schedules/schedule-list.tsx +2 -2
  62. package/src/components/schedules/schedule-sheet.tsx +19 -7
  63. package/src/components/secrets/secret-sheet.tsx +7 -2
  64. package/src/components/secrets/secrets-list.tsx +18 -5
  65. package/src/components/sessions/new-session-sheet.tsx +183 -376
  66. package/src/components/sessions/session-card.tsx +10 -2
  67. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  68. package/src/components/shared/command-palette.tsx +13 -5
  69. package/src/components/shared/empty-state.tsx +20 -8
  70. package/src/components/shared/notification-center.tsx +134 -86
  71. package/src/components/shared/profile-sheet.tsx +4 -0
  72. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  73. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  74. package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
  75. package/src/components/skills/clawhub-browser.tsx +1 -0
  76. package/src/components/skills/skill-list.tsx +31 -12
  77. package/src/components/skills/skill-sheet.tsx +20 -7
  78. package/src/components/tasks/approvals-panel.tsx +170 -66
  79. package/src/components/tasks/task-board.tsx +20 -12
  80. package/src/components/tasks/task-card.tsx +21 -7
  81. package/src/components/tasks/task-column.tsx +4 -3
  82. package/src/components/tasks/task-list.tsx +1 -1
  83. package/src/components/tasks/task-sheet.tsx +130 -1
  84. package/src/components/ui/dialog.tsx +1 -0
  85. package/src/components/ui/sheet.tsx +1 -0
  86. package/src/components/usage/metrics-dashboard.tsx +66 -64
  87. package/src/components/wallets/wallet-panel.tsx +65 -41
  88. package/src/components/wallets/wallet-section.tsx +9 -3
  89. package/src/components/webhooks/webhook-list.tsx +21 -12
  90. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  91. package/src/lib/approval-display.test.ts +45 -0
  92. package/src/lib/approval-display.ts +62 -0
  93. package/src/lib/clipboard.ts +38 -0
  94. package/src/lib/memory.ts +8 -0
  95. package/src/lib/providers/claude-cli.ts +5 -3
  96. package/src/lib/providers/index.ts +67 -21
  97. package/src/lib/runtime-loop.ts +3 -2
  98. package/src/lib/server/approvals.ts +150 -0
  99. package/src/lib/server/chat-execution.ts +223 -62
  100. package/src/lib/server/clawhub-client.ts +82 -6
  101. package/src/lib/server/connectors/manager.ts +27 -1
  102. package/src/lib/server/cost.test.ts +73 -0
  103. package/src/lib/server/cost.ts +165 -34
  104. package/src/lib/server/daemon-state.ts +42 -0
  105. package/src/lib/server/data-dir.ts +18 -1
  106. package/src/lib/server/integrity-monitor.ts +208 -0
  107. package/src/lib/server/llm-response-cache.test.ts +102 -0
  108. package/src/lib/server/llm-response-cache.ts +227 -0
  109. package/src/lib/server/main-agent-loop.ts +1 -1
  110. package/src/lib/server/main-session.ts +6 -3
  111. package/src/lib/server/mcp-conformance.test.ts +18 -0
  112. package/src/lib/server/mcp-conformance.ts +233 -0
  113. package/src/lib/server/memory-db.ts +180 -17
  114. package/src/lib/server/memory-retrieval.test.ts +56 -0
  115. package/src/lib/server/orchestrator-lg.ts +4 -1
  116. package/src/lib/server/orchestrator.ts +4 -3
  117. package/src/lib/server/plugins.ts +650 -142
  118. package/src/lib/server/process-manager.ts +18 -0
  119. package/src/lib/server/queue.ts +253 -11
  120. package/src/lib/server/runtime-settings.ts +9 -0
  121. package/src/lib/server/session-run-manager.test.ts +23 -0
  122. package/src/lib/server/session-run-manager.ts +11 -1
  123. package/src/lib/server/session-tools/canvas.ts +85 -50
  124. package/src/lib/server/session-tools/chatroom.ts +130 -127
  125. package/src/lib/server/session-tools/connector.ts +233 -454
  126. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  127. package/src/lib/server/session-tools/crud.ts +84 -7
  128. package/src/lib/server/session-tools/delegate.ts +351 -752
  129. package/src/lib/server/session-tools/discovery.ts +198 -0
  130. package/src/lib/server/session-tools/edit_file.ts +82 -0
  131. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  132. package/src/lib/server/session-tools/file.ts +257 -425
  133. package/src/lib/server/session-tools/git.ts +87 -47
  134. package/src/lib/server/session-tools/http.ts +85 -33
  135. package/src/lib/server/session-tools/index.ts +205 -160
  136. package/src/lib/server/session-tools/memory.ts +152 -265
  137. package/src/lib/server/session-tools/monitor.ts +126 -0
  138. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  139. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  140. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  141. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  142. package/src/lib/server/session-tools/platform.ts +86 -0
  143. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  144. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  145. package/src/lib/server/session-tools/sandbox.ts +175 -148
  146. package/src/lib/server/session-tools/schedule.ts +66 -31
  147. package/src/lib/server/session-tools/session-info.ts +104 -410
  148. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  149. package/src/lib/server/session-tools/shell.ts +171 -143
  150. package/src/lib/server/session-tools/subagent.ts +77 -77
  151. package/src/lib/server/session-tools/wallet.ts +182 -106
  152. package/src/lib/server/session-tools/web.ts +179 -349
  153. package/src/lib/server/storage.ts +24 -0
  154. package/src/lib/server/stream-agent-chat.ts +301 -244
  155. package/src/lib/server/task-quality-gate.test.ts +44 -0
  156. package/src/lib/server/task-quality-gate.ts +67 -0
  157. package/src/lib/server/task-validation.test.ts +78 -0
  158. package/src/lib/server/task-validation.ts +67 -2
  159. package/src/lib/server/tool-aliases.ts +68 -0
  160. package/src/lib/server/tool-capability-policy.ts +23 -5
  161. package/src/lib/tasks.ts +7 -1
  162. package/src/lib/tool-definitions.ts +23 -23
  163. package/src/lib/validation/schemas.ts +12 -0
  164. package/src/lib/view-routes.ts +2 -24
  165. package/src/stores/use-app-store.ts +23 -1
  166. package/src/types/index.ts +121 -7
@@ -1,121 +1,103 @@
1
1
  import { z } from 'zod'
2
2
  import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
3
  import { HumanMessage } from '@langchain/core/messages'
4
- import { loadSessions, saveSessions, loadCredentials, decryptKey } from '../storage'
4
+ import { loadSessions, saveSessions } from '../storage'
5
5
  import { buildChatModel } from '../build-llm'
6
- import { getProvider } from '@/lib/providers'
7
6
  import type { ToolBuildContext } from './context'
7
+ import type { Plugin, PluginHooks, Session } from '@/types'
8
+ import { getPluginManager } from '../plugins'
9
+ import { normalizeToolInputArgs } from './normalize-tool-args'
8
10
 
9
- export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterface[] {
10
- const tools: StructuredToolInterface[] = []
11
- const { ctx, activeTools, resolveCurrentSession } = bctx
11
+ interface ContextToolContext {
12
+ ctx?: { agentId?: string | null; sessionId?: string | null }
13
+ resolveCurrentSession?: () => Session | null
14
+ }
12
15
 
13
- if (activeTools.length > 0) {
14
- tools.push(
15
- tool(
16
- async () => {
17
- try {
18
- const { getContextStatus } = await import('../context-manager')
19
- const session = resolveCurrentSession()
20
- if (!session) return 'Error: no current session context.'
21
- const messages = session.messages || []
22
- const systemPromptTokens = 2000
23
- const status = getContextStatus(messages, systemPromptTokens, session.provider, session.model)
24
- return JSON.stringify(status)
25
- } catch (err: unknown) {
26
- return `Error: ${err instanceof Error ? err.message : String(err)}`
27
- }
28
- },
29
- {
30
- name: 'context_status',
31
- description: 'Check how much of my context window I\'ve used. Returns my token usage, the model\'s limit, percentage used, and whether I should compact.',
32
- schema: z.object({}),
33
- },
34
- ),
35
- )
16
+ /**
17
+ * Core Context Management Execution Logic
18
+ */
19
+ async function executeContextStatus(bctx: ContextToolContext) {
20
+ try {
21
+ const { getContextStatus } = await import('../context-manager')
22
+ const session = bctx.resolveCurrentSession?.()
23
+ if (!session) return 'Error: no current session context.'
24
+ const status = getContextStatus(session.messages || [], 2000, session.provider as string, session.model as string)
25
+ return JSON.stringify(status)
26
+ } catch (err: unknown) { return `Error: ${err instanceof Error ? err.message : String(err)}` }
27
+ }
36
28
 
37
- tools.push(
38
- tool(
39
- async ({ keepLastN }) => {
40
- try {
41
- const { summarizeAndCompact } = await import('../context-manager')
42
- const session = resolveCurrentSession()
43
- if (!session) return 'Error: no current session context.'
44
- if (!ctx?.sessionId) return 'Error: no session id in context.'
45
- const messages = session.messages || []
46
- const keep = Math.max(2, Math.min(keepLastN || 10, messages.length))
29
+ async function executeContextSummarize(args: { keepLastN?: number }, bctx: ContextToolContext) {
30
+ try {
31
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
32
+ const { summarizeAndCompact } = await import('../context-manager')
33
+ const session = bctx.resolveCurrentSession?.()
34
+ if (!session || !bctx.ctx?.sessionId) return 'Error: no session context.'
35
+
36
+ const messages = session.messages || []
37
+ const keepLastN = normalized.keepLastN as number | undefined
38
+ const keep = Math.max(2, Math.min(keepLastN || 10, messages.length))
39
+ if (messages.length <= keep) return JSON.stringify({ status: 'no_action' })
47
40
 
48
- if (messages.length <= keep) {
49
- return JSON.stringify({ status: 'no_action', reason: 'Not enough messages to compact', messageCount: messages.length })
50
- }
41
+ const generateSummary = async (prompt: string): Promise<string> => {
42
+ const llm = buildChatModel({ provider: session.provider, model: session.model, apiKey: null })
43
+ const res = await llm.invoke([new HumanMessage(prompt)])
44
+ return typeof res.content === 'string' ? res.content : ''
45
+ }
51
46
 
52
- // Resolve API key for the session's provider
53
- let apiKey: string | null = null
54
- const providerInfo = getProvider(session.provider)
55
- if ((providerInfo?.requiresApiKey || providerInfo?.optionalApiKey) && session.credentialId) {
56
- try {
57
- const creds = loadCredentials()
58
- const cred = creds[session.credentialId]
59
- if (cred) apiKey = decryptKey(cred.encryptedKey)
60
- } catch { /* continue without key */ }
61
- }
47
+ const result = await summarizeAndCompact({
48
+ messages, keepLastN: keep, agentId: bctx.ctx.agentId ?? null, sessionId: bctx.ctx.sessionId ?? '',
49
+ provider: session.provider, model: session.model, generateSummary
50
+ })
62
51
 
63
- // Build LLM summarizer using the session's provider/model
64
- const generateSummary = async (prompt: string): Promise<string> => {
65
- const llm = buildChatModel({
66
- provider: session.provider,
67
- model: session.model,
68
- apiKey,
69
- apiEndpoint: session.apiEndpoint,
70
- })
71
- const response = await llm.invoke([new HumanMessage(prompt)])
72
- if (typeof response.content === 'string') return response.content
73
- if (Array.isArray(response.content)) {
74
- return response.content
75
- .map((b: Record<string, unknown>) => (typeof b.text === 'string' ? b.text : ''))
76
- .join('')
77
- }
78
- return ''
79
- }
52
+ const sessions = loadSessions()
53
+ if (sessions[bctx.ctx.sessionId]) {
54
+ sessions[bctx.ctx.sessionId].messages = result.messages
55
+ saveSessions(sessions)
56
+ }
57
+ return JSON.stringify({ status: 'compacted', remaining: result.messages.length })
58
+ } catch (err: unknown) { return `Error: ${err instanceof Error ? err.message : String(err)}` }
59
+ }
80
60
 
81
- const result = await summarizeAndCompact({
82
- messages,
83
- keepLastN: keep,
84
- agentId: ctx?.agentId || session.agentId || null,
85
- sessionId: ctx.sessionId,
86
- provider: session.provider,
87
- model: session.model,
88
- generateSummary,
89
- })
61
+ /**
62
+ * Register as a Built-in Plugin
63
+ */
64
+ const ContextPlugin: Plugin = {
65
+ name: 'Core Context',
66
+ description: 'Manage and optimize the agent conversation context window.',
67
+ hooks: {} as PluginHooks,
68
+ tools: [
69
+ {
70
+ name: 'context_status',
71
+ description: 'Check token usage and context window limits.',
72
+ parameters: { type: 'object', properties: {} },
73
+ execute: async (_args, context) => executeContextStatus({ resolveCurrentSession: () => context.session as unknown as Session })
74
+ },
75
+ {
76
+ name: 'context_summarize',
77
+ description: 'Compact conversation history to free up space.',
78
+ parameters: {
79
+ type: 'object',
80
+ properties: { keepLastN: { type: 'number' } }
81
+ },
82
+ execute: async (args, context) => executeContextSummarize(args as { keepLastN?: number }, { ctx: { sessionId: context.session.id, agentId: context.session.agentId ?? null }, resolveCurrentSession: () => context.session as unknown as Session })
83
+ }
84
+ ]
85
+ }
90
86
 
91
- const sessions = loadSessions()
92
- const target = sessions[ctx.sessionId]
93
- if (target) {
94
- target.messages = result.messages
95
- saveSessions(sessions)
96
- }
87
+ getPluginManager().registerBuiltin('context_mgmt', ContextPlugin)
97
88
 
98
- return JSON.stringify({
99
- status: 'compacted',
100
- prunedCount: result.prunedCount,
101
- memoriesStored: result.memoriesStored,
102
- summaryAdded: result.summaryAdded,
103
- remainingMessages: result.messages.length,
104
- })
105
- } catch (err: unknown) {
106
- return `Error: ${err instanceof Error ? err.message : String(err)}`
107
- }
108
- },
109
- {
110
- name: 'context_summarize',
111
- description: 'Compact my conversation history to free up context space. I\'ll save important decisions, facts, and results to memory, then replace older messages with a summary. I should check context_status first to see if this is needed.',
112
- schema: z.object({
113
- keepLastN: z.number().optional().describe('Number of recent messages to keep (default 10, min 2).'),
114
- }),
115
- },
116
- ),
89
+ /**
90
+ * Legacy Bridge
91
+ */
92
+ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterface[] {
93
+ return [
94
+ tool(
95
+ async () => executeContextStatus(bctx),
96
+ { name: 'context_status', description: ContextPlugin.tools![0].description, schema: z.object({}).passthrough() }
97
+ ),
98
+ tool(
99
+ async (args) => executeContextSummarize(args as { keepLastN?: number }, bctx),
100
+ { name: 'context_summarize', description: ContextPlugin.tools![1].description, schema: z.object({}).passthrough() }
117
101
  )
118
- }
119
-
120
- return tools
102
+ ]
121
103
  }
@@ -15,6 +15,7 @@ import {
15
15
  loadWebhooks, saveWebhooks,
16
16
  loadSecrets, saveSecrets,
17
17
  loadSessions, saveSessions,
18
+ loadSettings,
18
19
  encryptKey,
19
20
  decryptKey,
20
21
  } from '../storage'
@@ -22,8 +23,10 @@ import { resolveScheduleName } from '@/lib/schedule-name'
22
23
  import { findDuplicateSchedule, type ScheduleLike } from '@/lib/schedule-dedupe'
23
24
  import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
24
25
  import { resolveTaskAgentFromDescription } from '@/lib/server/task-mention'
26
+ import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
25
27
  import type { ToolBuildContext } from './context'
26
28
  import { safePath, findBinaryOnPath } from './context'
29
+ import { normalizeToolInputArgs } from './normalize-tool-args'
27
30
 
28
31
  // ---------------------------------------------------------------------------
29
32
  // Document helpers
@@ -87,6 +90,41 @@ function trimDocumentContent(text: string): string {
87
90
  return normalized.slice(0, MAX_DOCUMENT_TEXT_CHARS)
88
91
  }
89
92
 
93
+ function deriveTaskTitle(input: { title?: unknown; description?: unknown }): string {
94
+ const explicit = typeof input.title === 'string' ? input.title.replace(/\s+/g, ' ').trim() : ''
95
+ if (explicit && !/^untitled task$/i.test(explicit)) return explicit.slice(0, 120)
96
+
97
+ const description = typeof input.description === 'string'
98
+ ? input.description.replace(/\s+/g, ' ').trim()
99
+ : ''
100
+ if (!description) return ''
101
+
102
+ const firstSentence = description.split(/[.!?]\s+/)[0] || description
103
+ const compact = firstSentence
104
+ .replace(/^please\s+/i, '')
105
+ .replace(/^(create|make|build|implement|write)\s+/i, '')
106
+ .trim()
107
+ if (!compact) return ''
108
+ return compact.slice(0, 120)
109
+ }
110
+
111
+ const TASK_STATUS_VALUES = new Set([
112
+ 'backlog',
113
+ 'queued',
114
+ 'running',
115
+ 'completed',
116
+ 'failed',
117
+ 'archived',
118
+ ])
119
+
120
+ function normalizeTaskStatusInput(status: unknown, prevStatus?: string): string | null {
121
+ if (typeof status !== 'string') return null
122
+ const normalized = status.trim().toLowerCase()
123
+ if (!TASK_STATUS_VALUES.has(normalized)) return null
124
+ if (normalized === 'running' && prevStatus !== 'running') return 'queued'
125
+ return normalized
126
+ }
127
+
90
128
  // ---------------------------------------------------------------------------
91
129
  // RESOURCE_DEFAULTS
92
130
  // ---------------------------------------------------------------------------
@@ -107,7 +145,7 @@ const RESOURCE_DEFAULTS: Record<string, (parsed: any) => any> = {
107
145
  ...p,
108
146
  }),
109
147
  manage_tasks: (p) => ({
110
- title: p.title || 'Untitled Task',
148
+ title: deriveTaskTitle(p) || 'Untitled Task',
111
149
  description: p.description || '',
112
150
  status: p.status || 'backlog',
113
151
  agentId: p.agentId || null,
@@ -218,9 +256,9 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
218
256
  let description = `Manage SwarmClaw ${res.label}. ${res.readOnly ? 'List and get only.' : 'List, get, create, update, or delete.'} Returns JSON.`
219
257
  if (toolKey === 'manage_tasks') {
220
258
  if (assignScope === 'self') {
221
- description += `\n\nSet "agentId" to assign a task to yourself ("${ctx?.agentId || 'unknown'}") or leave it null. You can only assign tasks to yourself. Valid statuses: backlog, queued, running, completed, failed.`
259
+ description += `\n\nSet "agentId" to assign a task to yourself ("${ctx?.agentId || 'unknown'}") or leave it null. You can only assign tasks to yourself. Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.`
222
260
  } else {
223
- description += `\n\nSet "agentId" to assign a task to an agent (including yourself: "${ctx?.agentId || 'unknown'}"). Valid statuses: backlog, queued, running, completed, failed.` + agentSummary
261
+ description += `\n\nSet "agentId" to assign a task to an agent (including yourself: "${ctx?.agentId || 'unknown'}"). Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.` + agentSummary
224
262
  }
225
263
  } else if (toolKey === 'manage_agents') {
226
264
  description += `\n\nAgents may self-edit their own soul. To update your soul, use action="update", id="${ctx?.agentId || 'your-agent-id'}", and include data with the "soul" field.`
@@ -236,7 +274,11 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
236
274
 
237
275
  tools.push(
238
276
  tool(
239
- async ({ action, id, data }) => {
277
+ async (rawArgs) => {
278
+ const normalized = normalizeToolInputArgs((rawArgs ?? {}) as Record<string, unknown>)
279
+ const action = normalized.action as string | undefined
280
+ const id = normalized.id as string | undefined
281
+ const data = normalized.data as string | undefined
240
282
  const canAccessSecret = (secret: any): boolean => {
241
283
  if (!secret) return false
242
284
  if (secret.scope !== 'agent') return true
@@ -354,6 +396,20 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
354
396
  agents,
355
397
  )
356
398
  }
399
+ if (toolKey === 'manage_tasks') {
400
+ parsed.title = deriveTaskTitle(parsed)
401
+ if (!parsed.title || /^untitled task$/i.test(parsed.title)) {
402
+ return 'Error: manage_tasks create requires a specific title or a meaningful description.'
403
+ }
404
+ parsed.status = normalizeTaskStatusInput(parsed.status) || 'backlog'
405
+ if (!parsed.cwd && cwd) parsed.cwd = cwd
406
+ if (Object.prototype.hasOwnProperty.call(parsed, 'qualityGate')) {
407
+ const settings = loadSettings()
408
+ parsed.qualityGate = parsed.qualityGate
409
+ ? normalizeTaskQualityGate(parsed.qualityGate, settings)
410
+ : null
411
+ }
412
+ }
357
413
  // Task dedup
358
414
  if (toolKey === 'manage_tasks') {
359
415
  const fp = computeTaskFingerprint(parsed.title || 'Untitled Task', parsed.agentId || ctx?.agentId || '')
@@ -400,9 +456,10 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
400
456
  if (toolKey === 'manage_tasks' && entry.status === 'completed') {
401
457
  const { formatValidationFailure, validateTaskCompletion } = await import('../task-validation')
402
458
  const { ensureTaskCompletionReport } = await import('../task-reports')
459
+ const settings = loadSettings()
403
460
  const report = ensureTaskCompletionReport(entry as any)
404
461
  if (report?.relativePath) (entry as any).completionReportPath = report.relativePath
405
- const validation = validateTaskCompletion(entry as any, { report })
462
+ const validation = validateTaskCompletion(entry as any, { report, settings })
406
463
  ;(entry as any).validation = validation
407
464
  if (!validation.ok) {
408
465
  entry.status = 'failed'
@@ -431,6 +488,17 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
431
488
  if (!all[id]) return `Not found: ${res.label} "${id}"`
432
489
  const parsed = data ? JSON.parse(data) : {}
433
490
  const prevStatus = all[id]?.status
491
+ if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsed, 'status')) {
492
+ const normalized = normalizeTaskStatusInput(parsed.status, prevStatus)
493
+ if (normalized) parsed.status = normalized
494
+ else delete parsed.status
495
+ }
496
+ if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsed, 'qualityGate')) {
497
+ const settings = loadSettings()
498
+ parsed.qualityGate = parsed.qualityGate
499
+ ? normalizeTaskQualityGate(parsed.qualityGate, settings)
500
+ : null
501
+ }
434
502
  // Enforce assignment scope for tasks and schedules
435
503
  if (assignScope === 'self' && (toolKey === 'manage_tasks' || toolKey === 'manage_schedules')) {
436
504
  if (parsed.agentId && parsed.agentId !== ctx?.agentId) {
@@ -468,9 +536,10 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
468
536
  if (toolKey === 'manage_tasks' && all[id].status === 'completed') {
469
537
  const { formatValidationFailure, validateTaskCompletion } = await import('../task-validation')
470
538
  const { ensureTaskCompletionReport } = await import('../task-reports')
539
+ const settings = loadSettings()
471
540
  const report = ensureTaskCompletionReport(all[id] as any)
472
541
  if (report?.relativePath) (all[id] as any).completionReportPath = report.relativePath
473
- const validation = validateTaskCompletion(all[id] as any, { report })
542
+ const validation = validateTaskCompletion(all[id] as any, { report, settings })
474
543
  ;(all[id] as any).validation = validation
475
544
  if (!validation.ok) {
476
545
  all[id].status = 'failed'
@@ -532,7 +601,15 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
532
601
  if (hasTool('manage_documents')) {
533
602
  tools.push(
534
603
  tool(
535
- async ({ action, id, filePath, query, limit, metadata, title }) => {
604
+ async (rawArgs) => {
605
+ const normalized = normalizeToolInputArgs((rawArgs ?? {}) as Record<string, unknown>)
606
+ const action = normalized.action as string | undefined
607
+ const id = normalized.id as string | undefined
608
+ const filePath = (normalized.filePath ?? normalized.path) as string | undefined
609
+ const query = normalized.query as string | undefined
610
+ const limit = normalized.limit as number | undefined
611
+ const metadata = normalized.metadata as string | undefined
612
+ const title = normalized.title as string | undefined
536
613
  try {
537
614
  const documents = loadDocuments()
538
615