@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
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
7
7
|
import { api } from '@/lib/api-client'
|
|
8
|
+
import { getPluginSourceLabel } from '@/lib/plugin-sources'
|
|
8
9
|
import { toast } from 'sonner'
|
|
9
10
|
import type { PluginMeta, PluginSettingsField, MarketplacePlugin } from '@/types'
|
|
10
11
|
|
|
@@ -176,7 +177,13 @@ export function PluginSheet() {
|
|
|
176
177
|
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
177
178
|
try {
|
|
178
179
|
const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
|
|
179
|
-
await api('POST', '/plugins/install', {
|
|
180
|
+
await api('POST', '/plugins/install', {
|
|
181
|
+
url: p.url,
|
|
182
|
+
filename: safeFilename,
|
|
183
|
+
installMethod: 'marketplace',
|
|
184
|
+
sourceLabel: p.source,
|
|
185
|
+
installSource: p.catalogSource || p.source,
|
|
186
|
+
})
|
|
180
187
|
await loadPlugins()
|
|
181
188
|
toast.success(`Installed ${p.name}`, { id: toastId })
|
|
182
189
|
} catch (err: unknown) {
|
|
@@ -231,19 +238,31 @@ export function PluginSheet() {
|
|
|
231
238
|
</span>
|
|
232
239
|
</div>
|
|
233
240
|
|
|
234
|
-
<div className="grid grid-cols-
|
|
241
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-3">
|
|
235
242
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
236
|
-
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">
|
|
243
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Type</div>
|
|
237
244
|
<div className="text-[11px] text-text-2">
|
|
238
|
-
{editing.isBuiltin
|
|
245
|
+
{editing.isBuiltin
|
|
246
|
+
? 'Core Platform'
|
|
247
|
+
: editing.source === 'marketplace'
|
|
248
|
+
? 'Marketplace Extension'
|
|
249
|
+
: editing.source === 'manual'
|
|
250
|
+
? 'Manual URL Extension'
|
|
251
|
+
: 'Local Extension'}
|
|
239
252
|
</div>
|
|
240
253
|
</div>
|
|
241
254
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
242
|
-
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">
|
|
243
|
-
<div className="text-[11px] text-text-2">{editing.
|
|
255
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Publisher</div>
|
|
256
|
+
<div className="text-[11px] text-text-2">{getPluginSourceLabel(editing.sourceLabel)}</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
259
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Installed Via</div>
|
|
260
|
+
<div className="text-[11px] text-text-2">{getPluginSourceLabel(editing.installSource || editing.sourceLabel)}</div>
|
|
244
261
|
</div>
|
|
245
262
|
</div>
|
|
246
263
|
|
|
264
|
+
<div className="text-[11px] text-text-3/60 mt-2">{editing.author || 'Unknown author'}</div>
|
|
265
|
+
|
|
247
266
|
<div className="text-[11px] font-mono text-text-3/60 mt-3 break-all">{editing.filename}</div>
|
|
248
267
|
<div className="flex items-center gap-1.5 mt-2 flex-wrap">
|
|
249
268
|
{pluginCapabilityBadges(editing).length > 0 ? (
|
|
@@ -255,6 +274,16 @@ export function PluginSheet() {
|
|
|
255
274
|
) : (
|
|
256
275
|
<span className="text-[10px] text-text-3/50">No declared tools/hooks metadata</span>
|
|
257
276
|
)}
|
|
277
|
+
{editing.sourceLabel && (
|
|
278
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300">
|
|
279
|
+
{getPluginSourceLabel(editing.sourceLabel)}
|
|
280
|
+
</span>
|
|
281
|
+
)}
|
|
282
|
+
{editing.installSource && editing.installSource !== editing.sourceLabel && (
|
|
283
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75">
|
|
284
|
+
via {getPluginSourceLabel(editing.installSource)}
|
|
285
|
+
</span>
|
|
286
|
+
)}
|
|
258
287
|
</div>
|
|
259
288
|
|
|
260
289
|
{editing.autoDisabled && (
|
|
@@ -392,7 +421,14 @@ export function PluginSheet() {
|
|
|
392
421
|
const q = search.toLowerCase()
|
|
393
422
|
const filtered = marketplace
|
|
394
423
|
.filter((p) => {
|
|
395
|
-
|
|
424
|
+
const sourceTerms = [getPluginSourceLabel(p.source).toLowerCase(), getPluginSourceLabel(p.catalogSource).toLowerCase()]
|
|
425
|
+
if (
|
|
426
|
+
q
|
|
427
|
+
&& !p.name.toLowerCase().includes(q)
|
|
428
|
+
&& !p.description.toLowerCase().includes(q)
|
|
429
|
+
&& !(p.tags ?? []).some((t) => t.toLowerCase().includes(q))
|
|
430
|
+
&& !sourceTerms.some((term) => term.includes(q))
|
|
431
|
+
) return false
|
|
396
432
|
if (activeTag && !(p.tags ?? []).includes(activeTag)) return false
|
|
397
433
|
return true
|
|
398
434
|
})
|
|
@@ -458,6 +494,18 @@ export function PluginSheet() {
|
|
|
458
494
|
<span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
|
|
459
495
|
{p.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
|
|
460
496
|
</div>
|
|
497
|
+
<div className="flex items-center gap-1.5 mt-2 flex-wrap">
|
|
498
|
+
{p.source && (
|
|
499
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300">
|
|
500
|
+
{getPluginSourceLabel(p.source)}
|
|
501
|
+
</span>
|
|
502
|
+
)}
|
|
503
|
+
{p.catalogSource && p.catalogSource !== p.source && (
|
|
504
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75">
|
|
505
|
+
via {getPluginSourceLabel(p.catalogSource)}
|
|
506
|
+
</span>
|
|
507
|
+
)}
|
|
508
|
+
</div>
|
|
461
509
|
<div className="text-[11px] text-text-3/60 mt-1">{p.description}</div>
|
|
462
510
|
<div className="flex items-center gap-2 mt-2">
|
|
463
511
|
<span className="text-[10px] text-text-3/70">by {p.author}</span>
|
|
@@ -16,6 +16,13 @@ function relativeDate(ts: number): string {
|
|
|
16
16
|
return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function formatHeartbeatInterval(intervalSec?: number | null): string {
|
|
20
|
+
if (!intervalSec || intervalSec <= 0) return 'Manual'
|
|
21
|
+
if (intervalSec % 3600 === 0) return `${intervalSec / 3600}h`
|
|
22
|
+
if (intervalSec % 60 === 0) return `${intervalSec / 60}m`
|
|
23
|
+
return `${intervalSec}s`
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
const STATUS_STYLES: Record<string, string> = {
|
|
20
27
|
backlog: 'bg-white/[0.06] text-text-3',
|
|
21
28
|
queued: 'bg-amber-500/15 text-amber-400',
|
|
@@ -92,6 +99,8 @@ export function ProjectDetail() {
|
|
|
92
99
|
const tasks = useAppStore((s) => s.tasks) as Record<string, BoardTask>
|
|
93
100
|
const schedules = useAppStore((s) => s.schedules) as Record<string, Schedule>
|
|
94
101
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
102
|
+
const secrets = useAppStore((s) => s.secrets)
|
|
103
|
+
const loadSecrets = useAppStore((s) => s.loadSecrets)
|
|
95
104
|
const setEditingProjectId = useAppStore((s) => s.setEditingProjectId)
|
|
96
105
|
const setProjectSheetOpen = useAppStore((s) => s.setProjectSheetOpen)
|
|
97
106
|
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
@@ -100,6 +109,8 @@ export function ProjectDetail() {
|
|
|
100
109
|
const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
|
|
101
110
|
const setEditingScheduleId = useAppStore((s) => s.setEditingScheduleId)
|
|
102
111
|
const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
|
|
112
|
+
const setEditingSecretId = useAppStore((s) => s.setEditingSecretId)
|
|
113
|
+
const setSecretSheetOpen = useAppStore((s) => s.setSecretSheetOpen)
|
|
103
114
|
|
|
104
115
|
const [assignPickerOpen, setAssignPickerOpen] = useState(false)
|
|
105
116
|
const [now, setNow] = useState(() => Date.now())
|
|
@@ -109,6 +120,11 @@ export function ProjectDetail() {
|
|
|
109
120
|
return () => window.clearInterval(intervalId)
|
|
110
121
|
}, [])
|
|
111
122
|
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!activeProjectFilter) return
|
|
125
|
+
void loadSecrets()
|
|
126
|
+
}, [activeProjectFilter, loadSecrets])
|
|
127
|
+
|
|
112
128
|
const project = activeProjectFilter ? projects[activeProjectFilter] : null
|
|
113
129
|
|
|
114
130
|
const projectAgents = useMemo(
|
|
@@ -128,6 +144,11 @@ export function ProjectDetail() {
|
|
|
128
144
|
[schedules, activeProjectFilter],
|
|
129
145
|
)
|
|
130
146
|
|
|
147
|
+
const projectSecrets = useMemo(
|
|
148
|
+
() => Object.values(secrets).filter((secret) => secret.projectId === activeProjectFilter),
|
|
149
|
+
[secrets, activeProjectFilter],
|
|
150
|
+
)
|
|
151
|
+
|
|
131
152
|
const completedTasks = projectTasks.filter((t) => t.status === 'completed').length
|
|
132
153
|
const totalTasks = projectTasks.length
|
|
133
154
|
const progressPct = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0
|
|
@@ -188,6 +209,23 @@ export function ProjectDetail() {
|
|
|
188
209
|
[now, projectSchedules],
|
|
189
210
|
)
|
|
190
211
|
|
|
212
|
+
const capabilityHints = Array.isArray(project?.capabilityHints) ? project.capabilityHints : []
|
|
213
|
+
const priorities = Array.isArray(project?.priorities) ? project.priorities : []
|
|
214
|
+
const openObjectives = Array.isArray(project?.openObjectives) ? project.openObjectives : []
|
|
215
|
+
const credentialRequirements = Array.isArray(project?.credentialRequirements) ? project.credentialRequirements : []
|
|
216
|
+
const successMetrics = Array.isArray(project?.successMetrics) ? project.successMetrics : []
|
|
217
|
+
const hasOperatingContext = Boolean(
|
|
218
|
+
project?.objective
|
|
219
|
+
|| project?.audience
|
|
220
|
+
|| priorities.length
|
|
221
|
+
|| openObjectives.length
|
|
222
|
+
|| capabilityHints.length
|
|
223
|
+
|| credentialRequirements.length
|
|
224
|
+
|| successMetrics.length
|
|
225
|
+
|| project?.heartbeatPrompt
|
|
226
|
+
|| project?.heartbeatIntervalSec,
|
|
227
|
+
)
|
|
228
|
+
|
|
191
229
|
const busiestAgent = useMemo(() => {
|
|
192
230
|
return projectAgents
|
|
193
231
|
.map((agent) => ({
|
|
@@ -305,6 +343,185 @@ export function ProjectDetail() {
|
|
|
305
343
|
</div>
|
|
306
344
|
</div>
|
|
307
345
|
|
|
346
|
+
<div className="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_280px] gap-4 mb-8">
|
|
347
|
+
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-5 py-5">
|
|
348
|
+
<div className="flex items-center justify-between gap-4 mb-4">
|
|
349
|
+
<div>
|
|
350
|
+
<h2 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">Project Operating System</h2>
|
|
351
|
+
<p className="text-[12px] text-text-3/60 mt-1">Define what this project is trying to achieve, how agents should operate, and what long-lived context matters.</p>
|
|
352
|
+
</div>
|
|
353
|
+
<button
|
|
354
|
+
onClick={() => { setEditingProjectId(project.id); setProjectSheetOpen(true) }}
|
|
355
|
+
className="shrink-0 px-3 py-2 rounded-[10px] bg-white/[0.04] text-[12px] font-600 text-text-2 hover:bg-white/[0.08] transition-all cursor-pointer border-none"
|
|
356
|
+
style={{ fontFamily: 'inherit' }}
|
|
357
|
+
>
|
|
358
|
+
Configure
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
{hasOperatingContext ? (
|
|
363
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
364
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-4 py-3.5">
|
|
365
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/50 mb-2">Mission</div>
|
|
366
|
+
<div className="space-y-3">
|
|
367
|
+
<div>
|
|
368
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Objective</div>
|
|
369
|
+
<p className="text-[13px] text-text leading-relaxed">{project.objective || 'Add a durable objective for this project.'}</p>
|
|
370
|
+
</div>
|
|
371
|
+
<div>
|
|
372
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Audience</div>
|
|
373
|
+
<p className="text-[13px] text-text-2/80 leading-relaxed">{project.audience || 'Set who this project is for so agents can answer from that context.'}</p>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-4 py-3.5">
|
|
379
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/50 mb-2">Execution Focus</div>
|
|
380
|
+
<div className="space-y-3">
|
|
381
|
+
<div>
|
|
382
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Pilot priorities</div>
|
|
383
|
+
{priorities.length > 0 ? (
|
|
384
|
+
<div className="flex flex-wrap gap-1.5">
|
|
385
|
+
{priorities.map((priority) => (
|
|
386
|
+
<span key={priority} className="rounded-full bg-accent-soft px-2.5 py-1 text-[11px] font-600 text-accent-bright">
|
|
387
|
+
{priority}
|
|
388
|
+
</span>
|
|
389
|
+
))}
|
|
390
|
+
</div>
|
|
391
|
+
) : (
|
|
392
|
+
<p className="text-[12px] text-text-3/50">No priorities captured yet.</p>
|
|
393
|
+
)}
|
|
394
|
+
</div>
|
|
395
|
+
<div>
|
|
396
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Open objectives</div>
|
|
397
|
+
{openObjectives.length > 0 ? (
|
|
398
|
+
<div className="space-y-1.5">
|
|
399
|
+
{openObjectives.map((objective) => (
|
|
400
|
+
<div key={objective} className="flex items-start gap-2 text-[12px] text-text-2">
|
|
401
|
+
<span className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-emerald-400" />
|
|
402
|
+
<span>{objective}</span>
|
|
403
|
+
</div>
|
|
404
|
+
))}
|
|
405
|
+
</div>
|
|
406
|
+
) : (
|
|
407
|
+
<p className="text-[12px] text-text-3/50">No open objectives captured yet.</p>
|
|
408
|
+
)}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-4 py-3.5">
|
|
414
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/50 mb-2">Operating Modes</div>
|
|
415
|
+
{capabilityHints.length > 0 ? (
|
|
416
|
+
<div className="flex flex-wrap gap-1.5">
|
|
417
|
+
{capabilityHints.map((hint) => (
|
|
418
|
+
<span key={hint} className="rounded-full bg-white/[0.06] px-2.5 py-1 text-[11px] font-600 text-text-2">
|
|
419
|
+
{hint}
|
|
420
|
+
</span>
|
|
421
|
+
))}
|
|
422
|
+
</div>
|
|
423
|
+
) : (
|
|
424
|
+
<p className="text-[12px] text-text-3/50">No capability hints yet. Add things like research, build, browsing, inbox ops, or credential bootstrapping.</p>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-4 py-3.5">
|
|
429
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/50 mb-2">Success Metrics</div>
|
|
430
|
+
{successMetrics.length > 0 ? (
|
|
431
|
+
<div className="space-y-1.5">
|
|
432
|
+
{successMetrics.map((metric) => (
|
|
433
|
+
<div key={metric} className="flex items-start gap-2 text-[12px] text-text-2">
|
|
434
|
+
<span className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-sky-400" />
|
|
435
|
+
<span>{metric}</span>
|
|
436
|
+
</div>
|
|
437
|
+
))}
|
|
438
|
+
</div>
|
|
439
|
+
) : (
|
|
440
|
+
<p className="text-[12px] text-text-3/50">Define success metrics if this project has open-ended goals like revenue, outreach, or inbox response quality.</p>
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
) : (
|
|
445
|
+
<div className="rounded-[12px] border border-dashed border-white/[0.08] px-5 py-6 text-center">
|
|
446
|
+
<p className="text-[13px] font-600 text-text-2">This project still needs operating context.</p>
|
|
447
|
+
<p className="mt-1 text-[12px] text-text-3/55">Add objective, open objectives, capability hints, credential needs, and heartbeat settings so agents can treat the project as a durable operating system.</p>
|
|
448
|
+
</div>
|
|
449
|
+
)}
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-5 py-5">
|
|
453
|
+
<div className="flex items-center justify-between mb-3">
|
|
454
|
+
<h3 className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Ops Readiness</h3>
|
|
455
|
+
<span className="text-[11px] text-text-3/40">{projectSecrets.length} secret{projectSecrets.length === 1 ? '' : 's'}</span>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
459
|
+
{[
|
|
460
|
+
{ label: 'Linked secrets', value: projectSecrets.length, tone: 'text-emerald-400' },
|
|
461
|
+
{ label: 'Credential reqs', value: credentialRequirements.length, tone: 'text-amber-400' },
|
|
462
|
+
{ label: 'Heartbeat', value: project.heartbeatIntervalSec ? formatHeartbeatInterval(project.heartbeatIntervalSec) : 'Off', tone: 'text-sky-400' },
|
|
463
|
+
{ label: 'Schedules', value: projectSchedules.length, tone: 'text-text-2' },
|
|
464
|
+
].map((item) => (
|
|
465
|
+
<div key={item.label} className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-3 py-3">
|
|
466
|
+
<div className={`text-[18px] font-display font-700 tracking-[-0.02em] ${item.tone}`}>{item.value}</div>
|
|
467
|
+
<div className="mt-1 text-[10px] font-600 uppercase tracking-[0.08em] text-text-3/45">{item.label}</div>
|
|
468
|
+
</div>
|
|
469
|
+
))}
|
|
470
|
+
</div>
|
|
471
|
+
|
|
472
|
+
<div className="space-y-3">
|
|
473
|
+
<div>
|
|
474
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Credential requirements</div>
|
|
475
|
+
{credentialRequirements.length > 0 ? (
|
|
476
|
+
<div className="space-y-1.5">
|
|
477
|
+
{credentialRequirements.map((item) => (
|
|
478
|
+
<div key={item} className="flex items-start gap-2 text-[12px] text-text-2">
|
|
479
|
+
<span className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-amber-400" />
|
|
480
|
+
<span>{item}</span>
|
|
481
|
+
</div>
|
|
482
|
+
))}
|
|
483
|
+
</div>
|
|
484
|
+
) : (
|
|
485
|
+
<p className="text-[12px] text-text-3/50">No credentials requested yet.</p>
|
|
486
|
+
)}
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<div>
|
|
490
|
+
<div className="text-[11px] font-600 text-text-3/50 mb-1">Heartbeat</div>
|
|
491
|
+
{project.heartbeatPrompt || project.heartbeatIntervalSec ? (
|
|
492
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface/60 px-3 py-3">
|
|
493
|
+
<div className="text-[11px] font-700 uppercase tracking-[0.08em] text-sky-400">
|
|
494
|
+
Every {formatHeartbeatInterval(project.heartbeatIntervalSec)}
|
|
495
|
+
</div>
|
|
496
|
+
<p className="mt-1 text-[12px] text-text-2 leading-relaxed">
|
|
497
|
+
{project.heartbeatPrompt || 'No heartbeat prompt configured.'}
|
|
498
|
+
</p>
|
|
499
|
+
</div>
|
|
500
|
+
) : (
|
|
501
|
+
<p className="text-[12px] text-text-3/50">No project heartbeat configured.</p>
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div className="flex flex-wrap gap-2 pt-1">
|
|
506
|
+
<button
|
|
507
|
+
onClick={() => { setEditingSecretId(null); setSecretSheetOpen(true) }}
|
|
508
|
+
className="px-3 py-2 rounded-[10px] bg-accent-soft text-[12px] font-600 text-accent-bright hover:bg-accent-bright/15 transition-all cursor-pointer border-none"
|
|
509
|
+
style={{ fontFamily: 'inherit' }}
|
|
510
|
+
>
|
|
511
|
+
Add project secret
|
|
512
|
+
</button>
|
|
513
|
+
<button
|
|
514
|
+
onClick={() => { setEditingScheduleId(null); setScheduleSheetOpen(true) }}
|
|
515
|
+
className="px-3 py-2 rounded-[10px] bg-white/[0.04] text-[12px] font-600 text-text-2 hover:bg-white/[0.08] transition-all cursor-pointer border-none"
|
|
516
|
+
style={{ fontFamily: 'inherit' }}
|
|
517
|
+
>
|
|
518
|
+
Add heartbeat schedule
|
|
519
|
+
</button>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
308
525
|
<div className="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_280px] gap-4 mb-8">
|
|
309
526
|
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-5 py-5">
|
|
310
527
|
<div className="flex items-center justify-between gap-4 mb-4">
|
|
@@ -12,6 +12,25 @@ const PROJECT_COLORS = [
|
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
const inputClass = 'w-full px-3 py-2.5 rounded-lg bg-white/[0.06] border border-white/[0.06] text-[13px] text-text-1 placeholder:text-text-3/40 focus:outline-none focus:border-accent/40 transition-colors'
|
|
15
|
+
const sectionTitleClass = 'block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2'
|
|
16
|
+
|
|
17
|
+
function listToText(values?: string[]) {
|
|
18
|
+
return Array.isArray(values) ? values.join('\n') : ''
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function textToList(value: string) {
|
|
22
|
+
return value
|
|
23
|
+
.split('\n')
|
|
24
|
+
.map((entry) => entry.trim())
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseOptionalInteger(value: string) {
|
|
29
|
+
const trimmed = value.trim()
|
|
30
|
+
if (!trimmed) return undefined
|
|
31
|
+
const parsed = Number.parseInt(trimmed, 10)
|
|
32
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
33
|
+
}
|
|
15
34
|
|
|
16
35
|
export function ProjectSheet() {
|
|
17
36
|
const open = useAppStore((s) => s.projectSheetOpen)
|
|
@@ -24,6 +43,15 @@ export function ProjectSheet() {
|
|
|
24
43
|
const [name, setName] = useState('')
|
|
25
44
|
const [description, setDescription] = useState('')
|
|
26
45
|
const [color, setColor] = useState<string | undefined>(undefined)
|
|
46
|
+
const [objective, setObjective] = useState('')
|
|
47
|
+
const [audience, setAudience] = useState('')
|
|
48
|
+
const [prioritiesText, setPrioritiesText] = useState('')
|
|
49
|
+
const [openObjectivesText, setOpenObjectivesText] = useState('')
|
|
50
|
+
const [capabilityHintsText, setCapabilityHintsText] = useState('')
|
|
51
|
+
const [credentialRequirementsText, setCredentialRequirementsText] = useState('')
|
|
52
|
+
const [successMetricsText, setSuccessMetricsText] = useState('')
|
|
53
|
+
const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
|
|
54
|
+
const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('')
|
|
27
55
|
|
|
28
56
|
const editing = editingId ? projects[editingId] : null
|
|
29
57
|
|
|
@@ -33,10 +61,28 @@ export function ProjectSheet() {
|
|
|
33
61
|
setName(editing.name)
|
|
34
62
|
setDescription(editing.description)
|
|
35
63
|
setColor(editing.color)
|
|
64
|
+
setObjective(editing.objective || '')
|
|
65
|
+
setAudience(editing.audience || '')
|
|
66
|
+
setPrioritiesText(listToText(editing.priorities))
|
|
67
|
+
setOpenObjectivesText(listToText(editing.openObjectives))
|
|
68
|
+
setCapabilityHintsText(listToText(editing.capabilityHints))
|
|
69
|
+
setCredentialRequirementsText(listToText(editing.credentialRequirements))
|
|
70
|
+
setSuccessMetricsText(listToText(editing.successMetrics))
|
|
71
|
+
setHeartbeatPrompt(editing.heartbeatPrompt || '')
|
|
72
|
+
setHeartbeatIntervalSec(editing.heartbeatIntervalSec ? String(editing.heartbeatIntervalSec) : '')
|
|
36
73
|
} else {
|
|
37
74
|
setName('')
|
|
38
75
|
setDescription('')
|
|
39
76
|
setColor(PROJECT_COLORS[0])
|
|
77
|
+
setObjective('')
|
|
78
|
+
setAudience('')
|
|
79
|
+
setPrioritiesText('')
|
|
80
|
+
setOpenObjectivesText('')
|
|
81
|
+
setCapabilityHintsText('')
|
|
82
|
+
setCredentialRequirementsText('')
|
|
83
|
+
setSuccessMetricsText('')
|
|
84
|
+
setHeartbeatPrompt('')
|
|
85
|
+
setHeartbeatIntervalSec('')
|
|
40
86
|
}
|
|
41
87
|
}
|
|
42
88
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -52,6 +98,15 @@ export function ProjectSheet() {
|
|
|
52
98
|
name: name.trim() || 'Unnamed Project',
|
|
53
99
|
description,
|
|
54
100
|
color,
|
|
101
|
+
objective: objective.trim() || undefined,
|
|
102
|
+
audience: audience.trim() || undefined,
|
|
103
|
+
priorities: textToList(prioritiesText),
|
|
104
|
+
openObjectives: textToList(openObjectivesText),
|
|
105
|
+
capabilityHints: textToList(capabilityHintsText),
|
|
106
|
+
credentialRequirements: textToList(credentialRequirementsText),
|
|
107
|
+
successMetrics: textToList(successMetricsText),
|
|
108
|
+
heartbeatPrompt: heartbeatPrompt.trim() || undefined,
|
|
109
|
+
heartbeatIntervalSec: parseOptionalInteger(heartbeatIntervalSec),
|
|
55
110
|
}
|
|
56
111
|
if (editing) {
|
|
57
112
|
await updateProject(editing.id, data)
|
|
@@ -72,10 +127,10 @@ export function ProjectSheet() {
|
|
|
72
127
|
}
|
|
73
128
|
|
|
74
129
|
return (
|
|
75
|
-
<BottomSheet open={open} onClose={onClose}>
|
|
130
|
+
<BottomSheet open={open} onClose={onClose} wide>
|
|
76
131
|
<h2 className="font-display text-[18px] font-700 text-text mb-6">{editing ? 'Edit Project' : 'New Project'}</h2>
|
|
77
132
|
<div className="mb-6">
|
|
78
|
-
<label className=
|
|
133
|
+
<label className={sectionTitleClass}>Name</label>
|
|
79
134
|
<input
|
|
80
135
|
type="text"
|
|
81
136
|
value={name}
|
|
@@ -88,7 +143,7 @@ export function ProjectSheet() {
|
|
|
88
143
|
</div>
|
|
89
144
|
|
|
90
145
|
<div className="mb-6">
|
|
91
|
-
<label className=
|
|
146
|
+
<label className={sectionTitleClass}>Description</label>
|
|
92
147
|
<textarea
|
|
93
148
|
value={description}
|
|
94
149
|
onChange={(e) => setDescription(e.target.value)}
|
|
@@ -99,8 +154,125 @@ export function ProjectSheet() {
|
|
|
99
154
|
/>
|
|
100
155
|
</div>
|
|
101
156
|
|
|
157
|
+
<div className="grid gap-6 sm:grid-cols-2 mb-6">
|
|
158
|
+
<div>
|
|
159
|
+
<label className={sectionTitleClass}>Objective</label>
|
|
160
|
+
<textarea
|
|
161
|
+
value={objective}
|
|
162
|
+
onChange={(e) => setObjective(e.target.value)}
|
|
163
|
+
placeholder="What durable outcome is this project driving?"
|
|
164
|
+
className={inputClass + ' min-h-[88px] resize-y'}
|
|
165
|
+
style={{ fontFamily: 'inherit' }}
|
|
166
|
+
rows={4}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
<div>
|
|
170
|
+
<label className={sectionTitleClass}>Audience</label>
|
|
171
|
+
<textarea
|
|
172
|
+
value={audience}
|
|
173
|
+
onChange={(e) => setAudience(e.target.value)}
|
|
174
|
+
placeholder="Who is this project for?"
|
|
175
|
+
className={inputClass + ' min-h-[88px] resize-y'}
|
|
176
|
+
style={{ fontFamily: 'inherit' }}
|
|
177
|
+
rows={4}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="grid gap-6 sm:grid-cols-2 mb-6">
|
|
183
|
+
<div>
|
|
184
|
+
<label className={sectionTitleClass}>Pilot Priorities</label>
|
|
185
|
+
<textarea
|
|
186
|
+
value={prioritiesText}
|
|
187
|
+
onChange={(e) => setPrioritiesText(e.target.value)}
|
|
188
|
+
placeholder={'One per line\nResearch the market\nBuild the pilot'}
|
|
189
|
+
className={inputClass + ' min-h-[110px] resize-y'}
|
|
190
|
+
style={{ fontFamily: 'inherit' }}
|
|
191
|
+
rows={5}
|
|
192
|
+
/>
|
|
193
|
+
<p className="mt-2 text-[11px] text-text-3/45">One priority per line.</p>
|
|
194
|
+
</div>
|
|
195
|
+
<div>
|
|
196
|
+
<label className={sectionTitleClass}>Open Objectives</label>
|
|
197
|
+
<textarea
|
|
198
|
+
value={openObjectivesText}
|
|
199
|
+
onChange={(e) => setOpenObjectivesText(e.target.value)}
|
|
200
|
+
placeholder={'One per line\nDraft the research brief\nPrepare the rollout checklist'}
|
|
201
|
+
className={inputClass + ' min-h-[110px] resize-y'}
|
|
202
|
+
style={{ fontFamily: 'inherit' }}
|
|
203
|
+
rows={5}
|
|
204
|
+
/>
|
|
205
|
+
<p className="mt-2 text-[11px] text-text-3/45">Use this for durable next outcomes, not one-off chat prompts.</p>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div className="grid gap-6 sm:grid-cols-2 mb-6">
|
|
210
|
+
<div>
|
|
211
|
+
<label className={sectionTitleClass}>Capability Hints</label>
|
|
212
|
+
<textarea
|
|
213
|
+
value={capabilityHintsText}
|
|
214
|
+
onChange={(e) => setCapabilityHintsText(e.target.value)}
|
|
215
|
+
placeholder={'One per line\nResearch\nWeb browsing\nInbox automation'}
|
|
216
|
+
className={inputClass + ' min-h-[110px] resize-y'}
|
|
217
|
+
style={{ fontFamily: 'inherit' }}
|
|
218
|
+
rows={5}
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
<div>
|
|
222
|
+
<label className={sectionTitleClass}>Credential Requirements</label>
|
|
223
|
+
<textarea
|
|
224
|
+
value={credentialRequirementsText}
|
|
225
|
+
onChange={(e) => setCredentialRequirementsText(e.target.value)}
|
|
226
|
+
placeholder={'One per line\nGmail app password\nCRM API token'}
|
|
227
|
+
className={inputClass + ' min-h-[110px] resize-y'}
|
|
228
|
+
style={{ fontFamily: 'inherit' }}
|
|
229
|
+
rows={5}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div className="grid gap-6 sm:grid-cols-2 mb-6">
|
|
235
|
+
<div>
|
|
236
|
+
<label className={sectionTitleClass}>Success Metrics</label>
|
|
237
|
+
<textarea
|
|
238
|
+
value={successMetricsText}
|
|
239
|
+
onChange={(e) => setSuccessMetricsText(e.target.value)}
|
|
240
|
+
placeholder={'One per line\nReduce response time below 10 minutes\nIncrease qualified replies'}
|
|
241
|
+
className={inputClass + ' min-h-[96px] resize-y'}
|
|
242
|
+
style={{ fontFamily: 'inherit' }}
|
|
243
|
+
rows={4}
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="grid gap-4">
|
|
247
|
+
<div>
|
|
248
|
+
<label className={sectionTitleClass}>Heartbeat Prompt</label>
|
|
249
|
+
<textarea
|
|
250
|
+
value={heartbeatPrompt}
|
|
251
|
+
onChange={(e) => setHeartbeatPrompt(e.target.value)}
|
|
252
|
+
placeholder="What should the project heartbeat ask the agent to review?"
|
|
253
|
+
className={inputClass + ' min-h-[72px] resize-y'}
|
|
254
|
+
style={{ fontFamily: 'inherit' }}
|
|
255
|
+
rows={3}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<label className={sectionTitleClass}>Heartbeat Interval (seconds)</label>
|
|
260
|
+
<input
|
|
261
|
+
type="number"
|
|
262
|
+
min={0}
|
|
263
|
+
step={60}
|
|
264
|
+
value={heartbeatIntervalSec}
|
|
265
|
+
onChange={(e) => setHeartbeatIntervalSec(e.target.value)}
|
|
266
|
+
placeholder="1800"
|
|
267
|
+
className={inputClass}
|
|
268
|
+
style={{ fontFamily: 'inherit' }}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
102
274
|
<div className="mb-8">
|
|
103
|
-
<label className=
|
|
275
|
+
<label className={sectionTitleClass}>Color</label>
|
|
104
276
|
<div className="flex items-center gap-2">
|
|
105
277
|
{PROJECT_COLORS.map((c) => (
|
|
106
278
|
<button
|
|
@@ -125,6 +125,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
125
125
|
ok: boolean
|
|
126
126
|
verify?: {
|
|
127
127
|
ok: boolean
|
|
128
|
+
message?: string
|
|
128
129
|
error?: string
|
|
129
130
|
hint?: string
|
|
130
131
|
models?: string[]
|
|
@@ -150,7 +151,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
150
151
|
lastVerifiedOk: verify.verify ? verifiedOk : (existing?.deployment?.lastVerifiedOk ?? null),
|
|
151
152
|
lastVerifiedMessage: verify.verify
|
|
152
153
|
? (verifiedOk
|
|
153
|
-
? `Verified during save with ${verify.verify.models?.length || 0} model${(verify.verify.models?.length || 0) === 1 ? '' : 's'}.`
|
|
154
|
+
? (verify.verify.message || `Verified during save with ${verify.verify.models?.length || 0} model${(verify.verify.models?.length || 0) === 1 ? '' : 's'}.`)
|
|
154
155
|
: (verify.verify.error || verify.verify.hint || 'Verification failed.'))
|
|
155
156
|
: (existing?.deployment?.lastVerifiedMessage || null),
|
|
156
157
|
},
|