clawport-ui 0.8.2 → 0.8.3
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 +2 -2
- package/app/agents/[id]/page.tsx +16 -0
- package/app/api/chat/[id]/route.ts +1 -1
- package/app/api/kanban/chat/[id]/route.ts +1 -1
- package/components/AgentNode.tsx +18 -0
- package/components/chat/ConversationView.tsx +1 -1
- package/lib/agents-registry.ts +43 -3
- package/lib/agents.json +20 -0
- package/lib/agents.test.ts +54 -1
- package/lib/conversations.test.ts +1 -0
- package/lib/slash-commands.test.ts +1 -0
- package/lib/teams.test.ts +1 -0
- package/lib/types.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/clawport-ui)
|
|
10
10
|
[](LICENSE)
|
|
11
|
-
[](#testing)
|
|
12
12
|
|
|
13
13
|
[Website](https://clawport.dev) | [Setup Guide](SETUP.md) | [API Docs](docs/API.md) | [npm](https://www.npmjs.com/package/clawport-ui)
|
|
14
14
|
|
|
@@ -171,7 +171,7 @@ clawport help # Show usage
|
|
|
171
171
|
## Testing
|
|
172
172
|
|
|
173
173
|
```bash
|
|
174
|
-
npm test #
|
|
174
|
+
npm test # 771 tests across 31 suites (Vitest)
|
|
175
175
|
npx tsc --noEmit # Type-check (zero errors)
|
|
176
176
|
npx next build # Production build
|
|
177
177
|
```
|
package/app/agents/[id]/page.tsx
CHANGED
|
@@ -490,6 +490,22 @@ export default function AgentDetailPage({
|
|
|
490
490
|
>
|
|
491
491
|
{agent.title}
|
|
492
492
|
</p>
|
|
493
|
+
{agent.model && (
|
|
494
|
+
<span
|
|
495
|
+
style={{
|
|
496
|
+
display: "inline-block",
|
|
497
|
+
marginTop: "var(--space-1)",
|
|
498
|
+
fontSize: "var(--text-caption2)",
|
|
499
|
+
fontFamily: "var(--font-mono)",
|
|
500
|
+
color: "var(--text-tertiary)",
|
|
501
|
+
background: "var(--fill-secondary)",
|
|
502
|
+
padding: "1px 8px",
|
|
503
|
+
borderRadius: 6,
|
|
504
|
+
}}
|
|
505
|
+
>
|
|
506
|
+
{agent.model.split("/").pop()}
|
|
507
|
+
</span>
|
|
508
|
+
)}
|
|
493
509
|
{/* Color swatch */}
|
|
494
510
|
<div
|
|
495
511
|
style={{
|
|
@@ -93,7 +93,7 @@ export async function POST(
|
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
95
|
const stream = await openai.chat.completions.create({
|
|
96
|
-
model: 'claude-sonnet-4-6',
|
|
96
|
+
model: agent.model || 'claude-sonnet-4-6',
|
|
97
97
|
stream: true,
|
|
98
98
|
messages: [
|
|
99
99
|
{ role: 'system' as const, content: systemPrompt },
|
|
@@ -90,7 +90,7 @@ Help the user with this ticket. Stay in character as ${agent.name}, ${agent.titl
|
|
|
90
90
|
|
|
91
91
|
try {
|
|
92
92
|
const stream = await openai.chat.completions.create({
|
|
93
|
-
model: 'claude-sonnet-4-6',
|
|
93
|
+
model: agent.model || 'claude-sonnet-4-6',
|
|
94
94
|
stream: true,
|
|
95
95
|
messages: [
|
|
96
96
|
{ role: 'system' as const, content: systemPrompt },
|
package/components/AgentNode.tsx
CHANGED
|
@@ -129,6 +129,24 @@ export function AgentNode({ data, selected }: NodeProps) {
|
|
|
129
129
|
{reportCount} reports
|
|
130
130
|
</span>
|
|
131
131
|
)}
|
|
132
|
+
{agent.model && (
|
|
133
|
+
<span
|
|
134
|
+
style={{
|
|
135
|
+
fontSize: "var(--text-caption2)",
|
|
136
|
+
fontWeight: "var(--weight-medium)",
|
|
137
|
+
color: "var(--text-tertiary)",
|
|
138
|
+
background: "var(--fill-tertiary)",
|
|
139
|
+
padding: "1px 7px",
|
|
140
|
+
borderRadius: 10,
|
|
141
|
+
overflow: "hidden",
|
|
142
|
+
textOverflow: "ellipsis",
|
|
143
|
+
whiteSpace: "nowrap",
|
|
144
|
+
maxWidth: 120,
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{agent.model.split("/").pop()}
|
|
148
|
+
</span>
|
|
149
|
+
)}
|
|
132
150
|
{hasCrons && (
|
|
133
151
|
<span
|
|
134
152
|
style={{
|
|
@@ -725,7 +725,7 @@ export function ConversationView({ agent, conversation, onUpdate, onBack }: Conv
|
|
|
725
725
|
textOverflow: 'ellipsis',
|
|
726
726
|
whiteSpace: 'nowrap',
|
|
727
727
|
}}>
|
|
728
|
-
{agent.title}{messages.length > 1 && ' · Synced'}
|
|
728
|
+
{agent.title}{agent.model && ` · ${agent.model.split('/').pop()}`}{messages.length > 1 && ' · Synced'}
|
|
729
729
|
</div>
|
|
730
730
|
</div>
|
|
731
731
|
</div>
|
package/lib/agents-registry.ts
CHANGED
|
@@ -208,6 +208,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
208
208
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
209
209
|
emoji: rootEmoji,
|
|
210
210
|
tools: ['read', 'write', 'exec', 'message'],
|
|
211
|
+
model: null,
|
|
211
212
|
memoryPath: null,
|
|
212
213
|
description: 'Top-level orchestrator.',
|
|
213
214
|
})
|
|
@@ -252,6 +253,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
252
253
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
253
254
|
emoji: subName.charAt(0).toUpperCase(),
|
|
254
255
|
tools: ['read', 'write'],
|
|
256
|
+
model: null,
|
|
255
257
|
memoryPath: null,
|
|
256
258
|
description: `${subName} agent.`,
|
|
257
259
|
})
|
|
@@ -275,6 +277,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
275
277
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
276
278
|
emoji: subName.charAt(0).toUpperCase(),
|
|
277
279
|
tools: ['read', 'write'],
|
|
280
|
+
model: null,
|
|
278
281
|
memoryPath: null,
|
|
279
282
|
description: `${subName} agent.`,
|
|
280
283
|
})
|
|
@@ -343,6 +346,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
343
346
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
344
347
|
emoji: subName.charAt(0).toUpperCase(),
|
|
345
348
|
tools: ['read', 'write'],
|
|
349
|
+
model: null,
|
|
346
350
|
memoryPath: null,
|
|
347
351
|
description: `${subName} agent.`,
|
|
348
352
|
})
|
|
@@ -369,6 +373,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
369
373
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
370
374
|
emoji: subName.charAt(0).toUpperCase(),
|
|
371
375
|
tools: ['read', 'write'],
|
|
376
|
+
model: null,
|
|
372
377
|
memoryPath: null,
|
|
373
378
|
description: `${subName} agent.`,
|
|
374
379
|
})
|
|
@@ -385,6 +390,7 @@ function discoverAgents(workspacePath: string): AgentEntry[] | null {
|
|
|
385
390
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
386
391
|
emoji: name.charAt(0).toUpperCase(),
|
|
387
392
|
tools: ['read', 'write'],
|
|
393
|
+
model: null,
|
|
388
394
|
memoryPath: null,
|
|
389
395
|
description: `${name} agent.`,
|
|
390
396
|
})
|
|
@@ -408,6 +414,7 @@ interface CliAgentEntry {
|
|
|
408
414
|
id: string
|
|
409
415
|
identityName?: string
|
|
410
416
|
identityEmoji?: string
|
|
417
|
+
model?: string
|
|
411
418
|
workspace?: string
|
|
412
419
|
isDefault?: boolean
|
|
413
420
|
}
|
|
@@ -457,6 +464,12 @@ function mergeExtraWorkspaces(
|
|
|
457
464
|
// Try discovering agents from this workspace's filesystem
|
|
458
465
|
const discovered = discoverAgents(ws)
|
|
459
466
|
if (discovered) {
|
|
467
|
+
// Enrich with model from this CLI agent
|
|
468
|
+
if (cli.model) {
|
|
469
|
+
for (const d of discovered) {
|
|
470
|
+
if (!d.model) d.model = cli.model
|
|
471
|
+
}
|
|
472
|
+
}
|
|
460
473
|
for (const agent of discovered) {
|
|
461
474
|
if (existingIds.has(agent.id)) continue
|
|
462
475
|
// Agents from other workspaces are top-level peers (no cross-workspace hierarchy)
|
|
@@ -482,6 +495,7 @@ function mergeExtraWorkspaces(
|
|
|
482
495
|
color: DISCOVER_COLORS[colorIndex++ % DISCOVER_COLORS.length],
|
|
483
496
|
emoji: cli.identityEmoji || name.charAt(0).toUpperCase(),
|
|
484
497
|
tools: ['read', 'write'],
|
|
498
|
+
model: cli.model || null,
|
|
485
499
|
memoryPath: null,
|
|
486
500
|
description: `${name} agent.`,
|
|
487
501
|
})
|
|
@@ -492,6 +506,29 @@ function mergeExtraWorkspaces(
|
|
|
492
506
|
return [...existing, ...added]
|
|
493
507
|
}
|
|
494
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Enrich filesystem-discovered agents with model data from CLI output.
|
|
511
|
+
*
|
|
512
|
+
* All agents in a workspace share the CLI agent's configured model.
|
|
513
|
+
* CLI agents are matched to discovered agents by workspace path.
|
|
514
|
+
*/
|
|
515
|
+
function enrichModelsFromCli(
|
|
516
|
+
agents: AgentEntry[],
|
|
517
|
+
cliAgents: CliAgentEntry[],
|
|
518
|
+
primaryWorkspace: string,
|
|
519
|
+
): void {
|
|
520
|
+
// Find CLI agent(s) for the primary workspace — take the default or first match
|
|
521
|
+
const primaryCli = cliAgents.find(c => c.workspace === primaryWorkspace && c.isDefault)
|
|
522
|
+
|| cliAgents.find(c => c.workspace === primaryWorkspace)
|
|
523
|
+
if (primaryCli?.model) {
|
|
524
|
+
for (const agent of agents) {
|
|
525
|
+
if (!agent.model) {
|
|
526
|
+
agent.model = primaryCli.model
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
495
532
|
/**
|
|
496
533
|
* Load the agent registry.
|
|
497
534
|
*
|
|
@@ -521,11 +558,14 @@ export function loadRegistry(): AgentEntry[] {
|
|
|
521
558
|
// 2. Auto-discover from primary workspace filesystem
|
|
522
559
|
const discovered = discoverAgents(workspacePath)
|
|
523
560
|
|
|
524
|
-
// 2b.
|
|
561
|
+
// 2b. Enrich with CLI model data + merge other workspaces
|
|
525
562
|
if (discovered && openclawBin) {
|
|
526
563
|
const cliAgents = listCliAgents(openclawBin)
|
|
527
|
-
if (cliAgents
|
|
528
|
-
|
|
564
|
+
if (cliAgents) {
|
|
565
|
+
enrichModelsFromCli(discovered, cliAgents, workspacePath)
|
|
566
|
+
if (cliAgents.length > 1) {
|
|
567
|
+
return mergeExtraWorkspaces(discovered, cliAgents, workspacePath)
|
|
568
|
+
}
|
|
529
569
|
}
|
|
530
570
|
return discovered
|
|
531
571
|
}
|
package/lib/agents.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"color": "#f5c518",
|
|
11
11
|
"emoji": "\ud83e\udd16",
|
|
12
12
|
"tools": ["exec", "read", "write", "edit", "web_search", "tts", "message", "sessions_spawn", "memory_search"],
|
|
13
|
+
"model": null,
|
|
13
14
|
"memoryPath": null,
|
|
14
15
|
"description": "Top-level orchestrator. Manages the team, holds memory, delivers briefings."
|
|
15
16
|
},
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"color": "#a855f7",
|
|
25
26
|
"emoji": "\u265f\ufe0f",
|
|
26
27
|
"tools": ["web_search", "web_fetch", "read", "write", "sessions_spawn"],
|
|
28
|
+
"model": null,
|
|
27
29
|
"memoryPath": null,
|
|
28
30
|
"description": "CSO. Manages validation team. Decides what gets built and what gets killed."
|
|
29
31
|
},
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
"color": "#3b82f6",
|
|
39
41
|
"emoji": "\ud83e\udd85",
|
|
40
42
|
"tools": ["web_search", "web_fetch", "read", "write", "message"],
|
|
43
|
+
"model": null,
|
|
41
44
|
"memoryPath": null,
|
|
42
45
|
"description": "Field operator. Competitive intel, opportunity scouting, lead signals."
|
|
43
46
|
},
|
|
@@ -52,6 +55,7 @@
|
|
|
52
55
|
"color": "#06b6d4",
|
|
53
56
|
"emoji": "\ud83d\udd0d",
|
|
54
57
|
"tools": ["web_search", "web_fetch", "read", "write"],
|
|
58
|
+
"model": null,
|
|
55
59
|
"memoryPath": null,
|
|
56
60
|
"description": "Market research. TAM, competitors, pricing benchmarks. Returns Market Briefs."
|
|
57
61
|
},
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
"color": "#06b6d4",
|
|
67
71
|
"emoji": "\u2705",
|
|
68
72
|
"tools": ["web_search", "web_fetch", "read", "write"],
|
|
73
|
+
"model": null,
|
|
69
74
|
"memoryPath": null,
|
|
70
75
|
"description": "Designs minimum viable tests. Writes outreach copy. Calls BUILD/KILL/PIVOT."
|
|
71
76
|
},
|
|
@@ -80,6 +85,7 @@
|
|
|
80
85
|
"color": "#22c55e",
|
|
81
86
|
"emoji": "\ud83d\udd26",
|
|
82
87
|
"tools": ["web_search", "web_fetch", "read", "write", "exec"],
|
|
88
|
+
"model": null,
|
|
83
89
|
"memoryPath": null,
|
|
84
90
|
"description": "SEO Team Director. Runs SCOUT\u2192ANALYST\u2192STRATEGIST\u2192WRITER pipeline."
|
|
85
91
|
},
|
|
@@ -94,6 +100,7 @@
|
|
|
94
100
|
"color": "#86efac",
|
|
95
101
|
"emoji": "\ud83d\uddfa\ufe0f",
|
|
96
102
|
"tools": ["web_search", "web_fetch", "read"],
|
|
103
|
+
"model": null,
|
|
97
104
|
"memoryPath": null,
|
|
98
105
|
"description": "Scouts trending topics, pulls RSS feeds, identifies content opportunities."
|
|
99
106
|
},
|
|
@@ -108,6 +115,7 @@
|
|
|
108
115
|
"color": "#86efac",
|
|
109
116
|
"emoji": "\ud83d\udcca",
|
|
110
117
|
"tools": ["web_search", "web_fetch", "read", "write"],
|
|
118
|
+
"model": null,
|
|
111
119
|
"memoryPath": null,
|
|
112
120
|
"description": "Keyword research, GSC data analysis, competitive gap identification."
|
|
113
121
|
},
|
|
@@ -122,6 +130,7 @@
|
|
|
122
130
|
"color": "#86efac",
|
|
123
131
|
"emoji": "\ud83c\udfaf",
|
|
124
132
|
"tools": ["read", "write"],
|
|
133
|
+
"model": null,
|
|
125
134
|
"memoryPath": null,
|
|
126
135
|
"description": "Topic angle selection using SAGE and ECHO briefs."
|
|
127
136
|
},
|
|
@@ -136,6 +145,7 @@
|
|
|
136
145
|
"color": "#86efac",
|
|
137
146
|
"emoji": "\u270d\ufe0f",
|
|
138
147
|
"tools": ["read", "write"],
|
|
148
|
+
"model": null,
|
|
139
149
|
"memoryPath": null,
|
|
140
150
|
"description": "1500-2000 word posts in the operator's voice."
|
|
141
151
|
},
|
|
@@ -150,6 +160,7 @@
|
|
|
150
160
|
"color": "#86efac",
|
|
151
161
|
"emoji": "\ud83d\udee1\ufe0f",
|
|
152
162
|
"tools": ["read", "write"],
|
|
163
|
+
"model": null,
|
|
153
164
|
"memoryPath": null,
|
|
154
165
|
"description": "Pre-ship quality gate. 6-item checklist before publishing."
|
|
155
166
|
},
|
|
@@ -164,6 +175,7 @@
|
|
|
164
175
|
"color": "#f97316",
|
|
165
176
|
"emoji": "\ud83d\udce3",
|
|
166
177
|
"tools": ["web_search", "web_fetch", "read", "write", "message", "exec"],
|
|
178
|
+
"model": null,
|
|
167
179
|
"memoryPath": null,
|
|
168
180
|
"description": "LinkedIn content pipeline. Reads Pulse feed, picks angles, briefs QUILL."
|
|
169
181
|
},
|
|
@@ -178,6 +190,7 @@
|
|
|
178
190
|
"color": "#fdba74",
|
|
179
191
|
"emoji": "\ud83d\udd8a\ufe0f",
|
|
180
192
|
"tools": ["read", "write"],
|
|
193
|
+
"model": null,
|
|
181
194
|
"memoryPath": null,
|
|
182
195
|
"description": "Writes LinkedIn posts in the operator's voice."
|
|
183
196
|
},
|
|
@@ -192,6 +205,7 @@
|
|
|
192
205
|
"color": "#fdba74",
|
|
193
206
|
"emoji": "\ud83e\udded",
|
|
194
207
|
"tools": ["web_search", "read", "write"],
|
|
208
|
+
"model": null,
|
|
195
209
|
"memoryPath": null,
|
|
196
210
|
"description": "Weekly LinkedIn strategy and content calendar."
|
|
197
211
|
},
|
|
@@ -206,6 +220,7 @@
|
|
|
206
220
|
"color": "#eab308",
|
|
207
221
|
"emoji": "\ud83c\udf0a",
|
|
208
222
|
"tools": ["web_search", "web_fetch", "read", "write", "message"],
|
|
223
|
+
"model": null,
|
|
209
224
|
"memoryPath": null,
|
|
210
225
|
"description": "Hype radar. Monitors trending signals. Feeds hot topics to LUMEN."
|
|
211
226
|
},
|
|
@@ -220,6 +235,7 @@
|
|
|
220
235
|
"color": "#14b8a6",
|
|
221
236
|
"emoji": "\ud83d\udce1",
|
|
222
237
|
"tools": ["web_fetch", "read", "write"],
|
|
238
|
+
"model": null,
|
|
223
239
|
"memoryPath": null,
|
|
224
240
|
"description": "Scans ICP subreddits weekly. Extracts verbatim customer language."
|
|
225
241
|
},
|
|
@@ -234,6 +250,7 @@
|
|
|
234
250
|
"color": "#14b8a6",
|
|
235
251
|
"emoji": "\ud83e\uddd9",
|
|
236
252
|
"tools": ["read"],
|
|
253
|
+
"model": null,
|
|
237
254
|
"memoryPath": null,
|
|
238
255
|
"description": "Deep ICP and market knowledge. Injected into STRATEGIST and WRITER."
|
|
239
256
|
},
|
|
@@ -248,6 +265,7 @@
|
|
|
248
265
|
"color": "#60a5fa",
|
|
249
266
|
"emoji": "\u2708\ufe0f",
|
|
250
267
|
"tools": ["web_fetch", "message"],
|
|
268
|
+
"model": null,
|
|
251
269
|
"memoryPath": null,
|
|
252
270
|
"description": "Monitors MSP to Tokyo flights. Alerts on deals under $1400."
|
|
253
271
|
},
|
|
@@ -262,6 +280,7 @@
|
|
|
262
280
|
"color": "#f59e0b",
|
|
263
281
|
"emoji": "\u26a1",
|
|
264
282
|
"tools": ["web_fetch", "web_search", "message"],
|
|
283
|
+
"model": null,
|
|
265
284
|
"memoryPath": null,
|
|
266
285
|
"description": "Finds cool OpenClaw builds. Reports every other day."
|
|
267
286
|
},
|
|
@@ -276,6 +295,7 @@
|
|
|
276
295
|
"color": "#94a3b8",
|
|
277
296
|
"emoji": "\ud83d\udcda",
|
|
278
297
|
"tools": ["read", "write", "exec"],
|
|
298
|
+
"model": null,
|
|
279
299
|
"memoryPath": null,
|
|
280
300
|
"description": "Weekly memory compression. Silent worker."
|
|
281
301
|
}
|
package/lib/agents.test.ts
CHANGED
|
@@ -18,6 +18,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
18
18
|
color: '#f5c518',
|
|
19
19
|
emoji: 'R',
|
|
20
20
|
tools: ['exec', 'read', 'write'],
|
|
21
|
+
model: null,
|
|
21
22
|
memoryPath: null,
|
|
22
23
|
description: 'Top-level orchestrator.',
|
|
23
24
|
},
|
|
@@ -32,6 +33,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
32
33
|
color: '#a855f7',
|
|
33
34
|
emoji: 'P',
|
|
34
35
|
tools: ['web_search', 'read'],
|
|
36
|
+
model: null,
|
|
35
37
|
memoryPath: null,
|
|
36
38
|
description: 'CSO. Decides what gets built.',
|
|
37
39
|
},
|
|
@@ -46,6 +48,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
46
48
|
color: '#3b82f6',
|
|
47
49
|
emoji: 'E',
|
|
48
50
|
tools: ['web_search'],
|
|
51
|
+
model: null,
|
|
49
52
|
memoryPath: null,
|
|
50
53
|
description: 'Field operator.',
|
|
51
54
|
},
|
|
@@ -60,6 +63,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
60
63
|
color: '#22c55e',
|
|
61
64
|
emoji: 'L',
|
|
62
65
|
tools: ['web_search', 'read'],
|
|
66
|
+
model: null,
|
|
63
67
|
memoryPath: null,
|
|
64
68
|
description: 'SEO Team Director.',
|
|
65
69
|
},
|
|
@@ -74,6 +78,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
74
78
|
color: '#86efac',
|
|
75
79
|
emoji: 'S',
|
|
76
80
|
tools: ['web_search'],
|
|
81
|
+
model: null,
|
|
77
82
|
memoryPath: null,
|
|
78
83
|
description: 'Scouts trending topics.',
|
|
79
84
|
},
|
|
@@ -88,6 +93,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
88
93
|
color: '#eab308',
|
|
89
94
|
emoji: 'W',
|
|
90
95
|
tools: ['web_search'],
|
|
96
|
+
model: null,
|
|
91
97
|
memoryPath: null,
|
|
92
98
|
description: 'Hype radar.',
|
|
93
99
|
},
|
|
@@ -102,6 +108,7 @@ const { mockReadFileSync, mockExistsSync, mockReaddirSync, mockExecSync, bundled
|
|
|
102
108
|
color: '#60a5fa',
|
|
103
109
|
emoji: 'A',
|
|
104
110
|
tools: ['web_fetch'],
|
|
111
|
+
model: null,
|
|
105
112
|
memoryPath: null,
|
|
106
113
|
description: 'Monitors flights.',
|
|
107
114
|
},
|
|
@@ -1115,6 +1122,7 @@ describe('CLI agent discovery (multi-workspace)', () => {
|
|
|
1115
1122
|
id: string
|
|
1116
1123
|
identityName?: string
|
|
1117
1124
|
identityEmoji?: string
|
|
1125
|
+
model?: string
|
|
1118
1126
|
workspace?: string
|
|
1119
1127
|
isDefault?: boolean
|
|
1120
1128
|
}>) {
|
|
@@ -1125,7 +1133,7 @@ describe('CLI agent discovery (multi-workspace)', () => {
|
|
|
1125
1133
|
identitySource: 'identity',
|
|
1126
1134
|
workspace: e.workspace ?? '/tmp/ws',
|
|
1127
1135
|
agentDir: `/home/.openclaw/agents/${e.id}/agent`,
|
|
1128
|
-
model: 'anthropic/claude-sonnet-4-6',
|
|
1136
|
+
model: e.model ?? 'anthropic/claude-sonnet-4-6',
|
|
1129
1137
|
bindings: 0,
|
|
1130
1138
|
isDefault: e.isDefault ?? false,
|
|
1131
1139
|
routes: ['default (no explicit rules)'],
|
|
@@ -1251,6 +1259,51 @@ describe('CLI agent discovery (multi-workspace)', () => {
|
|
|
1251
1259
|
expect(emptyBot!.tools).toEqual(['read', 'write'])
|
|
1252
1260
|
})
|
|
1253
1261
|
|
|
1262
|
+
it('flows CLI model through to minimal entry', async () => {
|
|
1263
|
+
setupPrimaryWorkspace()
|
|
1264
|
+
|
|
1265
|
+
const origExists = mockExistsSync.getMockImplementation()!
|
|
1266
|
+
mockExistsSync.mockImplementation((p: string) => {
|
|
1267
|
+
if (p.startsWith('/tmp/ws-model')) return false
|
|
1268
|
+
return origExists(p)
|
|
1269
|
+
})
|
|
1270
|
+
|
|
1271
|
+
mockExecSync.mockReturnValue(cliOutput([
|
|
1272
|
+
{ id: 'main', workspace: '/tmp/ws', isDefault: true },
|
|
1273
|
+
{ id: 'opus-bot', identityName: 'OpusBot', model: 'anthropic/claude-opus-4-6', workspace: '/tmp/ws-model' },
|
|
1274
|
+
]))
|
|
1275
|
+
|
|
1276
|
+
const agents = await getAgents()
|
|
1277
|
+
const opusBot = agents.find(a => a.id === 'opus-bot')
|
|
1278
|
+
expect(opusBot).toBeDefined()
|
|
1279
|
+
expect(opusBot!.model).toBe('anthropic/claude-opus-4-6')
|
|
1280
|
+
})
|
|
1281
|
+
|
|
1282
|
+
it('enriches primary workspace agents with CLI model', async () => {
|
|
1283
|
+
setupPrimaryWorkspace()
|
|
1284
|
+
mockExecSync.mockReturnValue(cliOutput([
|
|
1285
|
+
{ id: 'main', identityName: 'Jarvis', model: 'anthropic/claude-opus-4-6', workspace: '/tmp/ws', isDefault: true },
|
|
1286
|
+
]))
|
|
1287
|
+
|
|
1288
|
+
const agents = await getAgents()
|
|
1289
|
+
const jarvis = agents.find(a => a.id === 'jarvis')
|
|
1290
|
+
expect(jarvis).toBeDefined()
|
|
1291
|
+
expect(jarvis!.model).toBe('anthropic/claude-opus-4-6')
|
|
1292
|
+
// Sub-agents in the same workspace also get the model
|
|
1293
|
+
const echo = agents.find(a => a.id === 'echo')
|
|
1294
|
+
expect(echo!.model).toBe('anthropic/claude-opus-4-6')
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
it('agents stay model: null when CLI is unavailable', async () => {
|
|
1298
|
+
setupPrimaryWorkspace()
|
|
1299
|
+
mockExecSync.mockImplementation(() => { throw new Error('not found') })
|
|
1300
|
+
|
|
1301
|
+
const agents = await getAgents()
|
|
1302
|
+
const jarvis = agents.find(a => a.id === 'jarvis')
|
|
1303
|
+
expect(jarvis).toBeDefined()
|
|
1304
|
+
expect(jarvis!.model).toBeNull()
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1254
1307
|
it('does not duplicate agents already found in primary workspace', async () => {
|
|
1255
1308
|
setupPrimaryWorkspace()
|
|
1256
1309
|
|
package/lib/teams.test.ts
CHANGED
package/lib/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface Agent {
|
|
|
11
11
|
voiceId: string | null // ElevenLabs voice ID
|
|
12
12
|
color: string // hex color for node
|
|
13
13
|
emoji: string // emoji identifier
|
|
14
|
+
model: string | null // LLM model identifier (e.g. "anthropic/claude-sonnet-4-6")
|
|
14
15
|
tools: string[] // list of tools this agent has access to
|
|
15
16
|
crons: CronJob[] // associated cron jobs
|
|
16
17
|
memoryPath: string | null
|