@synergenius/flow-weaver-pack-weaver 0.9.134 → 0.9.136

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.
Files changed (49) hide show
  1. package/dist/bot/ai-client.d.ts +9 -0
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +59 -0
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/behavior-defaults.d.ts +4 -0
  6. package/dist/bot/behavior-defaults.d.ts.map +1 -1
  7. package/dist/bot/behavior-defaults.js +17 -0
  8. package/dist/bot/behavior-defaults.js.map +1 -1
  9. package/dist/bot/capability-registry.d.ts +20 -0
  10. package/dist/bot/capability-registry.d.ts.map +1 -0
  11. package/dist/bot/capability-registry.js +205 -0
  12. package/dist/bot/capability-registry.js.map +1 -0
  13. package/dist/bot/capability-types.d.ts +23 -0
  14. package/dist/bot/capability-types.d.ts.map +1 -0
  15. package/dist/bot/capability-types.js +9 -0
  16. package/dist/bot/capability-types.js.map +1 -0
  17. package/dist/bot/estimate-tokens.d.ts +12 -0
  18. package/dist/bot/estimate-tokens.d.ts.map +1 -0
  19. package/dist/bot/estimate-tokens.js +18 -0
  20. package/dist/bot/estimate-tokens.js.map +1 -0
  21. package/dist/bot/profile-types.d.ts +4 -0
  22. package/dist/bot/profile-types.d.ts.map +1 -1
  23. package/dist/bot/system-prompt.d.ts +11 -0
  24. package/dist/bot/system-prompt.d.ts.map +1 -1
  25. package/dist/bot/system-prompt.js +31 -0
  26. package/dist/bot/system-prompt.js.map +1 -1
  27. package/dist/bot/types.d.ts +1 -0
  28. package/dist/bot/types.d.ts.map +1 -1
  29. package/dist/node-types/plan-task.d.ts.map +1 -1
  30. package/dist/node-types/plan-task.js +49 -45
  31. package/dist/node-types/plan-task.js.map +1 -1
  32. package/dist/ui/capability-editor.js +657 -0
  33. package/dist/ui/profile-card.js +25 -0
  34. package/dist/ui/profile-editor.js +615 -316
  35. package/dist/ui/swarm-dashboard.js +643 -319
  36. package/flowweaver.manifest.json +1 -1
  37. package/package.json +2 -2
  38. package/src/bot/ai-client.ts +75 -0
  39. package/src/bot/behavior-defaults.ts +20 -0
  40. package/src/bot/capability-registry.ts +230 -0
  41. package/src/bot/capability-types.ts +23 -0
  42. package/src/bot/estimate-tokens.ts +16 -0
  43. package/src/bot/profile-types.ts +4 -0
  44. package/src/bot/system-prompt.ts +33 -0
  45. package/src/bot/types.ts +1 -0
  46. package/src/node-types/plan-task.ts +51 -44
  47. package/src/ui/capability-editor.tsx +420 -0
  48. package/src/ui/profile-card.tsx +20 -0
  49. package/src/ui/profile-editor.tsx +100 -6
@@ -0,0 +1,420 @@
1
+ /**
2
+ * CapabilityEditor — CRUD screen for managing knowledge capabilities.
3
+ *
4
+ * Two-screen pattern:
5
+ * List view → shows all capabilities (built-in read-only, custom editable)
6
+ * Detail view → full form for creating or editing a capability
7
+ *
8
+ * Pattern: CommonJS require for platform deps, React.createElement throughout.
9
+ */
10
+ const React = require('react');
11
+ const { useState, useCallback } = React;
12
+ const {
13
+ Flex, Typography, Input, Button, IconButton, Icon, Field, Chip, Checkbox, Table, CodeBlock,
14
+ ScrollArea, toast, usePackWorkspace,
15
+ } = require('@fw/plugin-ui-kit');
16
+
17
+ import { BUILT_IN_CAPABILITIES } from '../bot/capability-registry';
18
+ import { PLAN_OPERATIONS } from '../bot/operations';
19
+ import { estimateTokens } from '../bot/estimate-tokens';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Types
23
+ // ---------------------------------------------------------------------------
24
+
25
+ interface CapabilityDef {
26
+ name: string;
27
+ description: string;
28
+ prompt: string;
29
+ tools?: string[];
30
+ builtIn?: boolean;
31
+ }
32
+
33
+ interface CapabilityEditorProps {
34
+ customCapabilities?: CapabilityDef[];
35
+ onSave?: (capabilities: CapabilityDef[]) => void;
36
+ onClose?: () => void;
37
+ }
38
+
39
+ // Available tool operations for the tool picker
40
+ const AVAILABLE_TOOLS = [...PLAN_OPERATIONS] as string[];
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Detail screen — create or edit a single capability
44
+ // ---------------------------------------------------------------------------
45
+
46
+ function CapabilityDetail({
47
+ cap,
48
+ mode,
49
+ allNames,
50
+ onBack,
51
+ onSubmit,
52
+ }: {
53
+ cap?: CapabilityDef;
54
+ mode: 'create' | 'edit' | 'view';
55
+ allNames: string[];
56
+ onBack: () => void;
57
+ onSubmit: (cap: CapabilityDef) => void;
58
+ }) {
59
+ const [name, setName] = useState(cap?.name ?? '');
60
+ const [description, setDescription] = useState(cap?.description ?? '');
61
+ const [prompt, setPrompt] = useState(cap?.prompt ?? '');
62
+ const [tools, setTools] = useState(cap?.tools ?? ([] as string[]));
63
+
64
+ const readOnly = mode === 'view';
65
+ const isCreate = mode === 'create';
66
+
67
+ const handleToggleTool = useCallback((tool: string) => {
68
+ setTools((prev: string[]) =>
69
+ prev.includes(tool) ? prev.filter((t: string) => t !== tool) : [...prev, tool],
70
+ );
71
+ }, []);
72
+
73
+ const handleSubmit = useCallback(() => {
74
+ const trimName = name.trim().toLowerCase().replace(/\s+/g, '-');
75
+ if (!trimName || !description.trim()) {
76
+ toast('Name and description are required', { type: 'error' });
77
+ return;
78
+ }
79
+ if (isCreate && allNames.includes(trimName)) {
80
+ toast(`Capability "${trimName}" already exists`, { type: 'error' });
81
+ return;
82
+ }
83
+ onSubmit({
84
+ name: isCreate ? trimName : (cap?.name ?? trimName),
85
+ description: description.trim(),
86
+ prompt: prompt.trim() || `## ${description.trim()}`,
87
+ tools: tools.length > 0 ? tools : undefined,
88
+ });
89
+ }, [name, description, prompt, tools, isCreate, allNames, cap, onSubmit]);
90
+
91
+ const title = isCreate ? 'New Capability' : readOnly ? cap?.name ?? '' : `Edit: ${cap?.name ?? ''}`;
92
+
93
+ return React.createElement(Flex, {
94
+ variant: 'column-stretch-start-nowrap-0',
95
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
96
+ },
97
+ // Header
98
+ React.createElement(Flex, {
99
+ variant: 'row-center-space-between-nowrap-8',
100
+ style: { padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid var(--color-border-default)' },
101
+ },
102
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
103
+ React.createElement(IconButton, {
104
+ icon: 'back', size: 'xs', variant: 'clear', onClick: onBack, title: 'Back to list',
105
+ }),
106
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-high' }, title),
107
+ readOnly && React.createElement(Chip, {
108
+ label: 'built-in', size: 'small', color: 'color-status-info',
109
+ }),
110
+ ),
111
+ ),
112
+
113
+ // Scrollable form
114
+ React.createElement(ScrollArea, { style: { flex: 1, minHeight: 0 } },
115
+ React.createElement(Flex, {
116
+ variant: 'column-stretch-start-nowrap-10',
117
+ style: { padding: '12px 16px' },
118
+ },
119
+ // Name
120
+ React.createElement(Field, { label: 'Name' },
121
+ readOnly
122
+ ? React.createElement(Typography, {
123
+ variant: 'smallCaption-thick', color: 'color-text-high',
124
+ style: { padding: '4px 0' },
125
+ }, cap?.name)
126
+ : React.createElement(Input, {
127
+ type: 'text', size: 'small',
128
+ placeholder: 'e.g. api-design',
129
+ value: name, onChange: (v: string) => setName(v),
130
+ disabled: !isCreate,
131
+ defaultBoxStyle: { flex: 1, minWidth: 0 }, inputBoxStyle: { maxWidth: 'none' },
132
+ }),
133
+ ),
134
+
135
+ // Description
136
+ React.createElement(Field, { label: 'Description' },
137
+ readOnly
138
+ ? React.createElement(Typography, {
139
+ variant: 'smallCaption-regular', color: 'color-text-medium',
140
+ style: { padding: '4px 0' },
141
+ }, cap?.description)
142
+ : React.createElement(Input, {
143
+ type: 'text', size: 'small',
144
+ placeholder: 'What this capability provides',
145
+ value: description, onChange: (v: string) => setDescription(v),
146
+ defaultBoxStyle: { flex: 1, minWidth: 0 }, inputBoxStyle: { maxWidth: 'none' },
147
+ }),
148
+ ),
149
+
150
+ // Prompt
151
+ React.createElement(Field, { label: 'Prompt', align: 'start' },
152
+ readOnly
153
+ ? React.createElement(Typography, {
154
+ variant: 'smallCaption-regular', color: 'color-text-medium',
155
+ style: { whiteSpace: 'pre-wrap', lineHeight: 1.5 },
156
+ }, cap?.prompt)
157
+ : React.createElement(Input, {
158
+ type: 'text', size: 'small', multiline: true,
159
+ placeholder: 'Knowledge content (markdown) injected into the system prompt',
160
+ value: prompt, onChange: (v: string) => setPrompt(v),
161
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
162
+ inputBoxStyle: { maxWidth: 'none', minHeight: 160 },
163
+ }),
164
+ ),
165
+
166
+ // Tools
167
+ React.createElement(Field, { label: 'Tools', align: 'start' },
168
+ React.createElement(Table, {
169
+ size: 'compact',
170
+ showHeader: false,
171
+ data: AVAILABLE_TOOLS.map((t: string) => ({
172
+ tool: t,
173
+ active: (readOnly ? (cap?.tools ?? []) : tools).includes(t),
174
+ })),
175
+ getRowKey: (row: { tool: string }) => row.tool,
176
+ columns: [
177
+ {
178
+ key: 'check', header: '', width: '28px',
179
+ render: (_v: unknown, row: { tool: string; active: boolean }) =>
180
+ React.createElement('div', { style: { height: 20, display: 'flex', alignItems: 'center' } },
181
+ React.createElement(Checkbox, {
182
+ checked: row.active,
183
+ disabled: readOnly,
184
+ onChange: readOnly ? undefined : () => handleToggleTool(row.tool),
185
+ size: 'sm',
186
+ }),
187
+ ),
188
+ },
189
+ {
190
+ key: 'tool', header: 'Tool',
191
+ render: (_v: unknown, row: { tool: string; active: boolean }) =>
192
+ React.createElement('div', { style: { height: 20, display: 'flex', alignItems: 'center' } },
193
+ React.createElement(Typography, {
194
+ variant: 'smallCaption-regular',
195
+ color: row.active ? 'color-text-high' : 'color-text-subtle',
196
+ }, row.tool),
197
+ ),
198
+ },
199
+ ],
200
+ }),
201
+ ),
202
+
203
+ // Token estimate
204
+ React.createElement(Field, { label: 'Est. tokens' },
205
+ React.createElement(Typography, {
206
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
207
+ }, `~${estimateTokens(readOnly ? (cap?.prompt ?? '') : prompt)} tokens (approx.)`),
208
+ ),
209
+
210
+ // Submit button (not for view mode)
211
+ !readOnly && React.createElement(Flex, {
212
+ variant: 'row-center-end-nowrap-8',
213
+ style: { paddingTop: 4 },
214
+ },
215
+ React.createElement(Button, {
216
+ size: 'xs', variant: 'clear', color: 'secondary', onClick: onBack,
217
+ }, 'Cancel'),
218
+ React.createElement(Button, {
219
+ size: 'xs', variant: 'fill', color: 'primary',
220
+ onClick: handleSubmit,
221
+ disabled: readOnly || (!isCreate && !description.trim()) || (isCreate && (!name.trim() || !description.trim())),
222
+ }, isCreate ? 'Create' : 'Save'),
223
+ ),
224
+ ),
225
+ ),
226
+ );
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // List screen — shows all capabilities
231
+ // ---------------------------------------------------------------------------
232
+
233
+ function CapabilityList({
234
+ allCaps,
235
+ onOpen,
236
+ onDelete,
237
+ onCreate,
238
+ onSaveAll,
239
+ onClose,
240
+ hasCustom,
241
+ }: {
242
+ allCaps: CapabilityDef[];
243
+ onOpen: (name: string) => void;
244
+ onDelete: (name: string) => void;
245
+ onCreate: () => void;
246
+ onSaveAll?: () => void;
247
+ onClose?: () => void;
248
+ hasCustom: boolean;
249
+ }) {
250
+ return React.createElement(Flex, {
251
+ variant: 'column-stretch-start-nowrap-0',
252
+ style: { width: '100%', height: '100%', overflow: 'hidden' },
253
+ },
254
+ // Header
255
+ React.createElement(Flex, {
256
+ variant: 'row-center-space-between-nowrap-8',
257
+ style: { padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid var(--color-border-default)' },
258
+ },
259
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-8' },
260
+ onClose && React.createElement(IconButton, {
261
+ icon: 'back', size: 'xs', variant: 'clear', onClick: onClose, title: 'Back',
262
+ }),
263
+ React.createElement(Typography, { variant: 'caption-thick', color: 'color-text-high' }, 'Capabilities'),
264
+ React.createElement(Chip, {
265
+ label: `${allCaps.length}`,
266
+ size: 'small',
267
+ color: 'color-brand-main',
268
+ }),
269
+ ),
270
+ React.createElement(Flex, { variant: 'row-center-end-nowrap-6' },
271
+ onSaveAll && hasCustom && React.createElement(Button, {
272
+ size: 'xs', variant: 'outlined', color: 'primary', onClick: onSaveAll,
273
+ }, 'Save'),
274
+ React.createElement(Button, {
275
+ size: 'xs', variant: 'fill', color: 'primary', onClick: onCreate,
276
+ }, 'New'),
277
+ ),
278
+ ),
279
+
280
+ // Scrollable list
281
+ React.createElement(ScrollArea, { style: { flex: 1, minHeight: 0 } },
282
+ React.createElement(Flex, {
283
+ variant: 'column-stretch-start-nowrap-2',
284
+ style: { padding: '8px 16px' },
285
+ },
286
+ ...allCaps.map((cap: CapabilityDef) =>
287
+ React.createElement(Flex, {
288
+ key: cap.name,
289
+ variant: 'row-center-space-between-nowrap-8',
290
+ style: {
291
+ padding: '7px 10px',
292
+ borderRadius: 6,
293
+ cursor: 'pointer',
294
+ border: '1px solid var(--color-border-default)',
295
+ transition: 'background-color 0.1s',
296
+ },
297
+ onClick: () => onOpen(cap.name),
298
+ onMouseEnter: (e: { currentTarget: HTMLElement }) => {
299
+ e.currentTarget.style.backgroundColor = 'var(--color-surface-hover)';
300
+ },
301
+ onMouseLeave: (e: { currentTarget: HTMLElement }) => {
302
+ e.currentTarget.style.backgroundColor = 'transparent';
303
+ },
304
+ },
305
+ // Left: icon + name + description
306
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-8', style: { flex: 1, minWidth: 0 } },
307
+ React.createElement(Icon, {
308
+ name: cap.builtIn ? 'lock' : 'edit',
309
+ size: 13,
310
+ color: cap.builtIn ? 'color-text-subtle' : 'color-brand-main',
311
+ }),
312
+ React.createElement(Typography, {
313
+ variant: 'smallCaption-thick',
314
+ color: 'color-text-high',
315
+ style: { minWidth: 80, flexShrink: 0 },
316
+ }, cap.name),
317
+ React.createElement(Typography, {
318
+ variant: 'smallCaption-regular',
319
+ color: 'color-text-subtle',
320
+ style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
321
+ }, cap.description),
322
+ ),
323
+ // Right: chips + actions
324
+ React.createElement(Flex, { variant: 'row-center-end-nowrap-4', style: { flexShrink: 0 } },
325
+ cap.tools && cap.tools.length > 0 && React.createElement(Chip, {
326
+ label: `${cap.tools.length} tools`,
327
+ size: 'small',
328
+ color: 'color-status-positive',
329
+ }),
330
+ !cap.builtIn && React.createElement(IconButton, {
331
+ icon: 'outlinedDelete',
332
+ size: 'xs',
333
+ variant: 'clear',
334
+ color: 'danger',
335
+ onClick: (e: Event) => { e.stopPropagation(); onDelete(cap.name); },
336
+ title: 'Delete capability',
337
+ }),
338
+ React.createElement(Icon, {
339
+ name: 'chevronRight', size: 12, color: 'color-text-subtle',
340
+ }),
341
+ ),
342
+ ),
343
+ ),
344
+ ),
345
+ ),
346
+ );
347
+ }
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // Main component — manages navigation between list and detail
351
+ // ---------------------------------------------------------------------------
352
+
353
+ function CapabilityEditor({ customCapabilities = [], onSave, onClose }: CapabilityEditorProps) {
354
+ const [customs, setCustoms] = useState(customCapabilities as CapabilityDef[]);
355
+ // screen: 'list' | { mode: 'create' } | { mode: 'view' | 'edit', name: string }
356
+ const [screen, setScreen] = useState('list' as 'list' | { mode: 'create' | 'view' | 'edit'; name?: string });
357
+
358
+ const allCaps: CapabilityDef[] = [
359
+ ...BUILT_IN_CAPABILITIES.map((c: { name: string; description: string; prompt: string; tools?: string[] }) => ({ ...c, builtIn: true })),
360
+ ...customs.map((c: CapabilityDef) => ({ ...c, builtIn: false })),
361
+ ];
362
+
363
+ const allNames = allCaps.map((c: CapabilityDef) => c.name);
364
+
365
+ const handleOpen = useCallback((name: string) => {
366
+ const cap = allCaps.find((c: CapabilityDef) => c.name === name);
367
+ if (!cap) return;
368
+ setScreen({ mode: cap.builtIn ? 'view' : 'edit', name });
369
+ }, [allCaps]);
370
+
371
+ const handleDelete = useCallback((name: string) => {
372
+ setCustoms((prev: CapabilityDef[]) => prev.filter((c: CapabilityDef) => c.name !== name));
373
+ toast(`Capability "${name}" deleted`, { type: 'success' });
374
+ }, []);
375
+
376
+ const handleSubmit = useCallback((cap: CapabilityDef) => {
377
+ if (typeof screen === 'object' && screen.mode === 'edit') {
378
+ setCustoms((prev: CapabilityDef[]) =>
379
+ prev.map((c: CapabilityDef) => c.name === cap.name ? cap : c),
380
+ );
381
+ toast(`Capability "${cap.name}" updated`, { type: 'success' });
382
+ } else {
383
+ setCustoms((prev: CapabilityDef[]) => [...prev, cap]);
384
+ toast(`Capability "${cap.name}" created`, { type: 'success' });
385
+ }
386
+ setScreen('list');
387
+ }, [screen]);
388
+
389
+ const handleSaveAll = useCallback(() => {
390
+ onSave?.(customs);
391
+ toast('Capabilities saved', { type: 'success' });
392
+ }, [customs, onSave]);
393
+
394
+ // Detail screen
395
+ if (typeof screen === 'object') {
396
+ const cap = screen.name ? allCaps.find((c: CapabilityDef) => c.name === screen.name) : undefined;
397
+ return React.createElement(CapabilityDetail, {
398
+ cap,
399
+ mode: screen.mode,
400
+ allNames,
401
+ onBack: () => setScreen('list'),
402
+ onSubmit: handleSubmit,
403
+ });
404
+ }
405
+
406
+ // List screen
407
+ return React.createElement(CapabilityList, {
408
+ allCaps,
409
+ onOpen: handleOpen,
410
+ onDelete: handleDelete,
411
+ onCreate: () => setScreen({ mode: 'create' }),
412
+ onSaveAll: onSave ? handleSaveAll : undefined,
413
+ onClose,
414
+ hasCustom: customs.length > 0,
415
+ });
416
+ }
417
+
418
+ export { CapabilityEditor };
419
+ export default CapabilityEditor;
420
+ module.exports = CapabilityEditor;
@@ -25,6 +25,8 @@ interface PhaseBehavior {
25
25
  }
26
26
 
27
27
  interface ProfileBehavior {
28
+ capabilities?: string[];
29
+ budget?: number;
28
30
  phases: Record<string, PhaseBehavior>;
29
31
  escalation: { maxAttempts: number; onExhausted: string };
30
32
  scope?: { allowedPaths?: string[]; blockedPaths?: string[] };
@@ -159,6 +161,24 @@ function ProfileCard({ profile, activeInstances, onEdit, onDelete }: ProfileCard
159
161
  ),
160
162
  ),
161
163
 
164
+ // Knowledge capabilities (from behavior)
165
+ behavior?.capabilities && behavior.capabilities.length > 0 && React.createElement(Field, { label: 'Knowledge', labelWidth: 90, align: 'start' },
166
+ React.createElement(Flex, { variant: 'row-center-start-wrap-4' },
167
+ ...behavior.capabilities.map((capName: string) =>
168
+ React.createElement('span', { key: capName },
169
+ React.createElement(Chip, { label: capName, size: 'small', color: 'color-status-info' }),
170
+ ),
171
+ ),
172
+ ),
173
+ ),
174
+
175
+ // Budget (from behavior)
176
+ behavior?.budget && React.createElement(Field, { label: 'Budget', labelWidth: 90, align: 'center' },
177
+ React.createElement(Typography, {
178
+ variant: 'smallCaption-regular', color: 'color-text-high',
179
+ }, `$${behavior.budget.toFixed(2)}/task`),
180
+ ),
181
+
162
182
  // Strategy
163
183
  React.createElement(Field, { label: 'Strategy', labelWidth: 90, align: 'center' },
164
184
  React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
@@ -8,10 +8,12 @@ const React = require('react');
8
8
  const { useState, useEffect, useCallback } = React;
9
9
  const {
10
10
  Flex, Typography, Input, Button, IconButton, Icon, SectionTitle, Checkbox, Chip, Field, Table,
11
- IconPicker, ColorPicker, Switch, ToggleGroup, CollapsibleSection, toast, usePackWorkspace,
11
+ IconPicker, ColorPicker, Switch, ToggleGroup, CollapsibleSection, ScrollArea, toast, usePackWorkspace,
12
12
  } = require('@fw/plugin-ui-kit');
13
13
 
14
14
  import { ICON_CATALOG, BOT_COLORS } from './bot-constants';
15
+ import { BUILT_IN_CAPABILITIES } from '../bot/capability-registry';
16
+ import { STRATEGY_CAPABILITIES, STRATEGY_BUDGETS } from '../bot/behavior-defaults';
15
17
 
16
18
  // ---------------------------------------------------------------------------
17
19
  // Types
@@ -121,6 +123,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
121
123
  const [capDescription, setCapDescription] = useState('');
122
124
  const [instructions, setInstructions] = useState('');
123
125
  const [requireApproval, setRequireApproval] = useState(false);
126
+ const [knowledgeCaps, setKnowledgeCaps] = useState(() => [...STRATEGY_CAPABILITIES.balanced]);
127
+ const [budget, setBudget] = useState(STRATEGY_BUDGETS.balanced);
124
128
  const [behavior, setBehavior] = useState(defaultBehaviorForStrategy('balanced') as BehaviorState);
125
129
  const [blockedInput, setBlockedInput] = useState('');
126
130
  const [allowedInput, setAllowedInput] = useState('');
@@ -135,6 +139,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
135
139
  if (p !== 'custom') {
136
140
  setCostStrategy(p);
137
141
  setBehavior(defaultBehaviorForStrategy(p));
142
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[p]]);
143
+ setBudget(STRATEGY_BUDGETS[p]);
138
144
  }
139
145
  }, []);
140
146
 
@@ -185,6 +191,24 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
185
191
  setInstructions((prefs.instructions as string) || '');
186
192
  setRequireApproval(!!(prefs.requireApproval));
187
193
 
194
+ // Load knowledge capabilities and budget from behavior
195
+ if (prefs.behavior) {
196
+ const b = prefs.behavior as Record<string, unknown>;
197
+ if (Array.isArray(b.capabilities)) {
198
+ setKnowledgeCaps(b.capabilities as string[]);
199
+ } else {
200
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[strat]]);
201
+ }
202
+ if (typeof b.budget === 'number') {
203
+ setBudget(b.budget as number);
204
+ } else {
205
+ setBudget(STRATEGY_BUDGETS[strat]);
206
+ }
207
+ } else {
208
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[strat]]);
209
+ setBudget(STRATEGY_BUDGETS[strat]);
210
+ }
211
+
188
212
  // Load behavior config — dynamic phases from whatever the profile has
189
213
  if (prefs.behavior) {
190
214
  const b = prefs.behavior as Record<string, unknown>;
@@ -233,6 +257,17 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
233
257
  setCapabilities((prev: Array<{ name: string; description: string }>) => prev.filter((_: unknown, i: number) => i !== index));
234
258
  }, []);
235
259
 
260
+ // --- Knowledge capability toggle ---
261
+ const handleToggleKnowledgeCap = useCallback((capName: string) => {
262
+ setKnowledgeCaps((prev: string[]) => {
263
+ if (capName === 'core') return prev; // core cannot be toggled off
264
+ return prev.includes(capName)
265
+ ? prev.filter((n: string) => n !== capName)
266
+ : [...prev, capName];
267
+ });
268
+ setPreset('custom');
269
+ }, []);
270
+
236
271
  // --- Scope paths ---
237
272
  const handleAddScopePath = useCallback((type: 'blocked' | 'allowed') => {
238
273
  const input = type === 'blocked' ? blockedInput : allowedInput;
@@ -272,6 +307,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
272
307
  }
273
308
 
274
309
  const behaviorPayload = {
310
+ capabilities: knowledgeCaps,
311
+ budget,
275
312
  phases,
276
313
  escalation: { maxAttempts: behavior.maxAttempts, onExhausted: behavior.onExhausted },
277
314
  scope: (behavior.blockedPaths.length > 0 || behavior.allowedPaths.length > 0)
@@ -310,7 +347,7 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
310
347
  } catch (err: unknown) {
311
348
  toast(err instanceof Error ? err.message : `Failed to ${mode} profile`, { type: 'error' });
312
349
  }
313
- }, [mode, profileId, name, description, botId, icon, color, costStrategy, capabilities, instructions, requireApproval, behavior, callTool, onSave]);
350
+ }, [mode, profileId, name, description, botId, icon, color, costStrategy, capabilities, knowledgeCaps, budget, instructions, requireApproval, behavior, callTool, onSave]);
314
351
 
315
352
  // --- Delete ---
316
353
  const handleDelete = useCallback(async () => {
@@ -397,9 +434,12 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
397
434
  ),
398
435
 
399
436
  // ── Scrollable form body ──
437
+ React.createElement(ScrollArea, {
438
+ style: { flex: 1, minHeight: 0 },
439
+ },
400
440
  React.createElement(Flex, {
401
441
  variant: 'column-stretch-start-nowrap-10',
402
- style: { flex: 1, minHeight: 0, overflow: 'auto', padding: '12px 16px' },
442
+ style: { padding: '12px 16px' },
403
443
  },
404
444
 
405
445
  // ── Name ──
@@ -461,6 +501,58 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
461
501
  }),
462
502
  ),
463
503
 
504
+ // ── Knowledge Capabilities (checkbox grid) ──
505
+ React.createElement(Field, { label: 'Knowledge', align: 'start' },
506
+ React.createElement(Flex, { variant: 'column-stretch-start-nowrap-2' },
507
+ ...BUILT_IN_CAPABILITIES.map((cap: { name: string; description: string }) =>
508
+ React.createElement(Flex, {
509
+ key: cap.name,
510
+ variant: 'row-center-start-nowrap-8',
511
+ style: { minHeight: 28, padding: '1px 0' },
512
+ },
513
+ React.createElement(Checkbox, {
514
+ checked: knowledgeCaps.includes(cap.name),
515
+ onChange: () => handleToggleKnowledgeCap(cap.name),
516
+ disabled: cap.name === 'core' || !isCustom,
517
+ size: 'sm',
518
+ }),
519
+ React.createElement(Typography, {
520
+ variant: 'smallCaption-thick',
521
+ color: knowledgeCaps.includes(cap.name) ? 'color-text-high' : 'color-text-subtle',
522
+ style: { minWidth: 80 },
523
+ }, cap.name),
524
+ React.createElement(Typography, {
525
+ variant: 'smallCaption-regular',
526
+ color: 'color-text-subtle',
527
+ style: { flex: 1 },
528
+ }, cap.description),
529
+ ),
530
+ ),
531
+ ),
532
+ ),
533
+
534
+ // ── Budget ──
535
+ React.createElement(Field, { label: 'Budget', align: 'center' },
536
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-6' },
537
+ React.createElement(Typography, {
538
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
539
+ }, '$'),
540
+ React.createElement(Input, {
541
+ type: 'number', size: 'small',
542
+ value: String(budget),
543
+ readOnly: !isCustom,
544
+ onChange: isCustom ? (v: string) => {
545
+ const n = parseFloat(v);
546
+ if (!isNaN(n) && n >= 0) setBudget(n);
547
+ } : undefined,
548
+ defaultBoxStyle: { width: 80 },
549
+ }),
550
+ React.createElement(Typography, {
551
+ variant: 'smallCaption-regular', color: 'color-text-subtle',
552
+ }, '/ task'),
553
+ ),
554
+ ),
555
+
464
556
  // ── Phase Configuration (always visible, readOnly when preset) ──
465
557
  React.createElement(Field, { label: 'Phases', align: 'start' },
466
558
  React.createElement(Flex, {
@@ -572,21 +664,22 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
572
664
  React.createElement(Field, { label: 'Capabilities', align: 'start' },
573
665
  React.createElement(Flex, { variant: 'column-stretch-start-nowrap-6' },
574
666
  // Add row
575
- React.createElement(Flex, { variant: 'row-center-start-nowrap-4', style: { overflow: 'hidden' } },
667
+ React.createElement(Flex, { variant: 'row-center-start-nowrap-4', style: { minWidth: 0 } },
576
668
  React.createElement(Input, {
577
669
  type: 'text', size: 'small', placeholder: 'Name',
578
670
  value: capName, onChange: (v: string) => setCapName(v),
579
- defaultBoxStyle: { flex: 1, minWidth: 0 }, inputBoxStyle: { maxWidth: 'none' },
671
+ defaultBoxStyle: { flex: '0 1 120px', minWidth: 0 }, inputBoxStyle: { maxWidth: 'none', minWidth: 0 },
580
672
  }),
581
673
  React.createElement(Input, {
582
674
  type: 'text', size: 'small', placeholder: 'Description',
583
675
  value: capDescription, onChange: (v: string) => setCapDescription(v),
584
676
  onEnter: handleAddCapability,
585
- defaultBoxStyle: { flex: 2, minWidth: 0 }, inputBoxStyle: { maxWidth: 'none' },
677
+ defaultBoxStyle: { flex: '1 1 0', minWidth: 0 }, inputBoxStyle: { maxWidth: 'none', minWidth: 0 },
586
678
  }),
587
679
  React.createElement(IconButton, {
588
680
  icon: 'add', size: 'sm', variant: 'outlined', color: 'primary',
589
681
  onClick: handleAddCapability, disabled: !capName.trim() || !capDescription.trim(),
682
+ style: { flexShrink: 0 },
590
683
  }),
591
684
  ),
592
685
  // Table
@@ -646,6 +739,7 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }: Pr
646
739
  }, mode === 'create' ? 'Create' : 'Save'),
647
740
  ),
648
741
  ),
742
+ ),
649
743
  );
650
744
  }
651
745