@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.
- package/README.md +85 -139
- package/package.json +1 -1
- package/src/app/api/agents/[id]/thread/route.ts +1 -2
- package/src/app/api/agents/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/main-loop/route.ts +2 -2
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +4 -52
- package/src/app/api/{sessions → chats}/route.ts +5 -7
- package/src/app/api/plugins/route.ts +3 -0
- package/src/app/api/plugins/settings/route.ts +35 -0
- package/src/app/api/usage/route.ts +30 -0
- package/src/cli/index.js +35 -33
- package/src/cli/index.ts +40 -39
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-chat-list.tsx +3 -3
- package/src/components/agents/agent-list.tsx +8 -13
- package/src/components/agents/agent-sheet.tsx +2 -2
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +2 -2
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +10 -14
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +3 -3
- package/src/components/chat/chat-header.tsx +156 -73
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +4 -5
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +2 -2
- package/src/components/{sessions/new-session-sheet.tsx → chat/new-chat-sheet.tsx} +6 -6
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/connectors/connector-sheet.tsx +1 -1
- package/src/components/home/home-view.tsx +1 -1
- package/src/components/layout/app-layout.tsx +23 -2
- package/src/components/plugins/plugin-list.tsx +475 -254
- package/src/components/plugins/plugin-sheet.tsx +124 -10
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/command-palette.tsx +0 -1
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/settings-page.tsx +1 -12
- package/src/components/usage/metrics-dashboard.tsx +73 -0
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/approvals.ts +4 -4
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution.ts +36 -105
- package/src/lib/server/chatroom-helpers.ts +3 -3
- package/src/lib/server/connectors/manager.ts +4 -4
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +2 -2
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/main-agent-loop.ts +25 -160
- package/src/lib/server/main-session.ts +6 -13
- package/src/lib/server/orchestrator-lg.ts +3 -3
- package/src/lib/server/orchestrator.ts +5 -5
- package/src/lib/server/plugins.ts +112 -4
- package/src/lib/server/provider-health.ts +5 -3
- package/src/lib/server/queue.ts +12 -10
- package/src/lib/server/session-run-manager.test.ts +9 -6
- package/src/lib/server/session-run-manager.ts +1 -3
- package/src/lib/server/session-tools/calendar.ts +376 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +5 -2
- package/src/lib/server/session-tools/context.ts +7 -3
- package/src/lib/server/session-tools/crud.ts +14 -6
- package/src/lib/server/session-tools/delegate.ts +95 -8
- package/src/lib/server/session-tools/discovery.ts +2 -2
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +322 -0
- package/src/lib/server/session-tools/file.ts +5 -2
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/image-gen.ts +382 -0
- package/src/lib/server/session-tools/index.ts +74 -49
- package/src/lib/server/session-tools/memory.ts +139 -2
- package/src/lib/server/session-tools/monitor.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform.ts +6 -3
- package/src/lib/server/session-tools/plugin-creator.ts +3 -3
- package/src/lib/server/session-tools/replicate.ts +303 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +4 -2
- package/src/lib/server/session-tools/session-info.ts +7 -4
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +2 -2
- package/src/lib/server/session-tools/wallet.ts +29 -2
- package/src/lib/server/session-tools/web.ts +44 -5
- package/src/lib/server/storage.ts +29 -9
- package/src/lib/server/stream-agent-chat.ts +72 -249
- package/src/lib/server/tool-aliases.ts +26 -15
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +32 -27
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.ts +3 -1
- package/src/stores/use-app-store.ts +5 -5
- package/src/stores/use-chat-store.ts +7 -7
- package/src/types/index.ts +65 -3
- /package/src/app/api/{sessions → chats}/[id]/browser/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/chat/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/messages/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/stop/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
package/src/lib/server/queue.ts
CHANGED
|
@@ -4,14 +4,14 @@ import path from 'node:path'
|
|
|
4
4
|
import { loadTasks, saveTasks, loadQueue, saveQueue, loadAgents, loadSchedules, saveSchedules, loadSessions, saveSessions, loadSettings, loadConnectors, UPLOAD_DIR } from './storage'
|
|
5
5
|
import { notify } from './ws-hub'
|
|
6
6
|
import { WORKSPACE_DIR } from './data-dir'
|
|
7
|
-
import { createOrchestratorSession
|
|
7
|
+
import { createOrchestratorSession } from './orchestrator'
|
|
8
8
|
import { formatValidationFailure, validateTaskCompletion } from './task-validation'
|
|
9
9
|
import { ensureTaskCompletionReport } from './task-reports'
|
|
10
10
|
import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
11
11
|
import { executeSessionChatTurn } from './chat-execution'
|
|
12
12
|
import { extractTaskResult, formatResultBody } from './task-result'
|
|
13
13
|
import { getCheckpointSaver } from './langgraph-checkpoint'
|
|
14
|
-
import {
|
|
14
|
+
import { isMainLoopSession } from './main-session'
|
|
15
15
|
import { cascadeUnblock } from './dag-validation'
|
|
16
16
|
import { performGuardianRollback } from './guardian'
|
|
17
17
|
import type { Agent, BoardTask, Connector, Message } from '@/types'
|
|
@@ -283,7 +283,7 @@ function pushQueueUnique(queue: string[], id: string): void {
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
function isMainSession(session: SessionLike | null | undefined): boolean {
|
|
286
|
-
return
|
|
286
|
+
return isMainLoopSession(session)
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
function resolveTaskOwnerUser(task: ScheduleTaskMeta, sessions: Record<string, SessionLike>): string | null {
|
|
@@ -521,10 +521,8 @@ async function executeTaskRun(
|
|
|
521
521
|
'- Include concrete evidence in your final summary: changed file paths, commands run, and verification results.',
|
|
522
522
|
'- If blocked, state the blocker explicitly and what input or permission is missing.',
|
|
523
523
|
].join('\n')
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
524
|
+
// All agents (including orchestrators) go through the unified chat execution path.
|
|
525
|
+
// Agents with subAgentIds get delegation tools automatically via session-tools.
|
|
528
526
|
const run = await executeSessionChatTurn({
|
|
529
527
|
sessionId,
|
|
530
528
|
message: prompt,
|
|
@@ -773,6 +771,7 @@ function notifyAgentThreadTaskResult(task: BoardTask): void {
|
|
|
773
771
|
if (task.claudeResumeId) resumeLines.push(`Claude session: \`${task.claudeResumeId}\``)
|
|
774
772
|
if (task.codexResumeId) resumeLines.push(`Codex thread: \`${task.codexResumeId}\``)
|
|
775
773
|
if (task.opencodeResumeId) resumeLines.push(`OpenCode session: \`${task.opencodeResumeId}\``)
|
|
774
|
+
if (task.geminiResumeId) resumeLines.push(`Gemini session: \`${task.geminiResumeId}\``)
|
|
776
775
|
// Fallback to legacy field
|
|
777
776
|
if (resumeLines.length === 0 && task.cliResumeId) {
|
|
778
777
|
resumeLines.push(`${task.cliProvider || 'CLI'} session: \`${task.cliResumeId}\``)
|
|
@@ -1315,24 +1314,27 @@ export async function processNext() {
|
|
|
1315
1314
|
const execSession = execSessions[sessionId] as Record<string, unknown> | undefined
|
|
1316
1315
|
if (execSession) {
|
|
1317
1316
|
const delegateIds = execSession.delegateResumeIds as
|
|
1318
|
-
| { claudeCode?: string | null; codex?: string | null; opencode?: string | null }
|
|
1317
|
+
| { claudeCode?: string | null; codex?: string | null; opencode?: string | null; gemini?: string | null }
|
|
1319
1318
|
| undefined
|
|
1320
1319
|
// Store each CLI resume ID separately
|
|
1321
1320
|
const claudeId = (execSession.claudeSessionId as string) || delegateIds?.claudeCode || null
|
|
1322
1321
|
const codexId = (execSession.codexThreadId as string) || delegateIds?.codex || null
|
|
1323
1322
|
const opencodeId = (execSession.opencodeSessionId as string) || delegateIds?.opencode || null
|
|
1323
|
+
const geminiId = delegateIds?.gemini || null
|
|
1324
1324
|
if (claudeId) t2[taskId].claudeResumeId = claudeId
|
|
1325
1325
|
if (codexId) t2[taskId].codexResumeId = codexId
|
|
1326
1326
|
if (opencodeId) t2[taskId].opencodeResumeId = opencodeId
|
|
1327
|
+
if (geminiId) t2[taskId].geminiResumeId = geminiId
|
|
1327
1328
|
// Keep backward-compat single field (first available)
|
|
1328
|
-
const primaryId = claudeId || codexId || opencodeId
|
|
1329
|
+
const primaryId = claudeId || codexId || opencodeId || geminiId
|
|
1329
1330
|
if (primaryId) {
|
|
1330
1331
|
t2[taskId].cliResumeId = primaryId
|
|
1331
1332
|
if (claudeId) t2[taskId].cliProvider = 'claude-cli'
|
|
1332
1333
|
else if (codexId) t2[taskId].cliProvider = 'codex-cli'
|
|
1333
1334
|
else if (opencodeId) t2[taskId].cliProvider = 'opencode-cli'
|
|
1335
|
+
else if (geminiId) t2[taskId].cliProvider = 'gemini-cli'
|
|
1334
1336
|
}
|
|
1335
|
-
console.log(`[queue] CLI resume IDs for task ${taskId}: claude=${claudeId}, codex=${codexId}, opencode=${opencodeId}`)
|
|
1337
|
+
console.log(`[queue] CLI resume IDs for task ${taskId}: claude=${claudeId}, codex=${codexId}, opencode=${opencodeId}, gemini=${geminiId}`)
|
|
1336
1338
|
}
|
|
1337
1339
|
} catch (e) {
|
|
1338
1340
|
console.warn(`[queue] Failed to extract CLI resume IDs for task ${taskId}:`, e)
|
|
@@ -3,14 +3,10 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import { isMainMissionSession } from './session-run-manager'
|
|
4
4
|
|
|
5
5
|
describe('isMainMissionSession', () => {
|
|
6
|
-
it('accepts
|
|
7
|
-
assert.equal(isMainMissionSession({ id: 'main-user', name: '__main__' }), true)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('rejects human agent-thread sessions', () => {
|
|
6
|
+
it('accepts agent-thread sessions', () => {
|
|
11
7
|
assert.equal(
|
|
12
8
|
isMainMissionSession({ id: 'agent-thread-agent_coder-123', name: 'agent-thread:agent_coder', sessionType: 'human' }),
|
|
13
|
-
|
|
9
|
+
true,
|
|
14
10
|
)
|
|
15
11
|
})
|
|
16
12
|
|
|
@@ -20,4 +16,11 @@ describe('isMainMissionSession', () => {
|
|
|
20
16
|
true,
|
|
21
17
|
)
|
|
22
18
|
})
|
|
19
|
+
|
|
20
|
+
it('rejects regular chat sessions', () => {
|
|
21
|
+
assert.equal(
|
|
22
|
+
isMainMissionSession({ id: 'abc123', name: 'New Chat', sessionType: 'human' }),
|
|
23
|
+
false,
|
|
24
|
+
)
|
|
25
|
+
})
|
|
23
26
|
})
|
|
@@ -220,10 +220,8 @@ function scheduleMainLoopFollowup(sessionId: string, followup: MainLoopFollowupR
|
|
|
220
220
|
|
|
221
221
|
export function isMainMissionSession(session: Record<string, unknown>): boolean {
|
|
222
222
|
const id = typeof session.id === 'string' ? session.id.trim() : ''
|
|
223
|
-
const name = typeof session.name === 'string' ? session.name.trim() : ''
|
|
224
223
|
const sessionType = typeof session.sessionType === 'string' ? session.sessionType : ''
|
|
225
|
-
if (id.startsWith('
|
|
226
|
-
// Only orchestrated thread sessions should receive autonomous main-loop followups.
|
|
224
|
+
if (id.startsWith('agent-thread-')) return true
|
|
227
225
|
if (sessionType === 'orchestrated') return true
|
|
228
226
|
return false
|
|
229
227
|
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
4
|
+
import { getPluginManager } from '../plugins'
|
|
5
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
6
|
+
import { loadSettings } from '../storage'
|
|
7
|
+
import type { ToolBuildContext } from './context'
|
|
8
|
+
|
|
9
|
+
type CalendarProvider = 'google' | 'outlook'
|
|
10
|
+
|
|
11
|
+
interface CalendarConfig {
|
|
12
|
+
provider: CalendarProvider
|
|
13
|
+
accessToken: string
|
|
14
|
+
calendarId: string
|
|
15
|
+
refreshToken: string
|
|
16
|
+
clientId: string
|
|
17
|
+
clientSecret: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getConfig(): CalendarConfig {
|
|
21
|
+
const settings = loadSettings()
|
|
22
|
+
const ps = (settings.pluginSettings as Record<string, Record<string, unknown>> | undefined)?.calendar ?? {}
|
|
23
|
+
return {
|
|
24
|
+
provider: (ps.provider as CalendarProvider) || 'google',
|
|
25
|
+
accessToken: (ps.accessToken as string) || '',
|
|
26
|
+
calendarId: (ps.calendarId as string) || 'primary',
|
|
27
|
+
refreshToken: (ps.refreshToken as string) || '',
|
|
28
|
+
clientId: (ps.clientId as string) || '',
|
|
29
|
+
clientSecret: (ps.clientSecret as string) || '',
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Try to refresh the Google OAuth access token using the refresh token. */
|
|
34
|
+
async function refreshGoogleToken(cfg: CalendarConfig): Promise<string | null> {
|
|
35
|
+
if (!cfg.refreshToken || !cfg.clientId || !cfg.clientSecret) return null
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
40
|
+
body: new URLSearchParams({
|
|
41
|
+
grant_type: 'refresh_token',
|
|
42
|
+
refresh_token: cfg.refreshToken,
|
|
43
|
+
client_id: cfg.clientId,
|
|
44
|
+
client_secret: cfg.clientSecret,
|
|
45
|
+
}),
|
|
46
|
+
signal: AbortSignal.timeout(10_000),
|
|
47
|
+
})
|
|
48
|
+
if (!res.ok) return null
|
|
49
|
+
const data = await res.json()
|
|
50
|
+
const newToken = data?.access_token as string | undefined
|
|
51
|
+
if (newToken) {
|
|
52
|
+
// Persist the refreshed token
|
|
53
|
+
const settings = loadSettings()
|
|
54
|
+
const pluginSettings = (settings.pluginSettings as Record<string, Record<string, unknown>> | undefined) ?? {}
|
|
55
|
+
const calSettings = pluginSettings.calendar ?? {}
|
|
56
|
+
calSettings.accessToken = newToken
|
|
57
|
+
pluginSettings.calendar = calSettings
|
|
58
|
+
settings.pluginSettings = pluginSettings
|
|
59
|
+
const { saveSettings } = await import('../storage')
|
|
60
|
+
saveSettings(settings)
|
|
61
|
+
}
|
|
62
|
+
return newToken || null
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function googleRequest(method: string, urlPath: string, cfg: CalendarConfig, body?: unknown): Promise<{ ok: boolean; data?: unknown; error?: string }> {
|
|
69
|
+
let token = cfg.accessToken
|
|
70
|
+
const baseUrl = 'https://www.googleapis.com/calendar/v3'
|
|
71
|
+
|
|
72
|
+
const doFetch = async (t: string) => {
|
|
73
|
+
const init: RequestInit = {
|
|
74
|
+
method,
|
|
75
|
+
headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' },
|
|
76
|
+
signal: AbortSignal.timeout(15_000),
|
|
77
|
+
}
|
|
78
|
+
if (body && method !== 'GET' && method !== 'DELETE') init.body = JSON.stringify(body)
|
|
79
|
+
return fetch(`${baseUrl}${urlPath}`, init)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let res = await doFetch(token)
|
|
83
|
+
if (res.status === 401) {
|
|
84
|
+
const refreshed = await refreshGoogleToken(cfg)
|
|
85
|
+
if (refreshed) {
|
|
86
|
+
token = refreshed
|
|
87
|
+
res = await doFetch(token)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
const errText = await res.text().catch(() => '')
|
|
93
|
+
return { ok: false, error: `Google Calendar ${res.status}: ${errText.slice(0, 300)}` }
|
|
94
|
+
}
|
|
95
|
+
if (method === 'DELETE') return { ok: true }
|
|
96
|
+
const data = await res.json()
|
|
97
|
+
return { ok: true, data }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function outlookRequest(method: string, urlPath: string, cfg: CalendarConfig, body?: unknown): Promise<{ ok: boolean; data?: unknown; error?: string }> {
|
|
101
|
+
const baseUrl = 'https://graph.microsoft.com/v1.0/me'
|
|
102
|
+
const init: RequestInit = {
|
|
103
|
+
method,
|
|
104
|
+
headers: { Authorization: `Bearer ${cfg.accessToken}`, 'Content-Type': 'application/json' },
|
|
105
|
+
signal: AbortSignal.timeout(15_000),
|
|
106
|
+
}
|
|
107
|
+
if (body && method !== 'GET' && method !== 'DELETE') init.body = JSON.stringify(body)
|
|
108
|
+
const res = await fetch(`${baseUrl}${urlPath}`, init)
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const errText = await res.text().catch(() => '')
|
|
111
|
+
return { ok: false, error: `Outlook ${res.status}: ${errText.slice(0, 300)}` }
|
|
112
|
+
}
|
|
113
|
+
if (method === 'DELETE') return { ok: true }
|
|
114
|
+
const data = await res.json()
|
|
115
|
+
return { ok: true, data }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatEvent(e: Record<string, unknown>): Record<string, unknown> {
|
|
119
|
+
return {
|
|
120
|
+
id: e.id,
|
|
121
|
+
summary: e.summary ?? e.subject,
|
|
122
|
+
start: (e.start as Record<string, unknown>)?.dateTime ?? (e.start as Record<string, unknown>)?.date ?? e.start,
|
|
123
|
+
end: (e.end as Record<string, unknown>)?.dateTime ?? (e.end as Record<string, unknown>)?.date ?? e.end,
|
|
124
|
+
location: e.location ?? (e.location as unknown as Record<string, unknown>)?.displayName,
|
|
125
|
+
description: typeof e.description === 'string' ? e.description.slice(0, 200) : (e.body as Record<string, unknown>)?.content?.toString().slice(0, 200),
|
|
126
|
+
status: e.status ?? e.showAs,
|
|
127
|
+
htmlLink: e.htmlLink ?? e.webLink,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function executeCalendar(args: Record<string, unknown>): Promise<string> {
|
|
132
|
+
const normalized = normalizeToolInputArgs(args)
|
|
133
|
+
const action = String(normalized.action || 'list')
|
|
134
|
+
const cfg = getConfig()
|
|
135
|
+
|
|
136
|
+
if (!cfg.accessToken) {
|
|
137
|
+
return 'Error: Calendar not configured. Ask the user to add their access token in Plugin Settings > Calendar.'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
switch (action) {
|
|
142
|
+
case 'list': {
|
|
143
|
+
const timeMin = String(normalized.timeMin || new Date().toISOString())
|
|
144
|
+
const timeMax = normalized.timeMax as string | undefined
|
|
145
|
+
const maxResults = Math.min(Number(normalized.maxResults) || 20, 50)
|
|
146
|
+
|
|
147
|
+
if (cfg.provider === 'outlook') {
|
|
148
|
+
const params = new URLSearchParams({
|
|
149
|
+
$top: String(maxResults),
|
|
150
|
+
$orderby: 'start/dateTime',
|
|
151
|
+
$filter: `start/dateTime ge '${timeMin}'${timeMax ? ` and end/dateTime le '${timeMax}'` : ''}`,
|
|
152
|
+
})
|
|
153
|
+
const r = await outlookRequest('GET', `/calendar/events?${params}`, cfg)
|
|
154
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
155
|
+
const events = ((r.data as Record<string, unknown>)?.value as Record<string, unknown>[]) ?? []
|
|
156
|
+
return JSON.stringify(events.map(formatEvent))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const params = new URLSearchParams({
|
|
160
|
+
timeMin,
|
|
161
|
+
maxResults: String(maxResults),
|
|
162
|
+
singleEvents: 'true',
|
|
163
|
+
orderBy: 'startTime',
|
|
164
|
+
})
|
|
165
|
+
if (timeMax) params.set('timeMax', timeMax)
|
|
166
|
+
const r = await googleRequest('GET', `/calendars/${encodeURIComponent(cfg.calendarId)}/events?${params}`, cfg)
|
|
167
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
168
|
+
const events = ((r.data as Record<string, unknown>)?.items as Record<string, unknown>[]) ?? []
|
|
169
|
+
return JSON.stringify(events.map(formatEvent))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case 'create': {
|
|
173
|
+
const summary = String(normalized.summary || normalized.title || '').trim()
|
|
174
|
+
if (!summary) return 'Error: "summary" (event title) is required.'
|
|
175
|
+
const start = String(normalized.start || '').trim()
|
|
176
|
+
const end = String(normalized.end || '').trim()
|
|
177
|
+
if (!start) return 'Error: "start" (ISO datetime) is required.'
|
|
178
|
+
|
|
179
|
+
const description = (normalized.description as string) || ''
|
|
180
|
+
const location = (normalized.location as string) || ''
|
|
181
|
+
|
|
182
|
+
if (cfg.provider === 'outlook') {
|
|
183
|
+
const body = {
|
|
184
|
+
subject: summary,
|
|
185
|
+
body: { contentType: 'text', content: description },
|
|
186
|
+
start: { dateTime: start, timeZone: 'UTC' },
|
|
187
|
+
end: { dateTime: end || new Date(new Date(start).getTime() + 3600_000).toISOString(), timeZone: 'UTC' },
|
|
188
|
+
location: { displayName: location },
|
|
189
|
+
}
|
|
190
|
+
const r = await outlookRequest('POST', '/calendar/events', cfg, body)
|
|
191
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
192
|
+
return `Event created: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const body = {
|
|
196
|
+
summary,
|
|
197
|
+
description,
|
|
198
|
+
location,
|
|
199
|
+
start: { dateTime: start, timeZone: 'UTC' },
|
|
200
|
+
end: { dateTime: end || new Date(new Date(start).getTime() + 3600_000).toISOString(), timeZone: 'UTC' },
|
|
201
|
+
}
|
|
202
|
+
const r = await googleRequest('POST', `/calendars/${encodeURIComponent(cfg.calendarId)}/events`, cfg, body)
|
|
203
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
204
|
+
return `Event created: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'update': {
|
|
208
|
+
const eventId = String(normalized.eventId || normalized.id || '').trim()
|
|
209
|
+
if (!eventId) return 'Error: "eventId" is required.'
|
|
210
|
+
const updates: Record<string, unknown> = {}
|
|
211
|
+
if (normalized.summary) updates.summary = String(normalized.summary)
|
|
212
|
+
if (normalized.description) updates.description = String(normalized.description)
|
|
213
|
+
if (normalized.location) updates.location = String(normalized.location)
|
|
214
|
+
if (normalized.start) updates.start = { dateTime: String(normalized.start), timeZone: 'UTC' }
|
|
215
|
+
if (normalized.end) updates.end = { dateTime: String(normalized.end), timeZone: 'UTC' }
|
|
216
|
+
|
|
217
|
+
if (cfg.provider === 'outlook') {
|
|
218
|
+
const outlookUpdates: Record<string, unknown> = {}
|
|
219
|
+
if (normalized.summary) outlookUpdates.subject = String(normalized.summary)
|
|
220
|
+
if (normalized.description) outlookUpdates.body = { contentType: 'text', content: String(normalized.description) }
|
|
221
|
+
if (normalized.location) outlookUpdates.location = { displayName: String(normalized.location) }
|
|
222
|
+
if (normalized.start) outlookUpdates.start = { dateTime: String(normalized.start), timeZone: 'UTC' }
|
|
223
|
+
if (normalized.end) outlookUpdates.end = { dateTime: String(normalized.end), timeZone: 'UTC' }
|
|
224
|
+
const r = await outlookRequest('PATCH', `/calendar/events/${eventId}`, cfg, outlookUpdates)
|
|
225
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
226
|
+
return `Event updated: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const r = await googleRequest('PATCH', `/calendars/${encodeURIComponent(cfg.calendarId)}/events/${eventId}`, cfg, updates)
|
|
230
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
231
|
+
return `Event updated: ${JSON.stringify(formatEvent(r.data as Record<string, unknown>))}`
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case 'delete': {
|
|
235
|
+
const eventId = String(normalized.eventId || normalized.id || '').trim()
|
|
236
|
+
if (!eventId) return 'Error: "eventId" is required.'
|
|
237
|
+
|
|
238
|
+
if (cfg.provider === 'outlook') {
|
|
239
|
+
const r = await outlookRequest('DELETE', `/calendar/events/${eventId}`, cfg)
|
|
240
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
241
|
+
return `Event ${eventId} deleted.`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const r = await googleRequest('DELETE', `/calendars/${encodeURIComponent(cfg.calendarId)}/events/${eventId}`, cfg)
|
|
245
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
246
|
+
return `Event ${eventId} deleted.`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'status': {
|
|
250
|
+
return JSON.stringify({
|
|
251
|
+
configured: true,
|
|
252
|
+
provider: cfg.provider,
|
|
253
|
+
calendarId: cfg.calendarId,
|
|
254
|
+
hasRefreshToken: !!cfg.refreshToken,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
default:
|
|
259
|
+
return `Error: Unknown action "${action}". Use: list, create, update, delete, status.`
|
|
260
|
+
}
|
|
261
|
+
} catch (err: unknown) {
|
|
262
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const CalendarPlugin: Plugin = {
|
|
267
|
+
name: 'Calendar',
|
|
268
|
+
enabledByDefault: false,
|
|
269
|
+
description: 'Manage Google Calendar or Outlook calendar events — list, create, update, delete.',
|
|
270
|
+
hooks: {
|
|
271
|
+
getCapabilityDescription: () =>
|
|
272
|
+
'I can manage calendar events using `calendar`: list upcoming events, create new ones, update or delete existing events. Supports Google Calendar and Outlook.',
|
|
273
|
+
} as PluginHooks,
|
|
274
|
+
tools: [
|
|
275
|
+
{
|
|
276
|
+
name: 'calendar',
|
|
277
|
+
description: 'Manage calendar events. Actions: list (upcoming events), create (new event), update (modify event), delete (remove event), status (check config).',
|
|
278
|
+
parameters: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
action: { type: 'string', enum: ['list', 'create', 'update', 'delete', 'status'], description: 'Action to perform' },
|
|
282
|
+
summary: { type: 'string', description: 'Event title (for create/update)' },
|
|
283
|
+
description: { type: 'string', description: 'Event description (for create/update)' },
|
|
284
|
+
location: { type: 'string', description: 'Event location (for create/update)' },
|
|
285
|
+
start: { type: 'string', description: 'Start datetime in ISO 8601 format (for create/update)' },
|
|
286
|
+
end: { type: 'string', description: 'End datetime in ISO 8601 format (for create/update). Defaults to 1 hour after start.' },
|
|
287
|
+
eventId: { type: 'string', description: 'Event ID (for update/delete)' },
|
|
288
|
+
timeMin: { type: 'string', description: 'List events starting from this ISO datetime (default: now)' },
|
|
289
|
+
timeMax: { type: 'string', description: 'List events up to this ISO datetime' },
|
|
290
|
+
maxResults: { type: 'number', description: 'Max events to return (default: 20, max: 50)' },
|
|
291
|
+
},
|
|
292
|
+
required: ['action'],
|
|
293
|
+
},
|
|
294
|
+
execute: async (args) => executeCalendar(args),
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
ui: {
|
|
298
|
+
settingsFields: [
|
|
299
|
+
{
|
|
300
|
+
key: 'provider',
|
|
301
|
+
label: 'Calendar Provider',
|
|
302
|
+
type: 'select',
|
|
303
|
+
options: [
|
|
304
|
+
{ value: 'google', label: 'Google Calendar' },
|
|
305
|
+
{ value: 'outlook', label: 'Microsoft Outlook' },
|
|
306
|
+
],
|
|
307
|
+
defaultValue: 'google',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
key: 'accessToken',
|
|
311
|
+
label: 'Access Token',
|
|
312
|
+
type: 'secret',
|
|
313
|
+
required: true,
|
|
314
|
+
placeholder: 'ya29.a0...',
|
|
315
|
+
help: 'OAuth2 access token for the calendar API. For Google: generate via OAuth2 playground or a service account.',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
key: 'refreshToken',
|
|
319
|
+
label: 'Refresh Token (Google)',
|
|
320
|
+
type: 'secret',
|
|
321
|
+
placeholder: '1//0e...',
|
|
322
|
+
help: 'Google OAuth2 refresh token. When set, the plugin auto-refreshes expired access tokens.',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: 'clientId',
|
|
326
|
+
label: 'Client ID (Google)',
|
|
327
|
+
type: 'text',
|
|
328
|
+
placeholder: '123456789.apps.googleusercontent.com',
|
|
329
|
+
help: 'Google OAuth2 client ID. Required for token refresh.',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
key: 'clientSecret',
|
|
333
|
+
label: 'Client Secret (Google)',
|
|
334
|
+
type: 'secret',
|
|
335
|
+
placeholder: 'GOCSPX-...',
|
|
336
|
+
help: 'Google OAuth2 client secret. Required for token refresh.',
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
key: 'calendarId',
|
|
340
|
+
label: 'Calendar ID',
|
|
341
|
+
type: 'text',
|
|
342
|
+
defaultValue: 'primary',
|
|
343
|
+
placeholder: 'primary',
|
|
344
|
+
help: 'Google Calendar ID (default: "primary"). For Outlook, this is ignored.',
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getPluginManager().registerBuiltin('calendar', CalendarPlugin)
|
|
351
|
+
|
|
352
|
+
export function buildCalendarTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
353
|
+
if (!bctx.hasPlugin('calendar')) return []
|
|
354
|
+
|
|
355
|
+
return [
|
|
356
|
+
tool(
|
|
357
|
+
async (args) => executeCalendar(args),
|
|
358
|
+
{
|
|
359
|
+
name: 'calendar',
|
|
360
|
+
description: CalendarPlugin.tools![0].description,
|
|
361
|
+
schema: z.object({
|
|
362
|
+
action: z.enum(['list', 'create', 'update', 'delete', 'status']).describe('Action to perform'),
|
|
363
|
+
summary: z.string().optional().describe('Event title'),
|
|
364
|
+
description: z.string().optional().describe('Event description'),
|
|
365
|
+
location: z.string().optional().describe('Event location'),
|
|
366
|
+
start: z.string().optional().describe('Start datetime (ISO 8601)'),
|
|
367
|
+
end: z.string().optional().describe('End datetime (ISO 8601)'),
|
|
368
|
+
eventId: z.string().optional().describe('Event ID (for update/delete)'),
|
|
369
|
+
timeMin: z.string().optional().describe('List events from this datetime'),
|
|
370
|
+
timeMax: z.string().optional().describe('List events until this datetime'),
|
|
371
|
+
maxResults: z.number().optional().describe('Max results (default 20, max 50)'),
|
|
372
|
+
}),
|
|
373
|
+
},
|
|
374
|
+
),
|
|
375
|
+
]
|
|
376
|
+
}
|
|
@@ -88,7 +88,7 @@ getPluginManager().registerBuiltin('canvas', CanvasPlugin)
|
|
|
88
88
|
* Legacy Bridge
|
|
89
89
|
*/
|
|
90
90
|
export function buildCanvasTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
91
|
-
if (!bctx.
|
|
91
|
+
if (!bctx.hasPlugin('canvas')) return []
|
|
92
92
|
return [
|
|
93
93
|
tool(
|
|
94
94
|
async (args) => executeCanvasAction(args, { sessionId: bctx.ctx?.sessionId || undefined }),
|
|
@@ -107,7 +107,9 @@ async function executeChatroomAction(args: Record<string, unknown>, context: { a
|
|
|
107
107
|
const ChatroomPlugin: Plugin = {
|
|
108
108
|
name: 'Core Chatrooms',
|
|
109
109
|
description: 'Manage SwarmClaw routing rules and multi-agent chatrooms.',
|
|
110
|
-
hooks: {
|
|
110
|
+
hooks: {
|
|
111
|
+
getCapabilityDescription: () => 'I can create and participate in chatrooms (`manage_chatrooms`) for multi-agent collaboration with @mention-based discussions.',
|
|
112
|
+
} as PluginHooks,
|
|
111
113
|
tools: [
|
|
112
114
|
{
|
|
113
115
|
name: 'manage_chatrooms',
|
|
@@ -134,7 +136,7 @@ getPluginManager().registerBuiltin('chatroom', ChatroomPlugin)
|
|
|
134
136
|
* Legacy Bridge
|
|
135
137
|
*/
|
|
136
138
|
export function buildChatroomTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
137
|
-
if (!bctx.
|
|
139
|
+
if (!bctx.hasPlugin('manage_chatrooms')) return []
|
|
138
140
|
return [
|
|
139
141
|
tool(
|
|
140
142
|
async (args) => executeChatroomAction(args, { agentId: bctx.ctx?.agentId }),
|
|
@@ -429,7 +429,10 @@ async function executeConnectorAction(input: ConnectorActionInput, bctx: Connect
|
|
|
429
429
|
const ConnectorPlugin: Plugin = {
|
|
430
430
|
name: 'Core Connectors',
|
|
431
431
|
description: 'Manage and send messages through chat platform connectors (WhatsApp, Telegram, Slack, etc.).',
|
|
432
|
-
hooks: {
|
|
432
|
+
hooks: {
|
|
433
|
+
getCapabilityDescription: () => 'I can manage messaging channels (`manage_connectors`) — WhatsApp, Telegram, Slack, Discord — and send proactive messages via `connector_message_tool`.',
|
|
434
|
+
getOperatingGuidance: () => 'Connectors: proactive outreach for significant events only. Keep messages concise, no duplicates.',
|
|
435
|
+
} as PluginHooks,
|
|
433
436
|
tools: [
|
|
434
437
|
{
|
|
435
438
|
name: 'connector_message_tool',
|
|
@@ -456,7 +459,7 @@ getPluginManager().registerBuiltin('connectors', ConnectorPlugin)
|
|
|
456
459
|
* Legacy Bridge
|
|
457
460
|
*/
|
|
458
461
|
export function buildConnectorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
459
|
-
if (!bctx.
|
|
462
|
+
if (!bctx.hasPlugin('manage_connectors')) return []
|
|
460
463
|
return [
|
|
461
464
|
tool(
|
|
462
465
|
async (args) => executeConnectorAction(args as ConnectorActionInput, bctx),
|
|
@@ -14,20 +14,24 @@ export interface ToolContext {
|
|
|
14
14
|
export interface SessionToolsResult {
|
|
15
15
|
tools: StructuredToolInterface[]
|
|
16
16
|
cleanup: () => Promise<void>
|
|
17
|
+
/** Maps tool name → plugin ID for attribution in usage tracking */
|
|
18
|
+
toolToPluginMap: Record<string, string>
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface ToolBuildContext {
|
|
20
22
|
cwd: string
|
|
21
23
|
ctx: ToolContext | undefined
|
|
24
|
+
hasPlugin: (name: string) => boolean
|
|
25
|
+
/** @deprecated Use hasPlugin */
|
|
22
26
|
hasTool: (name: string) => boolean
|
|
23
27
|
cleanupFns: (() => Promise<void>)[]
|
|
24
28
|
commandTimeoutMs: number
|
|
25
29
|
claudeTimeoutMs: number
|
|
26
30
|
cliProcessTimeoutMs: number
|
|
27
|
-
persistDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode', id: string | null | undefined) => void
|
|
28
|
-
readStoredDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode') => string | null
|
|
31
|
+
persistDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini', id: string | null | undefined) => void
|
|
32
|
+
readStoredDelegateResumeId: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini') => string | null
|
|
29
33
|
resolveCurrentSession: () => any | null
|
|
30
|
-
|
|
34
|
+
activePlugins: string[]
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export function safePath(cwd: string, filePath: string): string {
|
|
@@ -233,12 +233,12 @@ const PLATFORM_RESOURCES: Record<string, {
|
|
|
233
233
|
|
|
234
234
|
export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
235
235
|
const tools: StructuredToolInterface[] = []
|
|
236
|
-
const { cwd, ctx,
|
|
236
|
+
const { cwd, ctx, hasPlugin } = bctx
|
|
237
237
|
|
|
238
238
|
// Build dynamic agent summary for tools that need agent awareness
|
|
239
239
|
const assignScope = ctx?.platformAssignScope || 'self'
|
|
240
240
|
let agentSummary = ''
|
|
241
|
-
if (
|
|
241
|
+
if (hasPlugin('manage_tasks') || hasPlugin('manage_schedules')) {
|
|
242
242
|
if (assignScope === 'all') {
|
|
243
243
|
try {
|
|
244
244
|
const agents = loadAgents()
|
|
@@ -251,14 +251,14 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
for (const [toolKey, res] of Object.entries(PLATFORM_RESOURCES)) {
|
|
254
|
-
if (!
|
|
254
|
+
if (!hasPlugin(toolKey)) continue
|
|
255
255
|
|
|
256
256
|
let description = `Manage SwarmClaw ${res.label}. ${res.readOnly ? 'List and get only.' : 'List, get, create, update, or delete.'} Returns JSON.`
|
|
257
257
|
if (toolKey === 'manage_tasks') {
|
|
258
258
|
if (assignScope === 'self') {
|
|
259
|
-
description += `\n\
|
|
259
|
+
description += `\n\nDo NOT create tasks for yourself — just do the work directly. Tasks are for delegating work to other agents or for user-created work items. You can only list, get, update status, or complete tasks assigned to you ("${ctx?.agentId || 'unknown'}"). Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.`
|
|
260
260
|
} else {
|
|
261
|
-
description += `\n\
|
|
261
|
+
description += `\n\nDo NOT create tasks for yourself — just do the work directly. Only create tasks to delegate work to OTHER agents. Your agent ID is "${ctx?.agentId || 'unknown'}". Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.` + agentSummary
|
|
262
262
|
}
|
|
263
263
|
} else if (toolKey === 'manage_agents') {
|
|
264
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.`
|
|
@@ -396,6 +396,14 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
396
396
|
agents,
|
|
397
397
|
)
|
|
398
398
|
}
|
|
399
|
+
// Agents cannot create tasks for themselves — just do the work directly.
|
|
400
|
+
// Tasks are for delegating to other agents or user-created work items.
|
|
401
|
+
if (toolKey === 'manage_tasks' && ctx?.agentId) {
|
|
402
|
+
const resolvedAgentId = parsed.agentId || ctx.agentId
|
|
403
|
+
if (resolvedAgentId === ctx.agentId) {
|
|
404
|
+
return 'Error: You cannot create tasks for yourself — just do the work directly. Tasks are for delegating work to other agents. If you need to track progress, use memory instead.'
|
|
405
|
+
}
|
|
406
|
+
}
|
|
399
407
|
if (toolKey === 'manage_tasks') {
|
|
400
408
|
parsed.title = deriveTaskTitle(parsed)
|
|
401
409
|
if (!parsed.title || /^untitled task$/i.test(parsed.title)) {
|
|
@@ -598,7 +606,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
598
606
|
)
|
|
599
607
|
}
|
|
600
608
|
|
|
601
|
-
if (
|
|
609
|
+
if (hasPlugin('manage_documents')) {
|
|
602
610
|
tools.push(
|
|
603
611
|
tool(
|
|
604
612
|
async (rawArgs) => {
|