@swarmclawai/swarmclaw 0.7.7 → 0.8.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 +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +249 -14
|
@@ -7,6 +7,7 @@ import { spawnSync } from 'child_process'
|
|
|
7
7
|
import * as cheerio from 'cheerio'
|
|
8
8
|
import {
|
|
9
9
|
loadAgents, saveAgents,
|
|
10
|
+
loadProjects, saveProjects,
|
|
10
11
|
loadTasks, saveTasks,
|
|
11
12
|
loadSchedules, saveSchedules,
|
|
12
13
|
loadSkills, saveSkills,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
decryptKey,
|
|
21
22
|
} from '../storage'
|
|
22
23
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
23
|
-
import { findDuplicateSchedule, type ScheduleLike } from '@/lib/schedule-dedupe'
|
|
24
|
+
import { findDuplicateSchedule, findEquivalentSchedules, type ScheduleLike } from '@/lib/schedule-dedupe'
|
|
24
25
|
import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
|
|
25
26
|
import {
|
|
26
27
|
hasManagedAgentAssignmentInput,
|
|
@@ -31,9 +32,11 @@ import {
|
|
|
31
32
|
} from '@/lib/server/agent-assignment'
|
|
32
33
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
33
34
|
import { normalizeSchedulePayload } from '@/lib/server/schedule-normalization'
|
|
35
|
+
import { buildProjectSnapshot, ensureProjectWorkspace, normalizeProjectCreateInput, normalizeProjectPatchInput } from '@/lib/server/project-utils'
|
|
34
36
|
import type { ToolBuildContext } from './context'
|
|
35
37
|
import { safePath, findBinaryOnPath } from './context'
|
|
36
38
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
39
|
+
import type { BoardTask } from '@/types'
|
|
37
40
|
|
|
38
41
|
// ---------------------------------------------------------------------------
|
|
39
42
|
// Document helpers
|
|
@@ -115,6 +118,95 @@ function deriveTaskTitle(input: { title?: unknown; description?: unknown }): str
|
|
|
115
118
|
return compact.slice(0, 120)
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
const VALID_CONNECTOR_PLATFORMS = new Set([
|
|
122
|
+
'discord',
|
|
123
|
+
'telegram',
|
|
124
|
+
'slack',
|
|
125
|
+
'whatsapp',
|
|
126
|
+
'openclaw',
|
|
127
|
+
'bluebubbles',
|
|
128
|
+
'signal',
|
|
129
|
+
'teams',
|
|
130
|
+
'googlechat',
|
|
131
|
+
'matrix',
|
|
132
|
+
'email',
|
|
133
|
+
'webchat',
|
|
134
|
+
'mockmail',
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
const VALID_CONNECTOR_STATUSES = new Set(['stopped', 'running', 'error'])
|
|
138
|
+
|
|
139
|
+
function normalizeConnectorConfig(raw: unknown): Record<string, string> {
|
|
140
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {}
|
|
141
|
+
const normalized: Record<string, string> = {}
|
|
142
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
143
|
+
const normalizedKey = typeof key === 'string' ? key.trim() : ''
|
|
144
|
+
if (!normalizedKey) continue
|
|
145
|
+
if (typeof value === 'string') {
|
|
146
|
+
normalized[normalizedKey] = value
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
150
|
+
normalized[normalizedKey] = String(value)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return normalized
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sanitizeConnectorCrudPayload(
|
|
157
|
+
raw: Record<string, unknown>,
|
|
158
|
+
options: { forUpdate?: boolean } = {},
|
|
159
|
+
): Record<string, unknown> {
|
|
160
|
+
const { forUpdate = false } = options
|
|
161
|
+
const out: Record<string, unknown> = {}
|
|
162
|
+
const setString = (key: 'name' | 'platform' | 'status') => {
|
|
163
|
+
if (!Object.prototype.hasOwnProperty.call(raw, key)) return
|
|
164
|
+
const value = typeof raw[key] === 'string' ? raw[key].trim() : ''
|
|
165
|
+
if (!value) return
|
|
166
|
+
if (key === 'platform' && !VALID_CONNECTOR_PLATFORMS.has(value)) return
|
|
167
|
+
if (key === 'status' && !VALID_CONNECTOR_STATUSES.has(value)) return
|
|
168
|
+
out[key] = value
|
|
169
|
+
}
|
|
170
|
+
const setNullableId = (key: 'agentId' | 'chatroomId' | 'credentialId') => {
|
|
171
|
+
if (!Object.prototype.hasOwnProperty.call(raw, key)) return
|
|
172
|
+
const value = typeof raw[key] === 'string' ? raw[key].trim() : ''
|
|
173
|
+
out[key] = value || null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setString('name')
|
|
177
|
+
setString('platform')
|
|
178
|
+
setString('status')
|
|
179
|
+
setNullableId('agentId')
|
|
180
|
+
setNullableId('chatroomId')
|
|
181
|
+
setNullableId('credentialId')
|
|
182
|
+
|
|
183
|
+
if (Object.prototype.hasOwnProperty.call(raw, 'config')) {
|
|
184
|
+
out.config = normalizeConnectorConfig(raw.config)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (Object.prototype.hasOwnProperty.call(raw, 'isEnabled')) {
|
|
188
|
+
out.isEnabled = raw.isEnabled === true
|
|
189
|
+
} else if (Object.prototype.hasOwnProperty.call(raw, 'enabled')) {
|
|
190
|
+
out.isEnabled = raw.enabled === true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!forUpdate) {
|
|
194
|
+
const platform = typeof out.platform === 'string' ? out.platform : 'discord'
|
|
195
|
+
return {
|
|
196
|
+
name: typeof out.name === 'string' && out.name ? out.name : 'Unnamed Connector',
|
|
197
|
+
platform,
|
|
198
|
+
agentId: Object.prototype.hasOwnProperty.call(out, 'agentId') ? out.agentId : null,
|
|
199
|
+
chatroomId: Object.prototype.hasOwnProperty.call(out, 'chatroomId') ? out.chatroomId : null,
|
|
200
|
+
credentialId: Object.prototype.hasOwnProperty.call(out, 'credentialId') ? out.credentialId : null,
|
|
201
|
+
config: Object.prototype.hasOwnProperty.call(out, 'config') ? out.config : {},
|
|
202
|
+
isEnabled: Object.prototype.hasOwnProperty.call(out, 'isEnabled') ? out.isEnabled : false,
|
|
203
|
+
...(typeof out.status === 'string' ? { status: out.status } : {}),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return out
|
|
208
|
+
}
|
|
209
|
+
|
|
118
210
|
const TASK_STATUS_VALUES = new Set([
|
|
119
211
|
'backlog',
|
|
120
212
|
'queued',
|
|
@@ -132,6 +224,187 @@ function normalizeTaskStatusInput(status: unknown, prevStatus?: string): string
|
|
|
132
224
|
return normalized
|
|
133
225
|
}
|
|
134
226
|
|
|
227
|
+
function normalizeTaskIdList(value: unknown): string[] {
|
|
228
|
+
const rawValues = Array.isArray(value)
|
|
229
|
+
? value
|
|
230
|
+
: typeof value === 'string'
|
|
231
|
+
? value.split(',')
|
|
232
|
+
: []
|
|
233
|
+
const seen = new Set<string>()
|
|
234
|
+
const out: string[] = []
|
|
235
|
+
for (const entry of rawValues) {
|
|
236
|
+
const normalized = typeof entry === 'string' ? entry.trim() : ''
|
|
237
|
+
if (!normalized || seen.has(normalized)) continue
|
|
238
|
+
seen.add(normalized)
|
|
239
|
+
out.push(normalized)
|
|
240
|
+
}
|
|
241
|
+
return out
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function pickFirstTaskId(value: unknown): string | null {
|
|
245
|
+
const ids = normalizeTaskIdList(value)
|
|
246
|
+
return ids[0] || null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function buildScheduleCreatorScope(schedule: Record<string, unknown> | null | undefined): {
|
|
250
|
+
agentId?: string | null
|
|
251
|
+
sessionId?: string | null
|
|
252
|
+
} | null {
|
|
253
|
+
if (!schedule || typeof schedule !== 'object') return null
|
|
254
|
+
const agentId = typeof schedule.createdByAgentId === 'string' && schedule.createdByAgentId.trim()
|
|
255
|
+
? schedule.createdByAgentId.trim()
|
|
256
|
+
: null
|
|
257
|
+
const sessionId = typeof schedule.createdInSessionId === 'string' && schedule.createdInSessionId.trim()
|
|
258
|
+
? schedule.createdInSessionId.trim()
|
|
259
|
+
: null
|
|
260
|
+
if (!agentId && !sessionId) return null
|
|
261
|
+
return { agentId, sessionId }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function deriveScheduleFollowupTarget(sessionId: string | null | undefined): {
|
|
265
|
+
followupConnectorId?: string | null
|
|
266
|
+
followupChannelId?: string | null
|
|
267
|
+
followupThreadId?: string | null
|
|
268
|
+
followupSenderId?: string | null
|
|
269
|
+
followupSenderName?: string | null
|
|
270
|
+
} {
|
|
271
|
+
const normalizedSessionId = typeof sessionId === 'string' ? sessionId.trim() : ''
|
|
272
|
+
if (!normalizedSessionId) return {}
|
|
273
|
+
|
|
274
|
+
const session = loadSessions()[normalizedSessionId] as {
|
|
275
|
+
connectorContext?: Record<string, unknown>
|
|
276
|
+
messages?: Array<Record<string, unknown>>
|
|
277
|
+
} | undefined
|
|
278
|
+
if (!session) return {}
|
|
279
|
+
|
|
280
|
+
const pickSourceFields = (source: Record<string, unknown> | null | undefined) => {
|
|
281
|
+
const connectorId = typeof source?.connectorId === 'string' ? source.connectorId.trim() : ''
|
|
282
|
+
const channelId = typeof source?.channelId === 'string' ? source.channelId.trim() : ''
|
|
283
|
+
if (!connectorId || !channelId) return {}
|
|
284
|
+
const threadId = typeof source?.threadId === 'string' ? source.threadId.trim() : ''
|
|
285
|
+
const senderId = typeof source?.senderId === 'string' ? source.senderId.trim() : ''
|
|
286
|
+
const senderName = typeof source?.senderName === 'string' ? source.senderName.trim() : ''
|
|
287
|
+
return {
|
|
288
|
+
followupConnectorId: connectorId,
|
|
289
|
+
followupChannelId: channelId,
|
|
290
|
+
followupThreadId: threadId || null,
|
|
291
|
+
followupSenderId: senderId || null,
|
|
292
|
+
followupSenderName: senderName || null,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const contextTarget = pickSourceFields(session.connectorContext || undefined)
|
|
297
|
+
if (contextTarget.followupConnectorId && contextTarget.followupChannelId) return contextTarget
|
|
298
|
+
|
|
299
|
+
const messages = Array.isArray(session.messages) ? session.messages : []
|
|
300
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
301
|
+
const message = messages[i]
|
|
302
|
+
if ((typeof message?.role === 'string' ? message.role : '') !== 'user') continue
|
|
303
|
+
if (message?.historyExcluded === true) continue
|
|
304
|
+
const messageTarget = pickSourceFields(message?.source as Record<string, unknown> | undefined)
|
|
305
|
+
if (messageTarget.followupConnectorId && messageTarget.followupChannelId) return messageTarget
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function findRelatedScheduleIds(
|
|
312
|
+
schedules: Record<string, ScheduleLike>,
|
|
313
|
+
schedule: Record<string, unknown> | null | undefined,
|
|
314
|
+
opts: { ignoreId?: string | null } = {},
|
|
315
|
+
): string[] {
|
|
316
|
+
if (!schedule || typeof schedule !== 'object') return []
|
|
317
|
+
const scope = buildScheduleCreatorScope(schedule)
|
|
318
|
+
if (!scope?.sessionId) return []
|
|
319
|
+
const matches = findEquivalentSchedules(schedules, {
|
|
320
|
+
id: typeof schedule.id === 'string' ? schedule.id : null,
|
|
321
|
+
agentId: typeof schedule.agentId === 'string' ? schedule.agentId : null,
|
|
322
|
+
taskPrompt: typeof schedule.taskPrompt === 'string' ? schedule.taskPrompt : null,
|
|
323
|
+
scheduleType: typeof schedule.scheduleType === 'string' ? schedule.scheduleType : null,
|
|
324
|
+
cron: typeof schedule.cron === 'string' ? schedule.cron : null,
|
|
325
|
+
intervalMs: typeof schedule.intervalMs === 'number' ? schedule.intervalMs : null,
|
|
326
|
+
runAt: typeof schedule.runAt === 'number' ? schedule.runAt : null,
|
|
327
|
+
createdByAgentId: scope.agentId,
|
|
328
|
+
createdInSessionId: scope.sessionId,
|
|
329
|
+
}, {
|
|
330
|
+
ignoreId: opts.ignoreId || (typeof schedule.id === 'string' ? schedule.id : null),
|
|
331
|
+
creatorScope: scope,
|
|
332
|
+
})
|
|
333
|
+
return Array.from(new Set(matches
|
|
334
|
+
.map((entry) => (typeof entry.id === 'string' ? entry.id : ''))
|
|
335
|
+
.filter(Boolean)))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function applyTaskContinuationDefaults(
|
|
339
|
+
parsed: Record<string, unknown>,
|
|
340
|
+
tasks: Record<string, BoardTask>,
|
|
341
|
+
explicitInput?: Record<string, unknown>,
|
|
342
|
+
): string | null {
|
|
343
|
+
const explicit = explicitInput || parsed
|
|
344
|
+
const continuationTaskId = pickFirstTaskId(parsed.continueFromTaskId)
|
|
345
|
+
|| pickFirstTaskId(parsed.followUpToTaskId)
|
|
346
|
+
|| pickFirstTaskId(parsed.resumeFromTaskId)
|
|
347
|
+
const blockedBy = [
|
|
348
|
+
...normalizeTaskIdList(parsed.blockedBy),
|
|
349
|
+
...normalizeTaskIdList(parsed.dependsOn),
|
|
350
|
+
...normalizeTaskIdList(parsed.dependsOnTaskIds),
|
|
351
|
+
...normalizeTaskIdList(parsed.prerequisiteTaskIds),
|
|
352
|
+
]
|
|
353
|
+
if (continuationTaskId && !blockedBy.includes(continuationTaskId)) {
|
|
354
|
+
blockedBy.unshift(continuationTaskId)
|
|
355
|
+
}
|
|
356
|
+
if (blockedBy.length > 0) parsed.blockedBy = blockedBy
|
|
357
|
+
|
|
358
|
+
if (continuationTaskId) {
|
|
359
|
+
const sourceTask = tasks[continuationTaskId]
|
|
360
|
+
if (!sourceTask) return `Error: source task "${continuationTaskId}" not found.`
|
|
361
|
+
|
|
362
|
+
if (!Object.prototype.hasOwnProperty.call(explicit, 'projectId') && typeof sourceTask.projectId === 'string' && sourceTask.projectId.trim()) {
|
|
363
|
+
parsed.projectId = sourceTask.projectId.trim()
|
|
364
|
+
}
|
|
365
|
+
if (
|
|
366
|
+
!Object.prototype.hasOwnProperty.call(explicit, 'agentId')
|
|
367
|
+
&& !hasManagedAgentAssignmentInput(explicit)
|
|
368
|
+
&& typeof sourceTask.agentId === 'string'
|
|
369
|
+
&& sourceTask.agentId.trim()
|
|
370
|
+
) {
|
|
371
|
+
parsed.agentId = sourceTask.agentId.trim()
|
|
372
|
+
}
|
|
373
|
+
if (!Object.prototype.hasOwnProperty.call(explicit, 'cwd') && typeof sourceTask.cwd === 'string' && sourceTask.cwd.trim()) {
|
|
374
|
+
parsed.cwd = sourceTask.cwd.trim()
|
|
375
|
+
}
|
|
376
|
+
const sourceSessionId = typeof sourceTask.checkpoint?.lastSessionId === 'string' && sourceTask.checkpoint.lastSessionId.trim()
|
|
377
|
+
? sourceTask.checkpoint.lastSessionId.trim()
|
|
378
|
+
: typeof sourceTask.sessionId === 'string' && sourceTask.sessionId.trim()
|
|
379
|
+
? sourceTask.sessionId.trim()
|
|
380
|
+
: ''
|
|
381
|
+
if (!Object.prototype.hasOwnProperty.call(explicit, 'sessionId') && sourceSessionId) {
|
|
382
|
+
parsed.sessionId = sourceSessionId
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const resumeFieldMap: Array<[keyof BoardTask, string]> = [
|
|
386
|
+
['cliResumeId', 'cliResumeId'],
|
|
387
|
+
['cliProvider', 'cliProvider'],
|
|
388
|
+
['claudeResumeId', 'claudeResumeId'],
|
|
389
|
+
['codexResumeId', 'codexResumeId'],
|
|
390
|
+
['opencodeResumeId', 'opencodeResumeId'],
|
|
391
|
+
['geminiResumeId', 'geminiResumeId'],
|
|
392
|
+
]
|
|
393
|
+
for (const [sourceKey, targetKey] of resumeFieldMap) {
|
|
394
|
+
const value = sourceTask[sourceKey]
|
|
395
|
+
if (Object.prototype.hasOwnProperty.call(explicit, targetKey)) continue
|
|
396
|
+
if (typeof value === 'string' && value.trim()) {
|
|
397
|
+
parsed[targetKey] = value.trim()
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (const aliasKey of ['continueFromTaskId', 'followUpToTaskId', 'resumeFromTaskId', 'dependsOn', 'dependsOnTaskIds', 'prerequisiteTaskIds']) {
|
|
403
|
+
delete parsed[aliasKey]
|
|
404
|
+
}
|
|
405
|
+
return null
|
|
406
|
+
}
|
|
407
|
+
|
|
135
408
|
// ---------------------------------------------------------------------------
|
|
136
409
|
// RESOURCE_DEFAULTS
|
|
137
410
|
// ---------------------------------------------------------------------------
|
|
@@ -190,11 +463,7 @@ const RESOURCE_DEFAULTS: Record<string, (parsed: any) => any> = {
|
|
|
190
463
|
...p,
|
|
191
464
|
}),
|
|
192
465
|
manage_connectors: (p) => ({
|
|
193
|
-
|
|
194
|
-
platform: p.platform || 'discord',
|
|
195
|
-
agentId: p.agentId || null,
|
|
196
|
-
enabled: p.enabled ?? false,
|
|
197
|
-
...p,
|
|
466
|
+
...sanitizeConnectorCrudPayload(p as Record<string, unknown>),
|
|
198
467
|
}),
|
|
199
468
|
manage_webhooks: (p) => ({
|
|
200
469
|
name: p.name || 'Unnamed Webhook',
|
|
@@ -212,6 +481,7 @@ const RESOURCE_DEFAULTS: Record<string, (parsed: any) => any> = {
|
|
|
212
481
|
agentIds: Array.isArray(p.agentIds) ? p.agentIds : [],
|
|
213
482
|
...p,
|
|
214
483
|
}),
|
|
484
|
+
manage_projects: (p) => normalizeProjectCreateInput(p),
|
|
215
485
|
}
|
|
216
486
|
|
|
217
487
|
// ---------------------------------------------------------------------------
|
|
@@ -226,6 +496,7 @@ const PLATFORM_RESOURCES: Record<string, {
|
|
|
226
496
|
readOnly?: boolean
|
|
227
497
|
}> = {
|
|
228
498
|
manage_agents: { toolId: 'manage_agents', label: 'agents', load: loadAgents, save: saveAgents },
|
|
499
|
+
manage_projects: { toolId: 'manage_projects', label: 'projects', load: loadProjects, save: saveProjects },
|
|
229
500
|
manage_tasks: { toolId: 'manage_tasks', label: 'tasks', load: loadTasks, save: saveTasks },
|
|
230
501
|
manage_schedules: { toolId: 'manage_schedules', label: 'schedules', load: loadSchedules, save: saveSchedules },
|
|
231
502
|
manage_skills: { toolId: 'manage_skills', label: 'skills', load: loadSkills, save: saveSkills },
|
|
@@ -242,6 +513,14 @@ const PLATFORM_RESOURCES: Record<string, {
|
|
|
242
513
|
export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
243
514
|
const tools: StructuredToolInterface[] = []
|
|
244
515
|
const { cwd, ctx, hasPlugin } = bctx
|
|
516
|
+
const buildCrudPayload = (normalized: Record<string, unknown>, action: string | undefined, data: string | undefined): Record<string, unknown> => {
|
|
517
|
+
if (data) return JSON.parse(data)
|
|
518
|
+
if (action !== 'create' && action !== 'update') return {}
|
|
519
|
+
const entries = Object.entries(normalized).filter(([key]) =>
|
|
520
|
+
!['action', 'id', 'data', 'resource', 'input', 'args', 'arguments', 'payload', 'parameters'].includes(key),
|
|
521
|
+
)
|
|
522
|
+
return entries.length > 0 ? Object.fromEntries(entries) : {}
|
|
523
|
+
}
|
|
245
524
|
|
|
246
525
|
// Build dynamic agent summary for tools that need agent awareness
|
|
247
526
|
const assignScope = ctx?.platformAssignScope || 'self'
|
|
@@ -271,16 +550,35 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
271
550
|
} else {
|
|
272
551
|
description += `\n\nYou may create tasks for yourself, leave them unassigned, or delegate them to other agents. Your agent ID is "${ctx?.agentId || 'unknown'}". When delegating, set a target agent using "agentId", "assignee", "agent", "assignedAgentId", or "assigned_agent_id". Use the target agent's exact ID when possible. Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.` + agentSummary
|
|
273
552
|
}
|
|
553
|
+
description += '\n\nCreate/update calls accept either `data` as a JSON string or direct top-level fields like `title`, `description`, `status`, `agentId`, and `projectId`.'
|
|
554
|
+
description += '\n\nFor follow-up work, set `continueFromTaskId` (or `followUpToTaskId`) to a prior task ID. The new task will inherit the predecessor\'s project/agent/session context, block on that task by default, and reuse its execution session when possible.'
|
|
555
|
+
if (ctx?.projectId) {
|
|
556
|
+
description += `\n\nCurrent project context: "${ctx.projectName || ctx.projectId}" (projectId "${ctx.projectId}"). Omit "projectId" to use this active project by default.`
|
|
557
|
+
}
|
|
558
|
+
} else if (toolKey === 'manage_projects') {
|
|
559
|
+
description += '\n\nProjects hold durable execution context for longer-lived work: objective, audience, pilot priorities, open objectives, credential requirements, and preferred heartbeat cadence.'
|
|
560
|
+
description += '\n\nCreate/update calls accept either `data` as a JSON string or direct top-level fields like `name`, `description`, `objective`, `audience`, `priorities`, `openObjectives`, `capabilityHints`, `credentialRequirements`, `heartbeatPrompt`, and `heartbeatIntervalSec`.'
|
|
561
|
+
if (ctx?.projectId) {
|
|
562
|
+
description += `\n\nCurrent project context: "${ctx.projectName || ctx.projectId}" (projectId "${ctx.projectId}"). For get/update/delete, you may omit "id" to target this active project.`
|
|
563
|
+
}
|
|
274
564
|
} else if (toolKey === 'manage_agents') {
|
|
275
565
|
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. Set "platformAssignScope":"all" to let an agent delegate work across the fleet; use "self" for solo execution.`
|
|
276
566
|
} else if (toolKey === 'manage_schedules') {
|
|
277
567
|
if (assignScope === 'self') {
|
|
278
|
-
description += `\n\nOmit "agentId" to assign a schedule to yourself ("${ctx?.agentId || 'unknown'}"), or set it explicitly to yourself. You can only assign schedules to yourself. Schedule types: interval (set intervalMs), cron (set cron), once (set runAt). Provide either taskPrompt, command, or action+path. Before create, call list/get to avoid duplicate schedules. If an equivalent active/paused schedule already exists, create returns that existing schedule (deduplicated=true).`
|
|
568
|
+
description += `\n\nOmit "agentId" to assign a schedule to yourself ("${ctx?.agentId || 'unknown'}"), or set it explicitly to yourself. You can only assign schedules to yourself. Schedule types: interval (set intervalMs), cron (set cron), once (set runAt). Provide either taskPrompt, command, or action+path. Before create, call list/get to avoid duplicate schedules. Reuse or update an existing schedule you already created in this chat instead of making a near-duplicate. If an equivalent active/paused schedule already exists, create returns that existing schedule (deduplicated=true). For one-off reminders, prefer "once"; agent-created one-off schedules are cleaned up automatically after they finish. When the user says stop/pause/cancel a reminder, pause or delete every matching schedule you created in this chat, not just one row.`
|
|
279
569
|
} else {
|
|
280
|
-
description += `\n\nOmit "agentId" to assign a schedule to yourself ("${ctx?.agentId || 'unknown'}"), or set "agentId" to another agent when needed. Schedule types: interval (set intervalMs), cron (set cron), once (set runAt). Provide either taskPrompt, command, or action+path. Before create, call list/get to avoid duplicate schedules. If an equivalent active/paused schedule already exists, create returns that existing schedule (deduplicated=true).` + agentSummary
|
|
570
|
+
description += `\n\nOmit "agentId" to assign a schedule to yourself ("${ctx?.agentId || 'unknown'}"), or set "agentId" to another agent when needed. Schedule types: interval (set intervalMs), cron (set cron), once (set runAt). Provide either taskPrompt, command, or action+path. Before create, call list/get to avoid duplicate schedules. Reuse or update an existing schedule you already created in this chat instead of making a near-duplicate. If an equivalent active/paused schedule already exists, create returns that existing schedule (deduplicated=true). For one-off reminders, prefer "once"; agent-created one-off schedules are cleaned up automatically after they finish. When the user says stop/pause/cancel a reminder, pause or delete every matching schedule you created in this chat, not just one row.` + agentSummary
|
|
571
|
+
}
|
|
572
|
+
if (ctx?.projectId) {
|
|
573
|
+
description += `\n\nCurrent project context: "${ctx.projectName || ctx.projectId}" (projectId "${ctx.projectId}"). Omit "projectId" to use this active project by default.`
|
|
281
574
|
}
|
|
282
575
|
} else if (toolKey === 'manage_webhooks') {
|
|
283
576
|
description += '\n\nUse `source`, `events`, `agentId`, and `secret` when creating webhooks. Inbound calls should POST to `/api/webhooks/{id}` with header `x-webhook-secret` when a secret is configured.'
|
|
577
|
+
} else if (toolKey === 'manage_secrets') {
|
|
578
|
+
description += '\n\nUse this for credential bootstrapping and durable secret storage. Create/update calls accept either `data` as JSON or direct top-level fields like `name`, `service`, `value`, `scope`, `agentIds`, and `projectId`.'
|
|
579
|
+
if (ctx?.projectId) {
|
|
580
|
+
description += `\n\nCurrent project context: "${ctx.projectName || ctx.projectId}" (projectId "${ctx.projectId}"). Omit "projectId" to link the secret to this active project.`
|
|
581
|
+
}
|
|
284
582
|
}
|
|
285
583
|
|
|
286
584
|
tools.push(
|
|
@@ -298,6 +596,11 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
298
596
|
}
|
|
299
597
|
try {
|
|
300
598
|
if (action === 'list') {
|
|
599
|
+
if (toolKey === 'manage_projects') {
|
|
600
|
+
const values = Object.values(res.load())
|
|
601
|
+
.map((project: any) => buildProjectSnapshot(project))
|
|
602
|
+
return JSON.stringify(values)
|
|
603
|
+
}
|
|
301
604
|
if (toolKey === 'manage_secrets') {
|
|
302
605
|
const values = Object.values(res.load())
|
|
303
606
|
.filter((s: any) => canAccessSecret(s))
|
|
@@ -307,6 +610,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
307
610
|
service: s.service,
|
|
308
611
|
scope: s.scope || 'global',
|
|
309
612
|
agentIds: s.agentIds || [],
|
|
613
|
+
projectId: s.projectId || null,
|
|
310
614
|
createdAt: s.createdAt,
|
|
311
615
|
updatedAt: s.updatedAt,
|
|
312
616
|
}))
|
|
@@ -315,40 +619,56 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
315
619
|
return JSON.stringify(Object.values(res.load()))
|
|
316
620
|
}
|
|
317
621
|
if (action === 'get') {
|
|
318
|
-
|
|
622
|
+
const effectiveId = id || (toolKey === 'manage_projects' ? ctx?.projectId || undefined : undefined)
|
|
623
|
+
if (!effectiveId) return 'Error: "id" is required for get action.'
|
|
319
624
|
const all = res.load()
|
|
320
|
-
if (!all[
|
|
625
|
+
if (!all[effectiveId]) return `Not found: ${res.label} "${effectiveId}"`
|
|
626
|
+
if (toolKey === 'manage_projects') {
|
|
627
|
+
return JSON.stringify(buildProjectSnapshot(all[effectiveId]))
|
|
628
|
+
}
|
|
321
629
|
if (toolKey === 'manage_secrets') {
|
|
322
|
-
if (!canAccessSecret(all[
|
|
630
|
+
if (!canAccessSecret(all[effectiveId])) return 'Error: you do not have access to this secret.'
|
|
323
631
|
let value = ''
|
|
324
632
|
try {
|
|
325
|
-
value = all[
|
|
633
|
+
value = all[effectiveId].encryptedValue ? decryptKey(all[effectiveId].encryptedValue) : ''
|
|
326
634
|
} catch {
|
|
327
635
|
value = ''
|
|
328
636
|
}
|
|
329
637
|
return JSON.stringify({
|
|
330
|
-
id: all[
|
|
331
|
-
name: all[
|
|
332
|
-
service: all[
|
|
333
|
-
scope: all[
|
|
334
|
-
agentIds: all[
|
|
638
|
+
id: all[effectiveId].id,
|
|
639
|
+
name: all[effectiveId].name,
|
|
640
|
+
service: all[effectiveId].service,
|
|
641
|
+
scope: all[effectiveId].scope || 'global',
|
|
642
|
+
agentIds: all[effectiveId].agentIds || [],
|
|
643
|
+
projectId: all[effectiveId].projectId || null,
|
|
335
644
|
value,
|
|
336
|
-
createdAt: all[
|
|
337
|
-
updatedAt: all[
|
|
645
|
+
createdAt: all[effectiveId].createdAt,
|
|
646
|
+
updatedAt: all[effectiveId].updatedAt,
|
|
338
647
|
})
|
|
339
648
|
}
|
|
340
|
-
return JSON.stringify(all[
|
|
649
|
+
return JSON.stringify(all[effectiveId])
|
|
341
650
|
}
|
|
342
651
|
if (res.readOnly) return `Cannot ${action} ${res.label} via this tool (read-only).`
|
|
343
652
|
if (action === 'create') {
|
|
344
653
|
const all = res.load()
|
|
345
|
-
const raw =
|
|
654
|
+
const raw = buildCrudPayload(normalized, action, data)
|
|
346
655
|
const defaults = RESOURCE_DEFAULTS[toolKey]
|
|
347
656
|
const parsed = defaults ? defaults(raw) : raw
|
|
348
657
|
if (parsed && typeof parsed === 'object' && 'id' in parsed) {
|
|
349
658
|
delete (parsed as Record<string, unknown>).id
|
|
350
659
|
}
|
|
351
660
|
const now = Date.now()
|
|
661
|
+
if (toolKey === 'manage_tasks') {
|
|
662
|
+
const continuationError = applyTaskContinuationDefaults(
|
|
663
|
+
parsed as Record<string, unknown>,
|
|
664
|
+
all as Record<string, BoardTask>,
|
|
665
|
+
raw as Record<string, unknown>,
|
|
666
|
+
)
|
|
667
|
+
if (continuationError) return continuationError
|
|
668
|
+
}
|
|
669
|
+
if ((toolKey === 'manage_tasks' || toolKey === 'manage_schedules' || toolKey === 'manage_secrets') && !Object.prototype.hasOwnProperty.call(parsed, 'projectId') && ctx?.projectId) {
|
|
670
|
+
parsed.projectId = ctx.projectId
|
|
671
|
+
}
|
|
352
672
|
if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
|
|
353
673
|
const agents = loadAgents()
|
|
354
674
|
const resolution = resolveManagedAgentAssignment(
|
|
@@ -447,15 +767,19 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
447
767
|
}
|
|
448
768
|
}
|
|
449
769
|
const newId = genId()
|
|
770
|
+
const scheduleFollowupTarget = toolKey === 'manage_schedules'
|
|
771
|
+
? deriveScheduleFollowupTarget(ctx?.sessionId || null)
|
|
772
|
+
: {}
|
|
450
773
|
const entry = {
|
|
451
774
|
id: newId,
|
|
452
775
|
...parsed,
|
|
453
776
|
createdByAgentId: ctx?.agentId || null,
|
|
454
777
|
createdInSessionId: ctx?.sessionId || null,
|
|
778
|
+
...scheduleFollowupTarget,
|
|
455
779
|
createdAt: now,
|
|
456
780
|
updatedAt: now,
|
|
457
781
|
}
|
|
458
|
-
let responseEntry:
|
|
782
|
+
let responseEntry: unknown = entry
|
|
459
783
|
if (toolKey === 'manage_secrets') {
|
|
460
784
|
const secretValue = typeof parsed.value === 'string' ? parsed.value : null
|
|
461
785
|
if (!secretValue) return 'Error: data.value is required to create a secret.'
|
|
@@ -470,12 +794,17 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
470
794
|
...entry,
|
|
471
795
|
scope: normalizedScope,
|
|
472
796
|
agentIds: normalizedAgentIds,
|
|
797
|
+
projectId: typeof parsed.projectId === 'string' && parsed.projectId.trim() ? parsed.projectId.trim() : undefined,
|
|
473
798
|
encryptedValue: encryptKey(secretValue),
|
|
474
799
|
}
|
|
475
800
|
delete (stored as any).value
|
|
476
801
|
all[newId] = stored
|
|
477
802
|
const { encryptedValue, ...safe } = stored
|
|
478
803
|
responseEntry = safe
|
|
804
|
+
} else if (toolKey === 'manage_projects') {
|
|
805
|
+
all[newId] = entry
|
|
806
|
+
ensureProjectWorkspace(newId, entry.name)
|
|
807
|
+
responseEntry = buildProjectSnapshot(entry)
|
|
479
808
|
} else {
|
|
480
809
|
all[newId] = entry
|
|
481
810
|
}
|
|
@@ -510,30 +839,42 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
510
839
|
return JSON.stringify(responseEntry)
|
|
511
840
|
}
|
|
512
841
|
if (action === 'update') {
|
|
513
|
-
|
|
842
|
+
const effectiveId = id || (toolKey === 'manage_projects' ? ctx?.projectId || undefined : undefined)
|
|
843
|
+
if (!effectiveId) return 'Error: "id" is required for update action.'
|
|
514
844
|
const all = res.load()
|
|
515
|
-
if (!all[
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
845
|
+
if (!all[effectiveId]) return `Not found: ${res.label} "${effectiveId}"`
|
|
846
|
+
const previousEntry = all[effectiveId]
|
|
847
|
+
let affectedScheduleIds: string[] | null = null
|
|
848
|
+
const parsed = toolKey === 'manage_projects'
|
|
849
|
+
? normalizeProjectPatchInput(buildCrudPayload(normalized, action, data))
|
|
850
|
+
: toolKey === 'manage_connectors'
|
|
851
|
+
? sanitizeConnectorCrudPayload(buildCrudPayload(normalized, action, data), { forUpdate: true })
|
|
852
|
+
: buildCrudPayload(normalized, action, data)
|
|
853
|
+
const parsedRecord = parsed as Record<string, unknown>
|
|
854
|
+
if (toolKey === 'manage_tasks') {
|
|
855
|
+
const continuationError = applyTaskContinuationDefaults(parsedRecord, all as Record<string, BoardTask>, parsedRecord)
|
|
856
|
+
if (continuationError) return continuationError
|
|
857
|
+
}
|
|
858
|
+
const prevStatus = all[effectiveId]?.status
|
|
859
|
+
if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsedRecord, 'status')) {
|
|
860
|
+
const normalized = normalizeTaskStatusInput(parsedRecord.status, prevStatus)
|
|
861
|
+
if (normalized) parsedRecord.status = normalized
|
|
862
|
+
else delete parsedRecord.status
|
|
522
863
|
}
|
|
523
|
-
if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(
|
|
864
|
+
if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsedRecord, 'qualityGate')) {
|
|
524
865
|
const settings = loadSettings()
|
|
525
|
-
|
|
526
|
-
? normalizeTaskQualityGate(
|
|
866
|
+
parsedRecord.qualityGate = parsedRecord.qualityGate
|
|
867
|
+
? normalizeTaskQualityGate(parsedRecord.qualityGate, settings)
|
|
527
868
|
: null
|
|
528
869
|
}
|
|
529
870
|
if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
|
|
530
871
|
const agents = loadAgents()
|
|
531
|
-
const requestedClear = Object.prototype.hasOwnProperty.call(
|
|
872
|
+
const requestedClear = Object.prototype.hasOwnProperty.call(parsedRecord, 'agentId') && parsedRecord.agentId == null
|
|
532
873
|
const shouldResolveAssignment = requestedClear
|
|
533
|
-
|| hasManagedAgentAssignmentInput(
|
|
874
|
+
|| hasManagedAgentAssignmentInput(parsedRecord)
|
|
534
875
|
if (shouldResolveAssignment) {
|
|
535
876
|
const resolution = resolveManagedAgentAssignment(
|
|
536
|
-
|
|
877
|
+
parsedRecord,
|
|
537
878
|
agents,
|
|
538
879
|
null,
|
|
539
880
|
{ allowDescription: false },
|
|
@@ -547,108 +888,167 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
547
888
|
unresolvedReference: requestedClear ? null : resolution.unresolvedReference,
|
|
548
889
|
isDelegation: toolKey === 'manage_tasks'
|
|
549
890
|
? isDelegationTaskPayload({
|
|
550
|
-
...all[
|
|
551
|
-
...
|
|
891
|
+
...all[effectiveId],
|
|
892
|
+
...parsedRecord,
|
|
552
893
|
agentId: requestedClear ? null : resolution.agentId,
|
|
553
894
|
} as Record<string, unknown>)
|
|
554
895
|
: false,
|
|
555
896
|
delegatorAgentId: toolKey === 'manage_tasks'
|
|
556
897
|
? resolveDelegatorAgentId({
|
|
557
|
-
...all[
|
|
558
|
-
...
|
|
559
|
-
}
|
|
898
|
+
...all[effectiveId],
|
|
899
|
+
...parsedRecord,
|
|
900
|
+
}, agents, ctx?.agentId || null)
|
|
560
901
|
: null,
|
|
561
902
|
})
|
|
562
903
|
if (assignmentError) return assignmentError
|
|
563
|
-
if (!requestedClear)
|
|
904
|
+
if (!requestedClear) parsedRecord.agentId = resolution.agentId
|
|
564
905
|
}
|
|
565
906
|
}
|
|
566
|
-
all[
|
|
907
|
+
all[effectiveId] = { ...all[effectiveId], ...parsed, updatedAt: Date.now() }
|
|
567
908
|
if (toolKey === 'manage_schedules') {
|
|
568
|
-
const normalizedSchedule = normalizeSchedulePayload(all[
|
|
909
|
+
const normalizedSchedule = normalizeSchedulePayload(all[effectiveId] as Record<string, unknown>, {
|
|
569
910
|
cwd,
|
|
570
911
|
now: Date.now(),
|
|
571
912
|
})
|
|
572
913
|
if (!normalizedSchedule.ok) return normalizedSchedule.error
|
|
573
|
-
all[
|
|
574
|
-
...all[
|
|
914
|
+
all[effectiveId] = {
|
|
915
|
+
...all[effectiveId],
|
|
575
916
|
...normalizedSchedule.value,
|
|
576
917
|
updatedAt: Date.now(),
|
|
577
918
|
}
|
|
919
|
+
const nextStatus = typeof all[effectiveId].status === 'string' ? all[effectiveId].status.trim().toLowerCase() : ''
|
|
920
|
+
if (nextStatus === 'paused' || nextStatus === 'completed' || nextStatus === 'failed') {
|
|
921
|
+
const relatedIds = findRelatedScheduleIds(all as Record<string, ScheduleLike>, previousEntry, {
|
|
922
|
+
ignoreId: effectiveId,
|
|
923
|
+
})
|
|
924
|
+
for (const relatedId of relatedIds) {
|
|
925
|
+
if (!all[relatedId]) continue
|
|
926
|
+
all[relatedId] = {
|
|
927
|
+
...all[relatedId],
|
|
928
|
+
status: nextStatus,
|
|
929
|
+
updatedAt: Date.now(),
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
affectedScheduleIds = [effectiveId, ...relatedIds]
|
|
933
|
+
}
|
|
578
934
|
}
|
|
579
935
|
if (toolKey === 'manage_secrets') {
|
|
580
|
-
if (!canAccessSecret(all[
|
|
581
|
-
const nextScope =
|
|
936
|
+
if (!canAccessSecret(all[effectiveId])) return 'Error: you do not have access to this secret.'
|
|
937
|
+
const nextScope = parsedRecord.scope === 'agent'
|
|
582
938
|
? 'agent'
|
|
583
|
-
:
|
|
939
|
+
: parsedRecord.scope === 'global'
|
|
584
940
|
? 'global'
|
|
585
|
-
: (all[
|
|
941
|
+
: (all[effectiveId].scope === 'agent' ? 'agent' : 'global')
|
|
586
942
|
if (nextScope === 'agent') {
|
|
587
|
-
const incomingIds = Array.isArray(
|
|
588
|
-
?
|
|
589
|
-
: Array.isArray(all[
|
|
590
|
-
? all[
|
|
943
|
+
const incomingIds = Array.isArray(parsedRecord.agentIds)
|
|
944
|
+
? parsedRecord.agentIds.filter((x: any) => typeof x === 'string')
|
|
945
|
+
: Array.isArray(all[effectiveId].agentIds)
|
|
946
|
+
? all[effectiveId].agentIds
|
|
591
947
|
: []
|
|
592
|
-
all[
|
|
948
|
+
all[effectiveId].agentIds = Array.from(new Set([
|
|
593
949
|
...incomingIds,
|
|
594
950
|
...(ctx?.agentId ? [ctx.agentId] : []),
|
|
595
951
|
]))
|
|
596
952
|
} else {
|
|
597
|
-
all[
|
|
953
|
+
all[effectiveId].agentIds = []
|
|
598
954
|
}
|
|
599
|
-
all[
|
|
600
|
-
if (
|
|
601
|
-
all[
|
|
955
|
+
all[effectiveId].scope = nextScope
|
|
956
|
+
if (Object.prototype.hasOwnProperty.call(parsedRecord, 'projectId')) {
|
|
957
|
+
all[effectiveId].projectId = typeof parsedRecord.projectId === 'string' && parsedRecord.projectId.trim()
|
|
958
|
+
? parsedRecord.projectId.trim()
|
|
959
|
+
: undefined
|
|
602
960
|
}
|
|
603
|
-
|
|
961
|
+
if (typeof parsedRecord.value === 'string' && parsedRecord.value.trim()) {
|
|
962
|
+
all[effectiveId].encryptedValue = encryptKey(parsedRecord.value)
|
|
963
|
+
}
|
|
964
|
+
delete all[effectiveId].value
|
|
604
965
|
}
|
|
605
966
|
|
|
606
|
-
if (toolKey === 'manage_tasks' && all[
|
|
967
|
+
if (toolKey === 'manage_tasks' && all[effectiveId].status === 'completed') {
|
|
607
968
|
const { formatValidationFailure, validateTaskCompletion } = await import('../task-validation')
|
|
608
969
|
const { ensureTaskCompletionReport } = await import('../task-reports')
|
|
609
970
|
const settings = loadSettings()
|
|
610
|
-
const report = ensureTaskCompletionReport(all[
|
|
611
|
-
if (report?.relativePath) (all[
|
|
612
|
-
const validation = validateTaskCompletion(all[
|
|
613
|
-
;(all[
|
|
971
|
+
const report = ensureTaskCompletionReport(all[effectiveId] as any)
|
|
972
|
+
if (report?.relativePath) (all[effectiveId] as any).completionReportPath = report.relativePath
|
|
973
|
+
const validation = validateTaskCompletion(all[effectiveId] as any, { report, settings })
|
|
974
|
+
;(all[effectiveId] as any).validation = validation
|
|
614
975
|
if (!validation.ok) {
|
|
615
|
-
all[
|
|
616
|
-
;(all[
|
|
617
|
-
;(all[
|
|
618
|
-
} else if ((all[
|
|
619
|
-
;(all[
|
|
976
|
+
all[effectiveId].status = 'failed'
|
|
977
|
+
;(all[effectiveId] as any).completedAt = null
|
|
978
|
+
;(all[effectiveId] as any).error = formatValidationFailure(validation.reasons).slice(0, 500)
|
|
979
|
+
} else if ((all[effectiveId] as any).completedAt == null) {
|
|
980
|
+
;(all[effectiveId] as any).completedAt = Date.now()
|
|
620
981
|
}
|
|
621
982
|
}
|
|
622
983
|
|
|
623
984
|
res.save(all)
|
|
624
|
-
if (toolKey === '
|
|
985
|
+
if (toolKey === 'manage_projects') {
|
|
986
|
+
ensureProjectWorkspace(effectiveId, all[effectiveId].name)
|
|
987
|
+
}
|
|
988
|
+
if (toolKey === 'manage_tasks' && prevStatus !== 'queued' && all[effectiveId].status === 'queued') {
|
|
625
989
|
const { enqueueTask } = await import('../queue')
|
|
626
|
-
enqueueTask(
|
|
990
|
+
enqueueTask(effectiveId)
|
|
627
991
|
} else if (
|
|
628
992
|
toolKey === 'manage_tasks'
|
|
629
|
-
&& prevStatus !== all[
|
|
630
|
-
&& (all[
|
|
631
|
-
&& all[
|
|
993
|
+
&& prevStatus !== all[effectiveId].status
|
|
994
|
+
&& (all[effectiveId].status === 'completed' || all[effectiveId].status === 'failed')
|
|
995
|
+
&& all[effectiveId].sessionId
|
|
632
996
|
) {
|
|
633
997
|
const { disableSessionHeartbeat } = await import('../queue')
|
|
634
|
-
disableSessionHeartbeat(all[
|
|
998
|
+
disableSessionHeartbeat(all[effectiveId].sessionId)
|
|
635
999
|
}
|
|
636
1000
|
if (toolKey === 'manage_secrets') {
|
|
637
|
-
const { encryptedValue, ...safe } = all[
|
|
1001
|
+
const { encryptedValue, ...safe } = all[effectiveId]
|
|
638
1002
|
return JSON.stringify(safe)
|
|
639
1003
|
}
|
|
640
|
-
|
|
1004
|
+
if (toolKey === 'manage_projects') {
|
|
1005
|
+
return JSON.stringify(buildProjectSnapshot(all[effectiveId]))
|
|
1006
|
+
}
|
|
1007
|
+
if (toolKey === 'manage_schedules' && affectedScheduleIds?.length) {
|
|
1008
|
+
return JSON.stringify({
|
|
1009
|
+
...all[effectiveId],
|
|
1010
|
+
affectedScheduleIds,
|
|
1011
|
+
})
|
|
1012
|
+
}
|
|
1013
|
+
return JSON.stringify(all[effectiveId])
|
|
641
1014
|
}
|
|
642
1015
|
if (action === 'delete') {
|
|
643
|
-
|
|
1016
|
+
const effectiveId = id || (toolKey === 'manage_projects' ? ctx?.projectId || undefined : undefined)
|
|
1017
|
+
if (!effectiveId) return 'Error: "id" is required for delete action.'
|
|
644
1018
|
const all = res.load()
|
|
645
|
-
if (!all[
|
|
646
|
-
if (toolKey === 'manage_secrets' && !canAccessSecret(all[
|
|
1019
|
+
if (!all[effectiveId]) return `Not found: ${res.label} "${effectiveId}"`
|
|
1020
|
+
if (toolKey === 'manage_secrets' && !canAccessSecret(all[effectiveId])) {
|
|
647
1021
|
return 'Error: you do not have access to this secret.'
|
|
648
1022
|
}
|
|
649
|
-
|
|
1023
|
+
const deletedIds = toolKey === 'manage_schedules'
|
|
1024
|
+
? [effectiveId, ...findRelatedScheduleIds(all as Record<string, ScheduleLike>, all[effectiveId], { ignoreId: effectiveId })]
|
|
1025
|
+
: [effectiveId]
|
|
1026
|
+
for (const deleteId of deletedIds) {
|
|
1027
|
+
delete all[deleteId]
|
|
1028
|
+
}
|
|
650
1029
|
res.save(all)
|
|
651
|
-
|
|
1030
|
+
if (toolKey === 'manage_projects') {
|
|
1031
|
+
const clearProjectId = (load: () => Record<string, Record<string, unknown>>, save: (d: Record<string, Record<string, unknown>>) => void) => {
|
|
1032
|
+
const items = load()
|
|
1033
|
+
let changed = false
|
|
1034
|
+
for (const item of Object.values(items)) {
|
|
1035
|
+
if (item.projectId === effectiveId) {
|
|
1036
|
+
item.projectId = undefined
|
|
1037
|
+
changed = true
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (changed) save(items)
|
|
1041
|
+
}
|
|
1042
|
+
clearProjectId(loadAgents, saveAgents)
|
|
1043
|
+
clearProjectId(loadTasks, saveTasks)
|
|
1044
|
+
clearProjectId(loadSchedules, saveSchedules)
|
|
1045
|
+
clearProjectId(loadSkills, saveSkills)
|
|
1046
|
+
clearProjectId(loadSecrets, saveSecrets)
|
|
1047
|
+
}
|
|
1048
|
+
return JSON.stringify({
|
|
1049
|
+
deleted: effectiveId,
|
|
1050
|
+
deletedIds,
|
|
1051
|
+
})
|
|
652
1052
|
}
|
|
653
1053
|
return `Unknown action "${action}". Valid: list, get, create, update, delete`
|
|
654
1054
|
} catch (err: any) {
|
|
@@ -662,7 +1062,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
662
1062
|
action: z.enum(['list', 'get', 'create', 'update', 'delete']).describe('The CRUD action to perform'),
|
|
663
1063
|
id: z.string().optional().describe('Resource ID (required for get, update, delete)'),
|
|
664
1064
|
data: z.string().optional().describe('JSON string of fields for create/update'),
|
|
665
|
-
}),
|
|
1065
|
+
}).passthrough(),
|
|
666
1066
|
},
|
|
667
1067
|
),
|
|
668
1068
|
)
|