@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.
- package/README.md +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { normalizeTaskQualityGate } from './task-quality-gate.ts'
|
|
4
|
+
|
|
5
|
+
test('normalizeTaskQualityGate uses defaults when unset', () => {
|
|
6
|
+
const gate = normalizeTaskQualityGate(undefined, undefined)
|
|
7
|
+
assert.equal(gate.enabled, true)
|
|
8
|
+
assert.equal(gate.minResultChars, 80)
|
|
9
|
+
assert.equal(gate.minEvidenceItems, 2)
|
|
10
|
+
assert.equal(gate.requireVerification, false)
|
|
11
|
+
assert.equal(gate.requireArtifact, false)
|
|
12
|
+
assert.equal(gate.requireReport, false)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('normalizeTaskQualityGate respects app settings defaults', () => {
|
|
16
|
+
const gate = normalizeTaskQualityGate(null, {
|
|
17
|
+
taskQualityGateEnabled: false,
|
|
18
|
+
taskQualityGateMinResultChars: 120,
|
|
19
|
+
taskQualityGateMinEvidenceItems: 1,
|
|
20
|
+
taskQualityGateRequireVerification: true,
|
|
21
|
+
})
|
|
22
|
+
assert.equal(gate.enabled, false)
|
|
23
|
+
assert.equal(gate.minResultChars, 120)
|
|
24
|
+
assert.equal(gate.minEvidenceItems, 1)
|
|
25
|
+
assert.equal(gate.requireVerification, true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('normalizeTaskQualityGate allows per-task overrides on top of settings', () => {
|
|
29
|
+
const gate = normalizeTaskQualityGate({
|
|
30
|
+
enabled: true,
|
|
31
|
+
minResultChars: 64,
|
|
32
|
+
minEvidenceItems: 3,
|
|
33
|
+
requireArtifact: true,
|
|
34
|
+
}, {
|
|
35
|
+
taskQualityGateEnabled: false,
|
|
36
|
+
taskQualityGateMinResultChars: 120,
|
|
37
|
+
taskQualityGateMinEvidenceItems: 1,
|
|
38
|
+
taskQualityGateRequireArtifact: false,
|
|
39
|
+
})
|
|
40
|
+
assert.equal(gate.enabled, true)
|
|
41
|
+
assert.equal(gate.minResultChars, 64)
|
|
42
|
+
assert.equal(gate.minEvidenceItems, 3)
|
|
43
|
+
assert.equal(gate.requireArtifact, true)
|
|
44
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AppSettings, TaskQualityGateConfig } from '@/types'
|
|
2
|
+
|
|
3
|
+
export interface NormalizedTaskQualityGate {
|
|
4
|
+
enabled: boolean
|
|
5
|
+
minResultChars: number
|
|
6
|
+
minEvidenceItems: number
|
|
7
|
+
requireVerification: boolean
|
|
8
|
+
requireArtifact: boolean
|
|
9
|
+
requireReport: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_TASK_QUALITY_GATE: NormalizedTaskQualityGate = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
minResultChars: 80,
|
|
15
|
+
minEvidenceItems: 2,
|
|
16
|
+
requireVerification: false,
|
|
17
|
+
requireArtifact: false,
|
|
18
|
+
requireReport: false,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeInt(value: unknown, fallback: number, min: number, max: number): number {
|
|
22
|
+
const parsed = typeof value === 'number'
|
|
23
|
+
? value
|
|
24
|
+
: typeof value === 'string'
|
|
25
|
+
? Number.parseInt(value, 10)
|
|
26
|
+
: Number.NaN
|
|
27
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
28
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeBool(value: unknown, fallback: boolean): boolean {
|
|
32
|
+
if (typeof value === 'boolean') return value
|
|
33
|
+
if (typeof value === 'string') {
|
|
34
|
+
const normalized = value.trim().toLowerCase()
|
|
35
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
|
|
36
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false
|
|
37
|
+
}
|
|
38
|
+
return fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeSettingsDefaults(settings?: AppSettings | Record<string, unknown> | null): NormalizedTaskQualityGate {
|
|
42
|
+
const raw = settings && typeof settings === 'object' ? settings as Record<string, unknown> : {}
|
|
43
|
+
return {
|
|
44
|
+
enabled: normalizeBool(raw.taskQualityGateEnabled, DEFAULT_TASK_QUALITY_GATE.enabled),
|
|
45
|
+
minResultChars: normalizeInt(raw.taskQualityGateMinResultChars, DEFAULT_TASK_QUALITY_GATE.minResultChars, 10, 2000),
|
|
46
|
+
minEvidenceItems: normalizeInt(raw.taskQualityGateMinEvidenceItems, DEFAULT_TASK_QUALITY_GATE.minEvidenceItems, 0, 8),
|
|
47
|
+
requireVerification: normalizeBool(raw.taskQualityGateRequireVerification, DEFAULT_TASK_QUALITY_GATE.requireVerification),
|
|
48
|
+
requireArtifact: normalizeBool(raw.taskQualityGateRequireArtifact, DEFAULT_TASK_QUALITY_GATE.requireArtifact),
|
|
49
|
+
requireReport: normalizeBool(raw.taskQualityGateRequireReport, DEFAULT_TASK_QUALITY_GATE.requireReport),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function normalizeTaskQualityGate(
|
|
54
|
+
rawGate?: TaskQualityGateConfig | Record<string, unknown> | null,
|
|
55
|
+
settings?: AppSettings | Record<string, unknown> | null,
|
|
56
|
+
): NormalizedTaskQualityGate {
|
|
57
|
+
const defaults = normalizeSettingsDefaults(settings)
|
|
58
|
+
const raw = rawGate && typeof rawGate === 'object' ? rawGate as Record<string, unknown> : {}
|
|
59
|
+
return {
|
|
60
|
+
enabled: normalizeBool(raw.enabled, defaults.enabled),
|
|
61
|
+
minResultChars: normalizeInt(raw.minResultChars, defaults.minResultChars, 10, 2000),
|
|
62
|
+
minEvidenceItems: normalizeInt(raw.minEvidenceItems, defaults.minEvidenceItems, 0, 8),
|
|
63
|
+
requireVerification: normalizeBool(raw.requireVerification, defaults.requireVerification),
|
|
64
|
+
requireArtifact: normalizeBool(raw.requireArtifact, defaults.requireArtifact),
|
|
65
|
+
requireReport: normalizeBool(raw.requireReport, defaults.requireReport),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -48,3 +48,81 @@ test('validateTaskCompletion still enforces stricter minimum for implementation
|
|
|
48
48
|
assert.equal(validation.ok, false)
|
|
49
49
|
assert.ok(validation.reasons.some((reason) => reason.includes('Result summary is too short')))
|
|
50
50
|
})
|
|
51
|
+
|
|
52
|
+
test('validateTaskCompletion fails implementation task with unfinished next-step language', () => {
|
|
53
|
+
const validation = validateTaskCompletion({
|
|
54
|
+
title: 'Build weather dashboard',
|
|
55
|
+
description: 'Implement dashboard and run dev server.',
|
|
56
|
+
result: 'I prepared an outline. Next I will run the server once access is granted.',
|
|
57
|
+
error: null,
|
|
58
|
+
} as Partial<BoardTask>)
|
|
59
|
+
|
|
60
|
+
assert.equal(validation.ok, false)
|
|
61
|
+
assert.ok(validation.reasons.some((reason) => reason.includes('unfinished work')))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('validateTaskCompletion fails implementation task that requests shell access', () => {
|
|
65
|
+
const validation = validateTaskCompletion({
|
|
66
|
+
title: 'Create blog and run server',
|
|
67
|
+
description: 'Create markdown blog and serve it.',
|
|
68
|
+
result: 'I created the blog file at data/workspace/blog/swarmclaw-blog.md, but I need access to the shell to proceed. Once the access is granted, I will finish setup.',
|
|
69
|
+
error: null,
|
|
70
|
+
} as Partial<BoardTask>)
|
|
71
|
+
|
|
72
|
+
assert.equal(validation.ok, false)
|
|
73
|
+
assert.ok(validation.reasons.some((reason) => reason.includes('unfinished work')))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('validateTaskCompletion fails untitled tasks with empty metadata', () => {
|
|
77
|
+
const validation = validateTaskCompletion({
|
|
78
|
+
title: 'Untitled Task',
|
|
79
|
+
description: '',
|
|
80
|
+
result: 'Could you provide more information about what you need?',
|
|
81
|
+
error: null,
|
|
82
|
+
} as Partial<BoardTask>)
|
|
83
|
+
|
|
84
|
+
assert.equal(validation.ok, false)
|
|
85
|
+
assert.ok(validation.reasons.some((reason) => reason.includes('metadata is too vague')))
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('validateTaskCompletion enforces explicit quality gate evidence requirements', () => {
|
|
89
|
+
const validation = validateTaskCompletion({
|
|
90
|
+
title: 'Ship API migration summary',
|
|
91
|
+
description: 'Summarize the migration outcome.',
|
|
92
|
+
result: 'Migration summary completed successfully with no extra artifacts included.',
|
|
93
|
+
qualityGate: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
minResultChars: 20,
|
|
96
|
+
minEvidenceItems: 2,
|
|
97
|
+
requireArtifact: true,
|
|
98
|
+
},
|
|
99
|
+
error: null,
|
|
100
|
+
} as Partial<BoardTask>)
|
|
101
|
+
|
|
102
|
+
assert.equal(validation.ok, false)
|
|
103
|
+
assert.ok(validation.reasons.some((reason) => reason.includes('insufficient completion evidence')))
|
|
104
|
+
assert.ok(validation.reasons.some((reason) => reason.includes('artifact evidence is required')))
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('validateTaskCompletion passes explicit quality gate when evidence checks are met', () => {
|
|
108
|
+
const validation = validateTaskCompletion({
|
|
109
|
+
title: 'Ship API migration summary',
|
|
110
|
+
description: 'Summarize the migration outcome.',
|
|
111
|
+
result: 'Ran npm test and tests passed. Updated src/api/migrate.ts. Uploaded evidence: sandbox:/api/uploads/migration-proof.png.',
|
|
112
|
+
artifacts: [{
|
|
113
|
+
url: 'sandbox:/api/uploads/migration-proof.png',
|
|
114
|
+
type: 'image',
|
|
115
|
+
filename: 'migration-proof.png',
|
|
116
|
+
}],
|
|
117
|
+
qualityGate: {
|
|
118
|
+
enabled: true,
|
|
119
|
+
minResultChars: 20,
|
|
120
|
+
minEvidenceItems: 2,
|
|
121
|
+
requireArtifact: true,
|
|
122
|
+
requireVerification: true,
|
|
123
|
+
},
|
|
124
|
+
error: null,
|
|
125
|
+
} as Partial<BoardTask>)
|
|
126
|
+
|
|
127
|
+
assert.equal(validation.ok, true)
|
|
128
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BoardTask } from '@/types'
|
|
2
2
|
import type { TaskReportArtifact } from './task-reports'
|
|
3
|
+
import { normalizeTaskQualityGate } from './task-quality-gate'
|
|
3
4
|
|
|
4
5
|
export interface TaskCompletionValidation {
|
|
5
6
|
ok: boolean
|
|
@@ -9,6 +10,7 @@ export interface TaskCompletionValidation {
|
|
|
9
10
|
|
|
10
11
|
interface TaskCompletionValidationOptions {
|
|
11
12
|
report?: TaskReportArtifact | null
|
|
13
|
+
settings?: Record<string, unknown> | null
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const MIN_RESULT_CHARS_IMPLEMENTATION = 40
|
|
@@ -24,8 +26,26 @@ const WEAK_RESULT_PATTERNS: RegExp[] = [
|
|
|
24
26
|
/\bzero typescript errors\b/i,
|
|
25
27
|
]
|
|
26
28
|
|
|
29
|
+
const INCOMPLETE_RESULT_PATTERNS: RegExp[] = [
|
|
30
|
+
/\b(?:next|then)\s*,?\s*i\s+(?:will|can|am going to)\b/i,
|
|
31
|
+
/\b(?:i(?:'| a)?ll|let me)\s+(?:start|begin|proceed|continue)\b/i,
|
|
32
|
+
/\b(?:once|when|after)\s+(?:the\s+)?(?:access|approval|permission)\s+(?:is|has been)\s+granted\b/i,
|
|
33
|
+
/\bneed (?:more )?(?:details|information|context)\b/i,
|
|
34
|
+
/\b(?:i|we)\s+(?:need|require)\s+(?:access|approval|permission)\b/i,
|
|
35
|
+
/\brequested\s+(?:access|approval|permission)\b/i,
|
|
36
|
+
/\bneed access to (?:the )?(?:shell|terminal|command line)\b/i,
|
|
37
|
+
/\battempted to\b[^.]{0,120}\b(?:but|however)\b/i,
|
|
38
|
+
/\bcould you provide\b/i,
|
|
39
|
+
/\blet me know once\b/i,
|
|
40
|
+
/\bthere (?:aren't|are not) any specific details\b/i,
|
|
41
|
+
]
|
|
42
|
+
|
|
27
43
|
const IMPLEMENTATION_HINT = /\b(add|build|create|fix|implement|integrat|refactor|update|write)\b/i
|
|
28
|
-
const
|
|
44
|
+
const EXECUTION_ACTION_HINT = /\b(changed|updated|added|modified|implemented|refactored|fixed|ran|executed|verified)\b/i
|
|
45
|
+
const COMMAND_EVIDENCE_HINT = /\b(npm|pnpm|yarn|bun|node|npx|pytest|vitest|jest|playwright|go test|cargo test|deno test|python|pip|uv|docker|git)\b/i
|
|
46
|
+
const FILE_PATH_EVIDENCE_HINT = /\b[\w./-]+\.(ts|tsx|js|jsx|mjs|cjs|json|md|css|scss|html|yml|yaml|sh|py|go|rs|java|kt|swift|rb|php|sql|txt)\b/i
|
|
47
|
+
const ARTIFACT_EVIDENCE_HINT = /(?:sandbox:)?\/api\/uploads\/[^\s)\]]+|https?:\/\/[^\s)\]]+\.(?:png|jpe?g|webp|gif|pdf|zip)\b/i
|
|
48
|
+
const VERIFICATION_EVIDENCE_HINT = /\b(test|tests|lint|typecheck|build)\b[^.]{0,40}\b(pass(?:ed)?|fail(?:ed)?|ok|success)\b/i
|
|
29
49
|
const SCREENSHOT_HINT = /\b(screenshot|screen shot|snapshot|capture)\b/i
|
|
30
50
|
const DELIVERY_HINT = /\b(send|deliver|return|share|upload|post|message)\b/i
|
|
31
51
|
const SCREENSHOT_ARTIFACT_HINT = /(?:sandbox:)?\/api\/uploads\/[^\s)\]]+|https?:\/\/[^\s)\]]+\.(?:png|jpe?g|webp|gif|pdf)\b/i
|
|
@@ -46,9 +66,14 @@ export function validateTaskCompletion(
|
|
|
46
66
|
const result = normalizeText(task.result)
|
|
47
67
|
const error = normalizeText(task.error)
|
|
48
68
|
const report = options.report || null
|
|
69
|
+
const hasExplicitQualityGate = !!task.qualityGate && typeof task.qualityGate === 'object'
|
|
70
|
+
const qualityGate = normalizeTaskQualityGate(task.qualityGate || null, options.settings || null)
|
|
49
71
|
const implementationTask = IMPLEMENTATION_HINT.test(title) || IMPLEMENTATION_HINT.test(description)
|
|
50
72
|
|
|
51
73
|
if (error) reasons.push('Task has a non-empty error field.')
|
|
74
|
+
if (/^untitled task$/i.test(title) && !description) {
|
|
75
|
+
reasons.push('Task metadata is too vague (untitled title with empty description).')
|
|
76
|
+
}
|
|
52
77
|
|
|
53
78
|
if (!result) reasons.push('Result summary is empty.')
|
|
54
79
|
else {
|
|
@@ -57,11 +82,20 @@ export function validateTaskCompletion(
|
|
|
57
82
|
if (WEAK_RESULT_PATTERNS.some((rx) => rx.test(result))) {
|
|
58
83
|
reasons.push('Result contains placeholder/planning language instead of completion evidence.')
|
|
59
84
|
}
|
|
85
|
+
if (INCOMPLETE_RESULT_PATTERNS.some((rx) => rx.test(result))) {
|
|
86
|
+
reasons.push('Result indicates unfinished work or missing inputs instead of completed execution.')
|
|
87
|
+
}
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
// If task description/title suggests implementation work, require concrete evidence in
|
|
63
91
|
// the result summary OR task report.
|
|
64
|
-
const hasResultEvidence =
|
|
92
|
+
const hasResultEvidence = (
|
|
93
|
+
COMMAND_EVIDENCE_HINT.test(result)
|
|
94
|
+
|| ARTIFACT_EVIDENCE_HINT.test(result)
|
|
95
|
+
|| VERIFICATION_EVIDENCE_HINT.test(result)
|
|
96
|
+
|| (EXECUTION_ACTION_HINT.test(result)
|
|
97
|
+
&& (/\b(command|test|lint|typecheck|build|file|artifact)\b/i.test(result) || FILE_PATH_EVIDENCE_HINT.test(result)))
|
|
98
|
+
)
|
|
65
99
|
const hasReportEvidence = report?.evidence.hasEvidence === true
|
|
66
100
|
if (implementationTask && !hasResultEvidence && !hasReportEvidence) {
|
|
67
101
|
if (report?.relativePath) {
|
|
@@ -80,6 +114,37 @@ export function validateTaskCompletion(
|
|
|
80
114
|
}
|
|
81
115
|
}
|
|
82
116
|
|
|
117
|
+
if (qualityGate.enabled && (implementationTask || hasExplicitQualityGate)) {
|
|
118
|
+
if (result && result.length < qualityGate.minResultChars) {
|
|
119
|
+
reasons.push(`Quality gate: result summary is shorter than required minimum (${result.length} chars; min ${qualityGate.minResultChars}).`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const hasCommandEvidence = COMMAND_EVIDENCE_HINT.test(result) || (report?.evidence.commandsRun.length || 0) > 0
|
|
123
|
+
const hasFileEvidence = FILE_PATH_EVIDENCE_HINT.test(result) || (report?.evidence.changedFiles.length || 0) > 0
|
|
124
|
+
const hasVerificationEvidence = VERIFICATION_EVIDENCE_HINT.test(result) || (report?.evidence.verification.length || 0) > 0
|
|
125
|
+
const hasArtifactEvidence = ARTIFACT_EVIDENCE_HINT.test(result) || ((task.artifacts?.length || 0) > 0)
|
|
126
|
+
|
|
127
|
+
const evidenceSignals = [
|
|
128
|
+
hasCommandEvidence,
|
|
129
|
+
hasFileEvidence,
|
|
130
|
+
hasVerificationEvidence,
|
|
131
|
+
hasArtifactEvidence,
|
|
132
|
+
].filter(Boolean).length
|
|
133
|
+
|
|
134
|
+
if (evidenceSignals < qualityGate.minEvidenceItems) {
|
|
135
|
+
reasons.push(`Quality gate: insufficient completion evidence (${evidenceSignals}/${qualityGate.minEvidenceItems} required evidence signals).`)
|
|
136
|
+
}
|
|
137
|
+
if (qualityGate.requireVerification && !hasVerificationEvidence) {
|
|
138
|
+
reasons.push('Quality gate: verification evidence is required (tests/lint/build/check output missing).')
|
|
139
|
+
}
|
|
140
|
+
if (qualityGate.requireArtifact && !hasArtifactEvidence) {
|
|
141
|
+
reasons.push('Quality gate: artifact evidence is required (artifact URL/upload or structured artifacts list missing).')
|
|
142
|
+
}
|
|
143
|
+
if (qualityGate.requireReport && !report?.relativePath) {
|
|
144
|
+
reasons.push('Quality gate: task completion report is required but missing.')
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
83
148
|
return {
|
|
84
149
|
ok: reasons.length === 0,
|
|
85
150
|
reasons,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const TOOL_ALIAS_GROUPS: string[][] = [
|
|
2
|
+
['shell', 'execute_command', 'process_tool', 'process'],
|
|
3
|
+
['files', 'read_file', 'write_file', 'list_files', 'copy_file', 'move_file', 'delete_file', 'send_file'],
|
|
4
|
+
['edit_file'],
|
|
5
|
+
['web', 'web_search', 'web_fetch'],
|
|
6
|
+
['browser', 'openclaw_browser'],
|
|
7
|
+
['delegate', 'claude_code', 'codex_cli', 'opencode_cli', 'delegate_to_claude_code', 'delegate_to_codex_cli', 'delegate_to_opencode_cli'],
|
|
8
|
+
['manage_platform', 'manage_agents', 'manage_tasks', 'manage_schedules', 'manage_skills', 'manage_documents', 'manage_webhooks', 'manage_secrets', 'manage_sessions'],
|
|
9
|
+
['manage_connectors', 'connectors', 'connector_message_tool'],
|
|
10
|
+
['manage_chatrooms', 'chatroom'],
|
|
11
|
+
['spawn_subagent', 'subagent', 'delegate_to_agent'],
|
|
12
|
+
['manage_sessions', 'session_info', 'sessions_tool', 'whoami_tool', 'search_history_tool'],
|
|
13
|
+
['schedule_wake', 'schedule'],
|
|
14
|
+
['http_request', 'http'],
|
|
15
|
+
['memory', 'memory_tool'],
|
|
16
|
+
['sandbox', 'sandbox_exec', 'sandbox_list_runtimes'],
|
|
17
|
+
['wallet', 'wallet_tool'],
|
|
18
|
+
['monitor', 'monitor_tool'],
|
|
19
|
+
['sample_ui', 'show_plugin_card'],
|
|
20
|
+
['context_mgmt', 'context_status', 'context_summarize'],
|
|
21
|
+
['openclaw_workspace'],
|
|
22
|
+
['openclaw_nodes'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const TOOL_ALIAS_MAP = (() => {
|
|
26
|
+
const map = new Map<string, Set<string>>()
|
|
27
|
+
for (const group of TOOL_ALIAS_GROUPS) {
|
|
28
|
+
const normalized = group.map((tool) => tool.trim().toLowerCase()).filter(Boolean)
|
|
29
|
+
for (const tool of normalized) {
|
|
30
|
+
const current = map.get(tool) || new Set<string>()
|
|
31
|
+
for (const alias of normalized) current.add(alias)
|
|
32
|
+
map.set(tool, current)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return map
|
|
36
|
+
})()
|
|
37
|
+
|
|
38
|
+
export function normalizeToolId(value: unknown): string {
|
|
39
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function expandToolIds(values: string[] | null | undefined): string[] {
|
|
43
|
+
if (!Array.isArray(values) || values.length === 0) return []
|
|
44
|
+
const expanded = new Set<string>()
|
|
45
|
+
const queue: string[] = values
|
|
46
|
+
.map((tool) => normalizeToolId(tool))
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
|
|
49
|
+
while (queue.length > 0) {
|
|
50
|
+
const next = queue.shift()!
|
|
51
|
+
if (expanded.has(next)) continue
|
|
52
|
+
expanded.add(next)
|
|
53
|
+
const aliases = TOOL_ALIAS_MAP.get(next)
|
|
54
|
+
if (!aliases) continue
|
|
55
|
+
for (const alias of aliases) {
|
|
56
|
+
if (!expanded.has(alias)) queue.push(alias)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return Array.from(expanded)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function toolIdMatches(enabledTools: string[] | null | undefined, toolId: string): boolean {
|
|
64
|
+
const normalized = normalizeToolId(toolId)
|
|
65
|
+
if (!normalized) return false
|
|
66
|
+
return expandToolIds(enabledTools).includes(normalized)
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -32,9 +32,9 @@ interface ToolDescriptor {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
|
|
35
|
-
shell: { categories: ['execution'], concreteTools: ['execute_command'] },
|
|
36
|
-
process: { categories: ['execution'], concreteTools: ['process_tool'] },
|
|
37
|
-
files: { categories: ['filesystem'], concreteTools: ['read_file', 'write_file', 'list_files', 'send_file'] },
|
|
35
|
+
shell: { categories: ['execution'], concreteTools: ['shell', 'execute_command'] },
|
|
36
|
+
process: { categories: ['execution'], concreteTools: ['process', 'process_tool'] },
|
|
37
|
+
files: { categories: ['filesystem'], concreteTools: ['files', 'read_file', 'write_file', 'list_files', 'send_file'] },
|
|
38
38
|
read_file: { categories: ['filesystem'], concreteTools: ['read_file'] },
|
|
39
39
|
write_file: { categories: ['filesystem'], concreteTools: ['write_file'] },
|
|
40
40
|
list_files: { categories: ['filesystem'], concreteTools: ['list_files'] },
|
|
@@ -43,13 +43,24 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
|
|
|
43
43
|
move_file: { categories: ['filesystem'], concreteTools: ['move_file'] },
|
|
44
44
|
edit_file: { categories: ['filesystem'], concreteTools: ['edit_file'] },
|
|
45
45
|
delete_file: { categories: ['filesystem'], concreteTools: ['delete_file'], destructive: true },
|
|
46
|
+
web: { categories: ['network'], concreteTools: ['web', 'web_search', 'web_fetch'] },
|
|
46
47
|
web_search: { categories: ['network'], concreteTools: ['web_search'] },
|
|
47
48
|
web_fetch: { categories: ['network'], concreteTools: ['web_fetch'] },
|
|
48
|
-
browser: { categories: ['browser', 'network'], concreteTools: ['browser'] },
|
|
49
|
+
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'] },
|
|
49
51
|
claude_code: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_claude_code'] },
|
|
50
52
|
codex_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_codex_cli'] },
|
|
51
53
|
opencode_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_opencode_cli'] },
|
|
52
|
-
memory: { categories: ['memory'], concreteTools: ['memory_tool', 'context_status', 'context_summarize'] },
|
|
54
|
+
memory: { categories: ['memory'], concreteTools: ['memory', 'memory_tool', 'context_status', 'context_summarize'] },
|
|
55
|
+
sandbox: { categories: ['execution', 'filesystem'], concreteTools: ['sandbox', 'sandbox_exec', 'sandbox_list_runtimes', 'openclaw_sandbox'] },
|
|
56
|
+
git: { categories: ['execution', 'filesystem'], concreteTools: ['git'] },
|
|
57
|
+
http_request: { categories: ['network'], concreteTools: ['http_request'] },
|
|
58
|
+
canvas: { categories: ['filesystem'], concreteTools: ['canvas'] },
|
|
59
|
+
wallet: { categories: ['outbound'], concreteTools: ['wallet', 'wallet_tool'] },
|
|
60
|
+
monitor: { categories: ['execution'], concreteTools: ['monitor', 'monitor_tool'] },
|
|
61
|
+
openclaw_workspace: { categories: ['filesystem', 'platform'], concreteTools: ['openclaw_workspace'] },
|
|
62
|
+
openclaw_nodes: { categories: ['platform'], concreteTools: ['openclaw_nodes'] },
|
|
63
|
+
manage_platform: { categories: ['platform'], concreteTools: ['manage_platform', 'manage_agents', 'manage_tasks', 'manage_schedules', 'manage_skills', 'manage_documents', 'manage_webhooks', 'manage_connectors', 'manage_sessions', 'manage_secrets'] },
|
|
53
64
|
manage_agents: { categories: ['platform'], concreteTools: ['manage_agents'] },
|
|
54
65
|
manage_tasks: { categories: ['platform'], concreteTools: ['manage_tasks'] },
|
|
55
66
|
manage_schedules: { categories: ['platform'], concreteTools: ['manage_schedules'] },
|
|
@@ -57,9 +68,16 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
|
|
|
57
68
|
manage_skills: { categories: ['platform'], concreteTools: ['manage_skills'] },
|
|
58
69
|
manage_documents: { categories: ['platform'], concreteTools: ['manage_documents'] },
|
|
59
70
|
manage_webhooks: { categories: ['platform', 'network'], concreteTools: ['manage_webhooks'] },
|
|
71
|
+
connectors: { categories: ['platform', 'outbound'], concreteTools: ['connectors', 'connector_message_tool'] },
|
|
60
72
|
manage_connectors: { categories: ['platform', 'outbound'], concreteTools: ['manage_connectors', 'connector_message_tool'] },
|
|
73
|
+
session_info: { categories: ['platform'], concreteTools: ['session_info', 'sessions_tool', 'search_history_tool', 'whoami_tool'] },
|
|
61
74
|
manage_sessions: { categories: ['platform'], concreteTools: ['manage_sessions', 'sessions_tool', 'search_history_tool', 'whoami_tool'] },
|
|
62
75
|
manage_secrets: { categories: ['platform'], concreteTools: ['manage_secrets'] },
|
|
76
|
+
manage_chatrooms: { categories: ['platform'], concreteTools: ['manage_chatrooms', 'chatroom'] },
|
|
77
|
+
spawn_subagent: { categories: ['delegation', 'platform'], concreteTools: ['spawn_subagent', 'delegate_to_agent'] },
|
|
78
|
+
context_mgmt: { categories: ['memory'], concreteTools: ['context_mgmt', 'context_status', 'context_summarize'] },
|
|
79
|
+
plugin_creator: { categories: ['filesystem', 'execution'], concreteTools: ['plugin_creator', 'plugin_creator_tool'] },
|
|
80
|
+
sample_ui: { categories: ['platform'], concreteTools: ['sample_ui', 'show_plugin_card'] },
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
const CONCRETE_TOOL_TO_SESSION_TOOL = new Map<string, string>()
|
package/src/lib/tasks.ts
CHANGED
|
@@ -4,7 +4,13 @@ import type { BoardTask } from '../types'
|
|
|
4
4
|
export const fetchTasks = (includeArchived = false) =>
|
|
5
5
|
api<Record<string, BoardTask>>('GET', `/tasks${includeArchived ? '?includeArchived=true' : ''}`)
|
|
6
6
|
|
|
7
|
-
export const createTask = (data: {
|
|
7
|
+
export const createTask = (data: {
|
|
8
|
+
title: string
|
|
9
|
+
description: string
|
|
10
|
+
agentId: string
|
|
11
|
+
status?: string
|
|
12
|
+
qualityGate?: BoardTask['qualityGate']
|
|
13
|
+
}) =>
|
|
8
14
|
api<BoardTask>('POST', '/tasks', data)
|
|
9
15
|
|
|
10
16
|
export const updateTask = (id: string, data: Partial<BoardTask>) =>
|
|
@@ -4,40 +4,39 @@ export interface ToolDefinition {
|
|
|
4
4
|
description: string
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Standard dynamic tools.
|
|
9
|
+
* Many granular tools (read_file, write_file, etc.) are now unified under 'files'.
|
|
10
|
+
*/
|
|
7
11
|
export const AVAILABLE_TOOLS: ToolDefinition[] = [
|
|
8
|
-
{ id: 'shell', label: 'Shell', description: 'Execute commands in the working directory' },
|
|
9
|
-
{ id: 'files', label: 'Files', description: '
|
|
10
|
-
{ id: '
|
|
11
|
-
{ id: '
|
|
12
|
-
{ id: '
|
|
13
|
-
{ id: 'edit_file', label: 'Edit File', description: 'Search-and-replace editing within files' },
|
|
14
|
-
{ id: 'process', label: 'Process', description: 'Monitor and control long-running shell commands' },
|
|
15
|
-
{ id: 'web_search', label: 'Web Search', description: 'Search the web via DuckDuckGo' },
|
|
16
|
-
{ id: 'web_fetch', label: 'Web Fetch', description: 'Fetch and extract text from URLs' },
|
|
17
|
-
{ id: 'claude_code', label: 'Claude Code', description: 'Delegate complex tasks to Claude Code CLI' },
|
|
18
|
-
{ id: 'codex_cli', label: 'Codex CLI', description: 'Delegate complex tasks to OpenAI Codex CLI' },
|
|
19
|
-
{ id: 'opencode_cli', label: 'OpenCode CLI', description: 'Delegate complex tasks to OpenCode CLI' },
|
|
12
|
+
{ id: 'shell', label: 'Shell', description: 'Execute commands in the working directory and manage background processes' },
|
|
13
|
+
{ id: 'files', label: 'Files', description: 'Complete file management: read, write, list, move, copy, delete, and send' },
|
|
14
|
+
{ id: 'edit_file', label: 'Edit File', description: 'Surgical search-and-replace within files' },
|
|
15
|
+
{ id: 'web', label: 'Web', description: 'Search the web via DuckDuckGo and fetch text from URLs' },
|
|
16
|
+
{ id: 'delegate', label: 'Delegate', description: 'Delegate complex tasks to specialized backends (Claude Code, Codex, OpenCode)' },
|
|
20
17
|
{ id: 'browser', label: 'Browser', description: 'Playwright — browse, scrape, interact with web pages' },
|
|
21
18
|
{ id: 'memory', label: 'Memory', description: 'Store and retrieve long-term memories across conversations' },
|
|
22
|
-
{ id: 'sandbox', label: 'Sandbox', description: '
|
|
19
|
+
{ id: 'sandbox', label: 'Sandbox', description: 'Secure isolated code execution for JS, TS, and Python' },
|
|
23
20
|
{ id: 'create_document', label: 'Create Document', description: 'Render markdown to PDF, HTML, or image' },
|
|
24
21
|
{ id: 'create_spreadsheet', label: 'Create Spreadsheet', description: 'Create Excel or CSV files from structured data' },
|
|
25
|
-
{ id: 'http_request', label: 'HTTP Request', description: 'Make HTTP API calls
|
|
22
|
+
{ id: 'http_request', label: 'HTTP Request', description: 'Make direct HTTP API calls with custom methods, headers, and bodies' },
|
|
26
23
|
{ id: 'git', label: 'Git', description: 'Run structured git operations (status, commit, push, diff, etc.)' },
|
|
27
24
|
{ id: 'wallet', label: 'Wallet', description: 'Manage agent crypto wallet — check balance, send SOL, view transactions' },
|
|
25
|
+
{ id: 'monitor', label: 'Monitor', description: 'System observability: check resource usage, watch logs, and ping endpoints' },
|
|
26
|
+
{ id: 'plugin_creator', label: 'Plugin Creator', description: 'Design, write, and test custom SwarmClaw plugins dynamically' },
|
|
27
|
+
{ id: 'sample_ui', label: 'Sample UI', description: 'Demonstration of dynamic UI injection into Sidebar and Chat Header' },
|
|
28
28
|
]
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Platform capability tools.
|
|
32
|
+
* Granular CRUD tools are now unified under 'manage_platform'.
|
|
33
|
+
*/
|
|
30
34
|
export const PLATFORM_TOOLS: ToolDefinition[] = [
|
|
31
|
-
{ id: '
|
|
32
|
-
{ id: '
|
|
33
|
-
{ id: '
|
|
35
|
+
{ id: 'manage_platform', label: 'Platform', description: 'Unified management of agents, tasks, schedules, skills, documents, and secrets' },
|
|
36
|
+
{ id: 'manage_connectors', label: 'Connectors', description: 'Manage chat platform bridges and send outbound messages' },
|
|
37
|
+
{ id: 'manage_chatrooms', label: 'Chatrooms', description: 'Manage SwarmClaw routing rules and multi-agent chatrooms' },
|
|
38
|
+
{ id: 'delegate_to_agent', label: 'Assign Agent', description: 'Delegate a task to another specific agent' },
|
|
34
39
|
{ id: 'schedule_wake', label: 'Reminders', description: 'Schedule a proactive wake event in the current chat' },
|
|
35
|
-
{ id: 'manage_skills', label: 'Skills', description: 'Create, edit, and delete skills' },
|
|
36
|
-
{ id: 'manage_documents', label: 'Documents', description: 'Upload, search, and delete indexed documents' },
|
|
37
|
-
{ id: 'manage_webhooks', label: 'Webhooks', description: 'Register webhooks that trigger agent workflows' },
|
|
38
|
-
{ id: 'manage_connectors', label: 'Connectors', description: 'Create, edit, and delete connectors' },
|
|
39
|
-
{ id: 'manage_sessions', label: 'Chats', description: 'List chats, send messages, and spawn agent work' },
|
|
40
|
-
{ id: 'manage_secrets', label: 'Secrets', description: 'Store and retrieve encrypted service secrets' },
|
|
41
40
|
]
|
|
42
41
|
|
|
43
42
|
export const ALL_TOOLS: ToolDefinition[] = [...AVAILABLE_TOOLS, ...PLATFORM_TOOLS]
|
|
@@ -46,3 +45,4 @@ export const ALL_TOOLS: ToolDefinition[] = [...AVAILABLE_TOOLS, ...PLATFORM_TOOL
|
|
|
46
45
|
export const TOOL_LABELS: Record<string, string> = Object.fromEntries(
|
|
47
46
|
ALL_TOOLS.map((t) => [t.id, t.label]),
|
|
48
47
|
)
|
|
48
|
+
|
|
@@ -15,6 +15,10 @@ export const AgentCreateSchema = z.object({
|
|
|
15
15
|
thinkingLevel: z.string().optional(),
|
|
16
16
|
soul: z.string().optional(),
|
|
17
17
|
autoRecovery: z.boolean().optional().default(false),
|
|
18
|
+
monthlyBudget: z.number().positive().nullable().optional().default(null),
|
|
19
|
+
dailyBudget: z.number().positive().nullable().optional().default(null),
|
|
20
|
+
hourlyBudget: z.number().positive().nullable().optional().default(null),
|
|
21
|
+
budgetAction: z.enum(['warn', 'block']).optional().default('warn'),
|
|
18
22
|
})
|
|
19
23
|
|
|
20
24
|
export const ConnectorCreateSchema = z.object({
|
|
@@ -46,6 +50,14 @@ export const TaskCreateSchema = z.object({
|
|
|
46
50
|
retryBackoffSec: z.number().optional(),
|
|
47
51
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
48
52
|
dueAt: z.number().nullable().optional(),
|
|
53
|
+
qualityGate: z.object({
|
|
54
|
+
enabled: z.boolean().optional(),
|
|
55
|
+
minResultChars: z.number().optional(),
|
|
56
|
+
minEvidenceItems: z.number().optional(),
|
|
57
|
+
requireVerification: z.boolean().optional(),
|
|
58
|
+
requireArtifact: z.boolean().optional(),
|
|
59
|
+
requireReport: z.boolean().optional(),
|
|
60
|
+
}).nullable().optional(),
|
|
49
61
|
})
|
|
50
62
|
|
|
51
63
|
export const ChatroomCreateSchema = z.object({
|
package/src/lib/view-routes.ts
CHANGED
|
@@ -1,31 +1,9 @@
|
|
|
1
1
|
import type { AppView } from '@/types'
|
|
2
|
+
import { VIEW_ROUTE_PATHS } from '../../view-route-paths'
|
|
2
3
|
|
|
3
4
|
export const DEFAULT_VIEW: AppView = 'home'
|
|
4
5
|
|
|
5
|
-
export const VIEW_TO_PATH: Record<AppView, string> =
|
|
6
|
-
home: '/',
|
|
7
|
-
agents: '/agents',
|
|
8
|
-
chatrooms: '/chatrooms',
|
|
9
|
-
schedules: '/schedules',
|
|
10
|
-
memory: '/memory',
|
|
11
|
-
tasks: '/tasks',
|
|
12
|
-
approvals: '/approvals',
|
|
13
|
-
secrets: '/secrets',
|
|
14
|
-
providers: '/providers',
|
|
15
|
-
skills: '/skills',
|
|
16
|
-
connectors: '/connectors',
|
|
17
|
-
webhooks: '/webhooks',
|
|
18
|
-
mcp_servers: '/mcp-servers',
|
|
19
|
-
knowledge: '/knowledge',
|
|
20
|
-
plugins: '/plugins',
|
|
21
|
-
usage: '/usage',
|
|
22
|
-
wallets: '/wallets',
|
|
23
|
-
runs: '/runs',
|
|
24
|
-
logs: '/logs',
|
|
25
|
-
settings: '/settings',
|
|
26
|
-
projects: '/projects',
|
|
27
|
-
activity: '/activity',
|
|
28
|
-
}
|
|
6
|
+
export const VIEW_TO_PATH: Record<AppView, string> = VIEW_ROUTE_PATHS
|
|
29
7
|
|
|
30
8
|
const entries = Object.entries(VIEW_TO_PATH) as [AppView, string][]
|
|
31
9
|
export const PATH_TO_VIEW: Record<string, AppView> = Object.fromEntries(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { create } from 'zustand'
|
|
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 } from '../types'
|
|
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
5
|
import { fetchSessions, fetchDirs, fetchProviders, fetchCredentials } from '../lib/sessions'
|
|
6
6
|
import { fetchAgents } from '../lib/agents'
|
|
7
7
|
import { fetchSchedules } from '../lib/schedules'
|
|
@@ -198,6 +198,11 @@ interface AppState {
|
|
|
198
198
|
lastReadTimestamps: Record<string, number>
|
|
199
199
|
markChatRead: (id: string) => void
|
|
200
200
|
|
|
201
|
+
// Approvals
|
|
202
|
+
approvals: Record<string, ApprovalRequest>
|
|
203
|
+
loadApprovals: () => Promise<void>
|
|
204
|
+
submitApprovalDecision: (id: string, approved: boolean) => Promise<void>
|
|
205
|
+
|
|
201
206
|
// Notifications
|
|
202
207
|
notifications: AppNotification[]
|
|
203
208
|
unreadNotificationCount: number
|
|
@@ -643,6 +648,23 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
643
648
|
safeStorageSet('sc_last_read', JSON.stringify(ts))
|
|
644
649
|
},
|
|
645
650
|
|
|
651
|
+
// Approvals
|
|
652
|
+
approvals: {},
|
|
653
|
+
loadApprovals: async () => {
|
|
654
|
+
try {
|
|
655
|
+
const list = await api<ApprovalRequest[]>('GET', '/approvals')
|
|
656
|
+
const approvals: Record<string, ApprovalRequest> = {}
|
|
657
|
+
for (const a of list) approvals[a.id] = a
|
|
658
|
+
set({ approvals })
|
|
659
|
+
} catch { /* ignore */ }
|
|
660
|
+
},
|
|
661
|
+
submitApprovalDecision: async (id, approved) => {
|
|
662
|
+
try {
|
|
663
|
+
await api('POST', '/approvals', { id, approved })
|
|
664
|
+
await get().loadApprovals()
|
|
665
|
+
} catch { /* ignore */ }
|
|
666
|
+
},
|
|
667
|
+
|
|
646
668
|
// Notifications
|
|
647
669
|
notifications: [],
|
|
648
670
|
unreadNotificationCount: 0,
|