@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
package/src/cli/index.test.js
CHANGED
|
@@ -163,6 +163,37 @@ test('runCli sends authenticated request and emits compact JSON when --json is s
|
|
|
163
163
|
assert.equal(stderr.toString(), '')
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
+
test('runCli falls back to platform-api-key.txt when env key is missing', async () => {
|
|
167
|
+
const stdout = makeWritable()
|
|
168
|
+
const stderr = makeWritable()
|
|
169
|
+
const calls = []
|
|
170
|
+
|
|
171
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-cli-keyfile-'))
|
|
172
|
+
fs.writeFileSync(path.join(tmpDir, 'platform-api-key.txt'), 'file-key\n', 'utf8')
|
|
173
|
+
|
|
174
|
+
const fetchImpl = async (url, init) => {
|
|
175
|
+
calls.push({ url: String(url), init })
|
|
176
|
+
return jsonResponse({ ok: true })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const exitCode = await runCli(
|
|
180
|
+
['runs', 'list', '--json'],
|
|
181
|
+
{
|
|
182
|
+
fetchImpl,
|
|
183
|
+
stdout,
|
|
184
|
+
stderr,
|
|
185
|
+
env: {},
|
|
186
|
+
cwd: tmpDir,
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
assert.equal(exitCode, 0)
|
|
191
|
+
assert.equal(calls[0].init.headers['X-Access-Key'], 'file-key')
|
|
192
|
+
assert.equal(stderr.toString(), '')
|
|
193
|
+
|
|
194
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
195
|
+
})
|
|
196
|
+
|
|
166
197
|
test('upload command sends binary body and x-filename header', async () => {
|
|
167
198
|
const stdout = makeWritable()
|
|
168
199
|
const stderr = makeWritable()
|
|
@@ -197,6 +228,32 @@ test('upload command sends binary body and x-filename header', async () => {
|
|
|
197
228
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
198
229
|
})
|
|
199
230
|
|
|
231
|
+
test('binary responses require --out when stdout is a TTY', async () => {
|
|
232
|
+
const stdout = makeWritable()
|
|
233
|
+
stdout.isTTY = true
|
|
234
|
+
const stderr = makeWritable()
|
|
235
|
+
|
|
236
|
+
const fetchImpl = async () =>
|
|
237
|
+
new Response(Buffer.from('hello'), {
|
|
238
|
+
status: 200,
|
|
239
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const exitCode = await runCli(
|
|
243
|
+
['uploads', 'get', 'artifact.bin'],
|
|
244
|
+
{
|
|
245
|
+
fetchImpl,
|
|
246
|
+
stdout,
|
|
247
|
+
stderr,
|
|
248
|
+
env: {},
|
|
249
|
+
cwd: process.cwd(),
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
assert.equal(exitCode, 1)
|
|
254
|
+
assert.match(stderr.toString(), /binary response requires --out <file>/i)
|
|
255
|
+
})
|
|
256
|
+
|
|
200
257
|
test('wait polls run endpoint until terminal state', async () => {
|
|
201
258
|
const stdout = makeWritable()
|
|
202
259
|
const stderr = makeWritable()
|
|
@@ -236,6 +293,144 @@ test('wait polls run endpoint until terminal state', async () => {
|
|
|
236
293
|
assert.match(stdout.toString(), /"status": "completed"/)
|
|
237
294
|
})
|
|
238
295
|
|
|
296
|
+
test('runCli parses CRLF-delimited SSE events correctly', async () => {
|
|
297
|
+
const stdout = makeWritable()
|
|
298
|
+
const stderr = makeWritable()
|
|
299
|
+
|
|
300
|
+
const fetchImpl = async () => new Response(
|
|
301
|
+
'data: {"t":"md","text":"first"}\r\n\r\ndata: {"t":"md","text":"second"}\r\n\r\n',
|
|
302
|
+
{
|
|
303
|
+
status: 200,
|
|
304
|
+
headers: { 'content-type': 'text/event-stream' },
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
const exitCode = await runCli(
|
|
309
|
+
['chatrooms', 'chat', 'room-1', '--data', '{}'],
|
|
310
|
+
{
|
|
311
|
+
fetchImpl,
|
|
312
|
+
stdout,
|
|
313
|
+
stderr,
|
|
314
|
+
env: {},
|
|
315
|
+
cwd: process.cwd(),
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
assert.equal(exitCode, 0)
|
|
320
|
+
assert.equal(stdout.toString(), 'first\nsecond\n')
|
|
321
|
+
assert.equal(stderr.toString(), '')
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test('binary responses require --out when stdout is a TTY', async () => {
|
|
325
|
+
const stdout = makeWritable()
|
|
326
|
+
stdout.isTTY = true
|
|
327
|
+
const stderr = makeWritable()
|
|
328
|
+
|
|
329
|
+
const fetchImpl = async () => new Response(Buffer.from('ok'), {
|
|
330
|
+
status: 200,
|
|
331
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
const exitCode = await runCli(
|
|
335
|
+
['memory-images', 'get', 'image-1.png'],
|
|
336
|
+
{
|
|
337
|
+
fetchImpl,
|
|
338
|
+
stdout,
|
|
339
|
+
stderr,
|
|
340
|
+
env: {},
|
|
341
|
+
cwd: process.cwd(),
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
assert.equal(exitCode, 1)
|
|
346
|
+
assert.equal(stdout.toString(), '')
|
|
347
|
+
assert.match(stderr.toString(), /binary response requires --out <file>/i)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test('client-side collection lookups fail cleanly when the entity is missing', async () => {
|
|
351
|
+
const stdout = makeWritable()
|
|
352
|
+
const stderr = makeWritable()
|
|
353
|
+
|
|
354
|
+
const fetchImpl = async () => jsonResponse([{ id: 'agent-2', name: 'Other Agent' }])
|
|
355
|
+
|
|
356
|
+
const exitCode = await runCli(
|
|
357
|
+
['agents', 'get', 'agent-1'],
|
|
358
|
+
{
|
|
359
|
+
fetchImpl,
|
|
360
|
+
stdout,
|
|
361
|
+
stderr,
|
|
362
|
+
env: {},
|
|
363
|
+
cwd: process.cwd(),
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
assert.equal(exitCode, 1)
|
|
368
|
+
assert.equal(stdout.toString(), '')
|
|
369
|
+
assert.match(stderr.toString(), /entity not found for id: agent-1/i)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('runCli loads request JSON from @file inputs', async () => {
|
|
373
|
+
const stdout = makeWritable()
|
|
374
|
+
const stderr = makeWritable()
|
|
375
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-cli-data-'))
|
|
376
|
+
const dataPath = path.join(tmpDir, 'payload.json')
|
|
377
|
+
fs.writeFileSync(dataPath, JSON.stringify({ title: 'From file', status: 'backlog' }), 'utf8')
|
|
378
|
+
|
|
379
|
+
const calls = []
|
|
380
|
+
const fetchImpl = async (url, init) => {
|
|
381
|
+
calls.push({ url: String(url), init })
|
|
382
|
+
return jsonResponse({ ok: true })
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const exitCode = await runCli(
|
|
386
|
+
['tasks', 'create', '--data', `@${dataPath}`],
|
|
387
|
+
{
|
|
388
|
+
fetchImpl,
|
|
389
|
+
stdout,
|
|
390
|
+
stderr,
|
|
391
|
+
env: {},
|
|
392
|
+
cwd: process.cwd(),
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
assert.equal(exitCode, 0)
|
|
397
|
+
assert.equal(calls.length, 1)
|
|
398
|
+
assert.equal(calls[0].init.headers['Content-Type'], 'application/json')
|
|
399
|
+
assert.deepEqual(JSON.parse(calls[0].init.body), { title: 'From file', status: 'backlog' })
|
|
400
|
+
|
|
401
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
test('runCli falls back to platform-api-key.txt when no env key is provided', async () => {
|
|
405
|
+
const stdout = makeWritable()
|
|
406
|
+
const stderr = makeWritable()
|
|
407
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-cli-key-'))
|
|
408
|
+
fs.writeFileSync(path.join(tmpDir, 'platform-api-key.txt'), 'file-key\n', 'utf8')
|
|
409
|
+
|
|
410
|
+
const calls = []
|
|
411
|
+
const fetchImpl = async (url, init) => {
|
|
412
|
+
calls.push({ url: String(url), init })
|
|
413
|
+
return jsonResponse({ ok: true })
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const exitCode = await runCli(
|
|
417
|
+
['runs', 'list'],
|
|
418
|
+
{
|
|
419
|
+
fetchImpl,
|
|
420
|
+
stdout,
|
|
421
|
+
stderr,
|
|
422
|
+
env: {},
|
|
423
|
+
cwd: tmpDir,
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
assert.equal(exitCode, 0)
|
|
428
|
+
assert.equal(calls.length, 1)
|
|
429
|
+
assert.equal(calls[0].init.headers['X-Access-Key'], 'file-key')
|
|
430
|
+
|
|
431
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
432
|
+
})
|
|
433
|
+
|
|
239
434
|
test('all command definitions execute with a mocked API transport', async () => {
|
|
240
435
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-cli-all-'))
|
|
241
436
|
const uploadPath = path.join(tmpDir, 'upload.txt')
|
package/src/cli/index.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
5
7
|
import {
|
|
6
8
|
SETUP_PROVIDERS,
|
|
7
9
|
DEFAULT_AGENTS,
|
|
@@ -17,7 +19,6 @@ interface CliContext {
|
|
|
17
19
|
|
|
18
20
|
interface SetupAuthStatus {
|
|
19
21
|
firstTime?: boolean
|
|
20
|
-
key?: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
interface SetupProviderCheckResponse {
|
|
@@ -34,7 +35,21 @@ const DEFAULT_BASE_URL =
|
|
|
34
35
|
|| process.env.SWARMCLAW_BASE_URL
|
|
35
36
|
|| 'http://localhost:3456'
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
function resolveDefaultAccessKey(cwd: string = process.cwd()): string {
|
|
39
|
+
const envKey = (
|
|
40
|
+
process.env.SWARMCLAW_ACCESS_KEY
|
|
41
|
+
|| process.env.SWARMCLAW_API_KEY
|
|
42
|
+
|| process.env.SC_ACCESS_KEY
|
|
43
|
+
|| ''
|
|
44
|
+
).trim()
|
|
45
|
+
if (envKey) return envKey
|
|
46
|
+
|
|
47
|
+
const keyFile = path.join(cwd, 'platform-api-key.txt')
|
|
48
|
+
if (!fs.existsSync(keyFile)) return ''
|
|
49
|
+
return fs.readFileSync(keyFile, 'utf8').trim()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const DEFAULT_ACCESS_KEY = resolveDefaultAccessKey()
|
|
38
53
|
|
|
39
54
|
function normalizeBaseUrl(value: string): string {
|
|
40
55
|
const trimmed = value.trim()
|
|
@@ -208,19 +223,13 @@ async function resolveSetupAccessKey(ctx: CliContext): Promise<{
|
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
const status = await apiRequestWithAccessKey<SetupAuthStatus>(ctx, 'GET', '/auth', undefined)
|
|
211
|
-
const discoveredKey = typeof status?.key === 'string' ? status.key.trim() : ''
|
|
212
226
|
const firstTime = status?.firstTime === true
|
|
213
227
|
|
|
214
|
-
if (
|
|
215
|
-
throw new Error('No access key provided.
|
|
228
|
+
if (firstTime) {
|
|
229
|
+
throw new Error('No access key provided. Read the generated key from the launch terminal or .env.local, then pass --key, set SWARMCLAW_ACCESS_KEY / SWARMCLAW_API_KEY, or use platform-api-key.txt.')
|
|
216
230
|
}
|
|
217
231
|
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
accessKey: discoveredKey,
|
|
221
|
-
firstTime: true,
|
|
222
|
-
autoDiscovered: true,
|
|
223
|
-
}
|
|
232
|
+
throw new Error('No access key provided. Pass --key, set SWARMCLAW_ACCESS_KEY / SWARMCLAW_API_KEY, or use platform-api-key.txt.')
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
function printResult(value: unknown, rawOutput: boolean): void {
|
|
@@ -1364,7 +1373,7 @@ export async function runCli(argv: string[] = process.argv.slice(2)): Promise<nu
|
|
|
1364
1373
|
? null
|
|
1365
1374
|
: checkForUpdate(
|
|
1366
1375
|
normalizeBaseUrl(process.env.SWARMCLAW_URL || process.env.SWARMCLAW_BASE_URL || 'http://localhost:3456'),
|
|
1367
|
-
(
|
|
1376
|
+
resolveDefaultAccessKey(),
|
|
1368
1377
|
)
|
|
1369
1378
|
|
|
1370
1379
|
await program.parseAsync(['node', 'swarmclaw', ...argv])
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const assert = require('node:assert/strict')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const os = require('node:os')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
|
|
10
|
+
function loadServerCmdForHome(homeDir) {
|
|
11
|
+
const modPath = require.resolve('../../bin/server-cmd.js')
|
|
12
|
+
const previousHome = process.env.SWARMCLAW_HOME
|
|
13
|
+
process.env.SWARMCLAW_HOME = homeDir
|
|
14
|
+
delete require.cache[modPath]
|
|
15
|
+
const loaded = require(modPath)
|
|
16
|
+
if (previousHome === undefined) delete process.env.SWARMCLAW_HOME
|
|
17
|
+
else process.env.SWARMCLAW_HOME = previousHome
|
|
18
|
+
delete require.cache[modPath]
|
|
19
|
+
return loaded
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('needsBuild returns true when no build marker exists', () => {
|
|
23
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
24
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
25
|
+
assert.equal(serverCmd.needsBuild(false), true)
|
|
26
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('needsBuild returns false when build marker version matches and standalone server exists', () => {
|
|
30
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
31
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
32
|
+
|
|
33
|
+
fs.mkdirSync(path.join(homeDir, '.next', 'standalone'), { recursive: true })
|
|
34
|
+
fs.writeFileSync(path.join(homeDir, '.next', 'standalone', 'server.js'), 'console.log("ok")\n', 'utf8')
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(homeDir, '.built'),
|
|
37
|
+
JSON.stringify({ builtAt: new Date().toISOString(), version: serverCmd.getVersion() }),
|
|
38
|
+
'utf8',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert.equal(serverCmd.needsBuild(false), false)
|
|
42
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('needsBuild returns true when build marker version is stale', () => {
|
|
46
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
47
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(path.join(homeDir, '.next', 'standalone'), { recursive: true })
|
|
50
|
+
fs.writeFileSync(path.join(homeDir, '.next', 'standalone', 'server.js'), 'console.log("ok")\n', 'utf8')
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(homeDir, '.built'),
|
|
53
|
+
JSON.stringify({ builtAt: new Date().toISOString(), version: '0.0.0-test' }),
|
|
54
|
+
'utf8',
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert.equal(serverCmd.needsBuild(false), true)
|
|
58
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
59
|
+
})
|
package/src/cli/spec.js
CHANGED
|
@@ -127,6 +127,16 @@ const COMMAND_GROUPS = {
|
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
},
|
|
130
|
+
'external-agents': {
|
|
131
|
+
description: 'Manage external agent runtimes',
|
|
132
|
+
commands: {
|
|
133
|
+
list: { description: 'List external agent runtimes', method: 'GET', path: '/external-agents' },
|
|
134
|
+
create: { description: 'Register an external agent runtime', method: 'POST', path: '/external-agents' },
|
|
135
|
+
update: { description: 'Update an external agent runtime', method: 'PUT', path: '/external-agents/:id', params: ['id'] },
|
|
136
|
+
delete: { description: 'Delete an external agent runtime', method: 'DELETE', path: '/external-agents/:id', params: ['id'] },
|
|
137
|
+
heartbeat: { description: 'Record an external agent heartbeat', method: 'POST', path: '/external-agents/:id/heartbeat', params: ['id'] },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
130
140
|
uploads: {
|
|
131
141
|
description: 'Manage uploaded artifacts',
|
|
132
142
|
commands: {
|
|
@@ -143,6 +153,16 @@ const COMMAND_GROUPS = {
|
|
|
143
153
|
open: { description: 'Open a local file path via host default app/browser', method: 'POST', path: '/files/open' },
|
|
144
154
|
},
|
|
145
155
|
},
|
|
156
|
+
gateways: {
|
|
157
|
+
description: 'Manage named OpenClaw gateway profiles',
|
|
158
|
+
commands: {
|
|
159
|
+
list: { description: 'List configured gateway profiles', method: 'GET', path: '/gateways' },
|
|
160
|
+
create: { description: 'Create a gateway profile', method: 'POST', path: '/gateways' },
|
|
161
|
+
update: { description: 'Update a gateway profile', method: 'PUT', path: '/gateways/:id', params: ['id'] },
|
|
162
|
+
delete: { description: 'Delete a gateway profile', method: 'DELETE', path: '/gateways/:id', params: ['id'] },
|
|
163
|
+
health: { description: 'Run a gateway health check', method: 'GET', path: '/gateways/:id/health', params: ['id'] },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
146
166
|
logs: {
|
|
147
167
|
description: 'Application logs',
|
|
148
168
|
commands: {
|
|
@@ -291,8 +311,6 @@ const COMMAND_GROUPS = {
|
|
|
291
311
|
'messages-delete': { description: 'Delete a message from a chat', method: 'DELETE', path: '/chats/:id/messages', params: ['id'] },
|
|
292
312
|
fork: { description: 'Fork chat from a specific message index', method: 'POST', path: '/chats/:id/fork', params: ['id'] },
|
|
293
313
|
'edit-resend': { description: 'Edit and resend from a specific message index', method: 'POST', path: '/chats/:id/edit-resend', params: ['id'] },
|
|
294
|
-
'main-loop': { description: 'Get main mission loop state for a chat', method: 'GET', path: '/chats/:id/main-loop', params: ['id'] },
|
|
295
|
-
'main-loop-action': { description: 'Control main mission loop (pause/resume/set_goal/set_mode/clear_events/nudge)', method: 'POST', path: '/chats/:id/main-loop', params: ['id'] },
|
|
296
314
|
chat: { description: 'Send chat message (SSE stream)', method: 'POST', path: '/chats/:id/chat', params: ['id'], stream: true, waitable: true },
|
|
297
315
|
stop: { description: 'Cancel active/running chat work', method: 'POST', path: '/chats/:id/stop', params: ['id'] },
|
|
298
316
|
clear: { description: 'Clear chat history', method: 'POST', path: '/chats/:id/clear', params: ['id'] },
|
|
@@ -37,6 +37,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
37
37
|
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
38
38
|
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
39
39
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
40
|
+
const sendMessage = useChatStore((s) => s.sendMessage)
|
|
40
41
|
const togglePinAgent = useAppStore((s) => s.togglePinAgent)
|
|
41
42
|
const [running, setRunning] = useState(false)
|
|
42
43
|
const [dialogOpen, setDialogOpen] = useState(false)
|
|
@@ -61,6 +62,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
61
62
|
budget: typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0 ? agent.dailyBudget : null,
|
|
62
63
|
},
|
|
63
64
|
].filter((entry) => entry.budget !== null)
|
|
65
|
+
const canDelegateToAgents = agent.platformAssignScope === 'all'
|
|
64
66
|
useWs(`heartbeat:agent:${agent.id}`, () => {
|
|
65
67
|
setHeartbeatPulse(true)
|
|
66
68
|
setTimeout(() => setHeartbeatPulse(false), 1500)
|
|
@@ -78,19 +80,20 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
const handleConfirmRun = async () => {
|
|
81
|
-
|
|
83
|
+
const task = taskInput.trim()
|
|
84
|
+
if (!task) return
|
|
82
85
|
setDialogOpen(false)
|
|
83
86
|
setRunning(true)
|
|
84
87
|
try {
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const session = await api<{ id: string }>('POST', `/agents/${agent.id}/thread`, { user: 'default' })
|
|
89
|
+
if (!session?.id) throw new Error('Agent thread not available')
|
|
90
|
+
await loadSessions()
|
|
91
|
+
setMessages([])
|
|
92
|
+
setCurrentSession(session.id)
|
|
93
|
+
setActiveView('agents')
|
|
94
|
+
await sendMessage(task)
|
|
92
95
|
} catch (err) {
|
|
93
|
-
console.error('
|
|
96
|
+
console.error('Agent task run failed:', err)
|
|
94
97
|
}
|
|
95
98
|
setRunning(false)
|
|
96
99
|
}
|
|
@@ -199,7 +202,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
199
202
|
default
|
|
200
203
|
</span>
|
|
201
204
|
)}
|
|
202
|
-
{
|
|
205
|
+
{canDelegateToAgents && (
|
|
203
206
|
<button
|
|
204
207
|
onClick={handleRunClick}
|
|
205
208
|
disabled={running}
|
|
@@ -210,7 +213,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
210
213
|
{running ? '...' : 'Run'}
|
|
211
214
|
</button>
|
|
212
215
|
)}
|
|
213
|
-
{
|
|
216
|
+
{canDelegateToAgents && (
|
|
214
217
|
<span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-amber-400/80 bg-amber-400/[0.08] px-2 py-0.5 rounded-[6px] flex items-center gap-1">
|
|
215
218
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
|
|
216
219
|
delegates
|
|
@@ -299,7 +302,7 @@ export function AgentCard({ agent, isDefault, isRunning, isOnline, isSelected, o
|
|
|
299
302
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
300
303
|
<DialogContent className="sm:max-w-[420px]">
|
|
301
304
|
<DialogHeader>
|
|
302
|
-
<DialogTitle>Run
|
|
305
|
+
<DialogTitle>Run Agent</DialogTitle>
|
|
303
306
|
</DialogHeader>
|
|
304
307
|
<div className="py-3">
|
|
305
308
|
<label className="block text-[12px] font-600 text-text-3 mb-2">Task for {agent.name}</label>
|
|
@@ -135,6 +135,17 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
135
135
|
})
|
|
136
136
|
}, [sortedAgents, chatFilter, sessions, runningAgentIds, streamingSessionId, chatroomActiveAgentIds])
|
|
137
137
|
|
|
138
|
+
const defaultAgent = useMemo(() => {
|
|
139
|
+
const id = appSettings.defaultAgentId
|
|
140
|
+
return id ? agents[id] || null : null
|
|
141
|
+
}, [appSettings.defaultAgentId, agents])
|
|
142
|
+
|
|
143
|
+
const defaultAgentVisible = !!defaultAgent && filteredAgents.some((agent) => agent.id === defaultAgent.id)
|
|
144
|
+
const listAgents = useMemo(
|
|
145
|
+
() => (defaultAgentVisible ? filteredAgents.filter((agent) => agent.id !== defaultAgent?.id) : filteredAgents),
|
|
146
|
+
[defaultAgent?.id, defaultAgentVisible, filteredAgents],
|
|
147
|
+
)
|
|
148
|
+
|
|
138
149
|
// FLIP: animate row position changes
|
|
139
150
|
useLayoutEffect(() => {
|
|
140
151
|
const prevTop = previousTopRef.current
|
|
@@ -258,7 +269,91 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
258
269
|
</div>
|
|
259
270
|
)}
|
|
260
271
|
<div className="flex flex-col gap-0.5 px-2 pb-4">
|
|
261
|
-
{
|
|
272
|
+
{defaultAgentVisible && defaultAgent && (() => {
|
|
273
|
+
const threadSession = defaultAgent.threadSessionId ? sessions[defaultAgent.threadSessionId] as Session | undefined : undefined
|
|
274
|
+
const lastMsg = threadSession?.messages?.at(-1)
|
|
275
|
+
const heartbeatOn = defaultAgent.heartbeatEnabled === true && (defaultAgent.plugins?.length ?? 0) > 0
|
|
276
|
+
const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
|
|
277
|
+
const isWorking = runningAgentIds.has(defaultAgent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(defaultAgent.id)
|
|
278
|
+
const isTyping = streamingSessionId === defaultAgent.threadSessionId
|
|
279
|
+
const preview = lastMsg?.text?.slice(0, 100)?.replace(/\n/g, ' ') || 'Your primary shortcut chat.'
|
|
280
|
+
const isActive = currentAgentId === defaultAgent.id
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="mb-2 px-2">
|
|
284
|
+
<div className="px-2 pb-1 text-[10px] font-700 uppercase tracking-[0.12em] text-accent-bright/65">
|
|
285
|
+
Default Agent
|
|
286
|
+
</div>
|
|
287
|
+
<div
|
|
288
|
+
className={`group/row relative w-full text-left py-3.5 px-4 rounded-[14px] cursor-pointer transition-all duration-150 border
|
|
289
|
+
${isActive
|
|
290
|
+
? 'bg-accent-soft border-accent-bright/25'
|
|
291
|
+
: 'bg-accent-soft/40 border-accent-bright/15 hover:bg-accent-soft/55'}`}
|
|
292
|
+
onClick={() => bulkMode ? toggleSelected(defaultAgent.id) : handleSelect(defaultAgent)}
|
|
293
|
+
>
|
|
294
|
+
<div className="flex items-center gap-3">
|
|
295
|
+
{bulkMode && (
|
|
296
|
+
<div className={`w-5 h-5 rounded-[6px] border-2 flex items-center justify-center shrink-0 transition-colors
|
|
297
|
+
${selectedIds.has(defaultAgent.id) ? 'bg-accent-bright border-accent-bright' : 'border-white/20 bg-transparent'}`}>
|
|
298
|
+
{selectedIds.has(defaultAgent.id) && (
|
|
299
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
300
|
+
<polyline points="20 6 9 17 4 12" />
|
|
301
|
+
</svg>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
<div className="relative shrink-0">
|
|
306
|
+
<AgentAvatar seed={defaultAgent.avatarSeed || null} avatarUrl={defaultAgent.avatarUrl} name={defaultAgent.name} size={38} />
|
|
307
|
+
<div className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-bg ${
|
|
308
|
+
isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/30'
|
|
309
|
+
}`} />
|
|
310
|
+
</div>
|
|
311
|
+
<div className="flex-1 min-w-0">
|
|
312
|
+
<div className="flex items-center gap-2">
|
|
313
|
+
<span className="font-display text-[14px] font-700 truncate text-text tracking-[-0.01em]">
|
|
314
|
+
{defaultAgent.name}
|
|
315
|
+
</span>
|
|
316
|
+
<span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/12 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em]">
|
|
317
|
+
Shortcut
|
|
318
|
+
</span>
|
|
319
|
+
</div>
|
|
320
|
+
{isTyping ? (
|
|
321
|
+
<div className="text-[12px] text-accent-bright/80 mt-1 flex items-center gap-1.5">
|
|
322
|
+
<span className="flex gap-0.5">
|
|
323
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
324
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
325
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
326
|
+
</span>
|
|
327
|
+
Typing...
|
|
328
|
+
</div>
|
|
329
|
+
) : (
|
|
330
|
+
<div className="text-[12px] text-text-3/70 mt-1 truncate">
|
|
331
|
+
{preview}
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
<button
|
|
336
|
+
onClick={async (e) => {
|
|
337
|
+
e.stopPropagation()
|
|
338
|
+
await updateSettings({ defaultAgentId: null })
|
|
339
|
+
toast.success('Default agent cleared')
|
|
340
|
+
}}
|
|
341
|
+
aria-label="Remove as default agent"
|
|
342
|
+
title="Default agent — click to clear"
|
|
343
|
+
className="shrink-0 p-1 rounded-[6px] transition-all bg-transparent border-none cursor-pointer hover:bg-white/[0.06] text-accent-bright"
|
|
344
|
+
style={{ fontFamily: 'inherit' }}
|
|
345
|
+
>
|
|
346
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
347
|
+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
348
|
+
<path d="M9 22V12h6v10" fill="rgba(0,0,0,0.3)" stroke="none" />
|
|
349
|
+
</svg>
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
)
|
|
355
|
+
})()}
|
|
356
|
+
{listAgents.map((agent) => {
|
|
262
357
|
const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
|
|
263
358
|
const lastMsg = threadSession?.messages?.at(-1)
|
|
264
359
|
const isActive = currentAgentId === agent.id
|
|
@@ -300,6 +395,11 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
300
395
|
<span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
|
|
301
396
|
{agent.name}
|
|
302
397
|
</span>
|
|
398
|
+
{appSettings.defaultAgentId === agent.id && (
|
|
399
|
+
<span className="px-1.5 py-0.5 rounded-[6px] bg-accent-bright/10 text-accent-bright text-[9px] font-700 uppercase tracking-[0.08em] shrink-0">
|
|
400
|
+
Default
|
|
401
|
+
</span>
|
|
402
|
+
)}
|
|
303
403
|
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
304
404
|
{(threadSession?.model || agent.model)
|
|
305
405
|
? (threadSession?.model || agent.model)!.split('/').pop()?.split(':')[0]
|