@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
@@ -207,6 +207,205 @@ var BOT_COLORS = [
207
207
  { label: "Info", token: "color-status-info" }
208
208
  ];
209
209
 
210
+ // src/bot/operations.ts
211
+ var OP_WRITE_FILE = "write_file";
212
+ var OP_READ_FILE = "read_file";
213
+ var OP_PATCH_FILE = "patch_file";
214
+ var OP_LIST_FILES = "list_files";
215
+ var OP_RUN_SHELL = "run_shell";
216
+ var OP_VALIDATE = "validate";
217
+ var OP_TSC_CHECK = "tsc_check";
218
+ var OP_RUN_TESTS = "run_tests";
219
+ var OP_TASK_CREATE = "task_create";
220
+
221
+ // src/bot/capability-registry.ts
222
+ var CAP_CORE = {
223
+ name: "core",
224
+ description: "Bot identity, structured plan output format, and safety rules. Always loaded.",
225
+ prompt: `You are Weaver, an expert AI companion for Flow Weaver workflows.
226
+
227
+ ## Plan Format
228
+ Your plans MUST be structured JSON with concrete steps.
229
+ Each step has: operation (tool name), description (what it does), args (complete arguments).
230
+ Do NOT describe what you would do \u2014 actually do it by calling tools.
231
+
232
+ ## Safety Rules
233
+ - Writes that shrink a file by >50% or write empty content are automatically BLOCKED.
234
+ - Blocked shell commands: rm -rf, git push, npm publish, sudo, curl|sh.
235
+ - Always validate BEFORE and AFTER patching.
236
+ - Always read a file before patching it (you need exact strings for find/replace).
237
+ - Use patch_file for modifications, write_file only for new files.
238
+ - Be concise \u2014 let tool results speak.`
239
+ };
240
+ var CAP_FILE_OPS = {
241
+ name: "file-ops",
242
+ description: "File read/write/patch operations and best practices for file manipulation.",
243
+ tools: [OP_READ_FILE, OP_WRITE_FILE, OP_PATCH_FILE, OP_LIST_FILES],
244
+ prompt: `## File Operations
245
+ - read_file: Read a file and return its content. args: { file }
246
+ - write_file: Write a file. args: { file, content }. Content must be the COMPLETE file.
247
+ - patch_file: Surgical find-and-replace edits. args: { file, patches: [{ find: "old text", replace: "new text" }] }. PREFERRED for modifying existing files.
248
+ - list_files: List files in a directory. args: { directory, pattern? } (pattern is regex)
249
+
250
+ ## Best Practices
251
+ PREFER patch_file over write_file for modifying existing files (surgical edits, no truncation risk).
252
+ Use read_file to understand a file before modifying it.
253
+ Use list_files to discover project structure.
254
+ Writes that shrink a file by >50% or write empty content are automatically BLOCKED.`
255
+ };
256
+ var CAP_SHELL = {
257
+ name: "shell",
258
+ description: "Shell command execution for running tests, builds, and inspecting output.",
259
+ tools: [OP_RUN_SHELL, OP_VALIDATE, OP_TSC_CHECK, OP_RUN_TESTS],
260
+ prompt: `## Shell Commands
261
+ - run_shell: Execute a shell command and return output. args: { command }
262
+ Use for: npx vitest, git status, grep, find, etc.
263
+ Examples: { "command": "npx vitest run --reporter verbose" }, { "command": "npx flow-weaver validate src/workflow.ts --json" }
264
+ Blocked: rm -rf, git push, npm publish, sudo, curl|sh (safety policy).
265
+ Use run_shell for running tests (npx vitest), validation (flow-weaver validate), and inspecting output.`
266
+ };
267
+ var CAP_TASK_MGMT = {
268
+ name: "task-mgmt",
269
+ description: "Create and manage swarm subtasks for parallel execution.",
270
+ tools: [OP_TASK_CREATE],
271
+ prompt: `## Task Management
272
+ - task_create: Create swarm subtasks for parallel execution. args: { title, description, complexity, subtasks[] }
273
+ - task_list, task_get, task_update: Query and update existing tasks
274
+
275
+ Use task_create to decompose complex work into smaller, independent subtasks that other bots can execute in parallel.`
276
+ };
277
+ var CAP_FW_GRAMMAR = {
278
+ name: "fw-grammar",
279
+ description: "Flow Weaver annotation syntax, node types, workflows, patterns, and data flow.",
280
+ prompt: `## Core Mental Model
281
+
282
+ The code IS the workflow. Flow Weaver workflows are plain TypeScript files with JSDoc annotations above functions. The compiler reads annotations and generates deterministic execution code between @flow-weaver-body-start/end markers. Compiled code is standalone with no runtime dependency on flow-weaver.
283
+
284
+ Three block types:
285
+ - @flowWeaver nodeType: A reusable function (node) with typed inputs/outputs
286
+ - @flowWeaver workflow: A DAG orchestration that wires node instances together
287
+ - @flowWeaver pattern: A reusable fragment with boundary ports (IN/OUT instead of Start/Exit)
288
+
289
+ ## Node Execution Model
290
+
291
+ Expression nodes (@expression):
292
+ - No execute/onSuccess/onFailure params. Just inputs and return value.
293
+ - throw = failure path, return = success path
294
+ - Synchronous. Use execSync for shell commands.
295
+ - Preferred for deterministic operations.
296
+
297
+ Standard nodes:
298
+ - execute: boolean param gates execution
299
+ - Return { onSuccess: boolean, onFailure: boolean, ...outputs }
300
+ - Can be async for I/O operations
301
+
302
+ Async agent nodes:
303
+ - Use (globalThis as any).__fw_agent_channel__ to pause workflow
304
+ - Call channel.request({ agentId, context, prompt }) which returns a Promise
305
+ - Workflow suspends until agent responds
306
+ - NOT @expression (must be async)
307
+
308
+ Pass-through pattern:
309
+ - FW auto-connects ports by matching names on adjacent nodes
310
+ - To forward data through intermediate nodes, declare it as both @input and @output with the same name
311
+ - For non-adjacent wiring, use @connect sourceNode.port -> targetNode.port
312
+
313
+ Data flow:
314
+ - @path A -> B -> C: Linear execution path (sugar for multiple @connect)
315
+ - @autoConnect: Auto-wire all nodes in declaration order
316
+ - @connect: Explicit port-to-port wiring
317
+ - Merge strategies for fan-in: FIRST, LAST, COLLECT, MERGE, CONCAT`
318
+ };
319
+ var CAP_FW_VALIDATE = {
320
+ name: "fw-validate",
321
+ description: "Flow Weaver validation error codes and common fix patterns.",
322
+ prompt: `## Validation Errors
323
+
324
+ When you encounter validation errors, suggest the specific fix. Common resolutions:
325
+ - UNKNOWN_NODE_TYPE: Ensure the referenced function has @flowWeaver nodeType annotation
326
+ - MISSING_REQUIRED_INPUT: Add a @connect from a source port or make the input optional with [brackets]
327
+ - UNKNOWN_SOURCE_PORT / UNKNOWN_TARGET_PORT: Check port name spelling in @connect
328
+ - CYCLE_DETECTED: Break the cycle; use scoped iteration (@scope, @map) instead of circular dependencies`
329
+ };
330
+ var CAP_FW_GENESIS = {
331
+ name: "fw-genesis",
332
+ description: "Flow Weaver genesis protocol for self-evolving workflows.",
333
+ prompt: `## Genesis Protocol
334
+
335
+ Genesis is a 17-step self-evolving workflow engine:
336
+ 1. Load config 2. Observe project 3. Load task workflow 4. Diff fingerprint
337
+ 5. Check stabilize mode 6. Wait for agent 7. Propose evolution 8. Validate proposal
338
+ 9. Snapshot for rollback 10. Apply changes 11. Compile and validate
339
+ 12. Diff workflow 13. Check approval threshold 14. Wait for approval
340
+ 15. Commit or rollback 16. Update history 17. Report summary
341
+
342
+ When stabilize mode is active, only fix-up operations are allowed.`
343
+ };
344
+ var CAP_FW_CLI = {
345
+ name: "fw-cli",
346
+ description: "Flow Weaver CLI command reference for compile, validate, diagram, and other operations.",
347
+ prompt: `## CLI Commands
348
+
349
+ Note: compile, validate, modify, diff, diagram, and describe operations are available as direct plan steps (no CLI needed).`
350
+ };
351
+ var CAP_CODE_REVIEW = {
352
+ name: "code-review",
353
+ description: "Code review guidelines, quality checklist, and security review patterns.",
354
+ prompt: `## Code Review
355
+
356
+ When reviewing code, check for:
357
+ 1. Correctness: Does the code do what the task asked?
358
+ 2. Security: No hardcoded secrets, no injection vulnerabilities, no exposed APIs
359
+ 3. Style: Consistent with project conventions, proper naming, no dead code
360
+ 4. Testing: Are there tests? Do they cover edge cases?
361
+ 5. Performance: No unnecessary loops, no blocking calls in async code
362
+
363
+ Report concerns with specific file:line references and suggested fixes.`
364
+ };
365
+ var CAP_WEB = {
366
+ name: "web",
367
+ description: "Web fetch capability for fetching URLs and external resources.",
368
+ tools: ["web_fetch"],
369
+ prompt: `## Web
370
+ - web_fetch(url): Fetch a URL and return its content. Use for API docs, examples, etc.`
371
+ };
372
+ var CAP_CONTEXT = {
373
+ name: "context",
374
+ description: "Project file listings, directory structure, and workspace context.",
375
+ prompt: `## Project Context
376
+
377
+ Use list_files to understand the project structure before making changes.
378
+ The context bundle (when available) provides a snapshot of the workspace.`
379
+ };
380
+ var BUILT_IN_CAPABILITIES = [
381
+ CAP_CORE,
382
+ CAP_FILE_OPS,
383
+ CAP_SHELL,
384
+ CAP_TASK_MGMT,
385
+ CAP_FW_GRAMMAR,
386
+ CAP_FW_VALIDATE,
387
+ CAP_FW_GENESIS,
388
+ CAP_FW_CLI,
389
+ CAP_CODE_REVIEW,
390
+ CAP_WEB,
391
+ CAP_CONTEXT
392
+ ];
393
+ var capabilityMap = new Map(
394
+ BUILT_IN_CAPABILITIES.map((c) => [c.name, c])
395
+ );
396
+
397
+ // src/bot/behavior-defaults.ts
398
+ var STRATEGY_CAPABILITIES = {
399
+ frugal: ["core", "file-ops", "shell", "context"],
400
+ balanced: ["core", "file-ops", "shell", "task-mgmt", "fw-grammar", "fw-validate", "context"],
401
+ performance: ["core", "file-ops", "shell", "task-mgmt", "fw-grammar", "fw-validate", "fw-genesis", "fw-cli", "code-review", "web", "context"]
402
+ };
403
+ var STRATEGY_BUDGETS = {
404
+ frugal: 0.1,
405
+ balanced: 0.5,
406
+ performance: 1
407
+ };
408
+
210
409
  // src/ui/profile-editor.tsx
211
410
  var React = require("react");
212
411
  var { useState, useEffect, useCallback } = React;
@@ -227,6 +426,7 @@ var {
227
426
  Switch,
228
427
  ToggleGroup,
229
428
  CollapsibleSection,
429
+ ScrollArea,
230
430
  toast,
231
431
  usePackWorkspace
232
432
  } = require("@fw/plugin-ui-kit");
@@ -286,6 +486,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
286
486
  const [capDescription, setCapDescription] = useState("");
287
487
  const [instructions, setInstructions] = useState("");
288
488
  const [requireApproval, setRequireApproval] = useState(false);
489
+ const [knowledgeCaps, setKnowledgeCaps] = useState(() => [...STRATEGY_CAPABILITIES.balanced]);
490
+ const [budget, setBudget] = useState(STRATEGY_BUDGETS.balanced);
289
491
  const [behavior, setBehavior] = useState(defaultBehaviorForStrategy("balanced"));
290
492
  const [blockedInput, setBlockedInput] = useState("");
291
493
  const [allowedInput, setAllowedInput] = useState("");
@@ -297,6 +499,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
297
499
  if (p !== "custom") {
298
500
  setCostStrategy(p);
299
501
  setBehavior(defaultBehaviorForStrategy(p));
502
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[p]]);
503
+ setBudget(STRATEGY_BUDGETS[p]);
300
504
  }
301
505
  }, []);
302
506
  const updatePhase = useCallback((phase, field, value) => {
@@ -338,6 +542,22 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
338
542
  );
339
543
  setInstructions(prefs.instructions || "");
340
544
  setRequireApproval(!!prefs.requireApproval);
545
+ if (prefs.behavior) {
546
+ const b = prefs.behavior;
547
+ if (Array.isArray(b.capabilities)) {
548
+ setKnowledgeCaps(b.capabilities);
549
+ } else {
550
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[strat]]);
551
+ }
552
+ if (typeof b.budget === "number") {
553
+ setBudget(b.budget);
554
+ } else {
555
+ setBudget(STRATEGY_BUDGETS[strat]);
556
+ }
557
+ } else {
558
+ setKnowledgeCaps([...STRATEGY_CAPABILITIES[strat]]);
559
+ setBudget(STRATEGY_BUDGETS[strat]);
560
+ }
341
561
  if (prefs.behavior) {
342
562
  const b = prefs.behavior;
343
563
  const phases = b.phases;
@@ -382,6 +602,13 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
382
602
  const handleRemoveCapability = useCallback((index) => {
383
603
  setCapabilities((prev) => prev.filter((_, i) => i !== index));
384
604
  }, []);
605
+ const handleToggleKnowledgeCap = useCallback((capName2) => {
606
+ setKnowledgeCaps((prev) => {
607
+ if (capName2 === "core") return prev;
608
+ return prev.includes(capName2) ? prev.filter((n) => n !== capName2) : [...prev, capName2];
609
+ });
610
+ setPreset("custom");
611
+ }, []);
385
612
  const handleAddScopePath = useCallback((type) => {
386
613
  const input = type === "blocked" ? blockedInput : allowedInput;
387
614
  const trimmed = input.trim();
@@ -422,6 +649,8 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
422
649
  }
423
650
  }
424
651
  const behaviorPayload = {
652
+ capabilities: knowledgeCaps,
653
+ budget,
425
654
  phases,
426
655
  escalation: { maxAttempts: behavior.maxAttempts, onExhausted: behavior.onExhausted },
427
656
  scope: behavior.blockedPaths.length > 0 || behavior.allowedPaths.length > 0 ? {
@@ -467,7 +696,7 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
467
696
  } catch (err) {
468
697
  toast(err instanceof Error ? err.message : `Failed to ${mode} profile`, { type: "error" });
469
698
  }
470
- }, [mode, profileId, name, description, botId, icon, color, costStrategy, capabilities, instructions, requireApproval, behavior, callTool, onSave]);
699
+ }, [mode, profileId, name, description, botId, icon, color, costStrategy, capabilities, knowledgeCaps, budget, instructions, requireApproval, behavior, callTool, onSave]);
471
700
  const handleDelete = useCallback(async () => {
472
701
  if (!profileId) return;
473
702
  const ok = await ctx.confirm("Are you sure you want to delete this profile?", {
@@ -571,361 +800,431 @@ function ProfileEditor({ mode, profileId, bots, onSave, onCancel, onDelete }) {
571
800
  ),
572
801
  // ── Scrollable form body ──
573
802
  React.createElement(
574
- Flex,
803
+ ScrollArea,
575
804
  {
576
- variant: "column-stretch-start-nowrap-10",
577
- style: { flex: 1, minHeight: 0, overflow: "auto", padding: "12px 16px" }
805
+ style: { flex: 1, minHeight: 0 }
578
806
  },
579
- // ── Name ──
580
807
  React.createElement(
581
- Field,
582
- { label: "Name" },
583
- React.createElement(Input, {
584
- type: "text",
585
- size: "small",
586
- placeholder: "Profile name",
587
- value: name,
588
- onChange: (v) => setName(v),
589
- defaultBoxStyle: { flex: 1, minWidth: 0 },
590
- inputBoxStyle: { maxWidth: "none" }
591
- })
592
- ),
593
- // ── Description ──
594
- React.createElement(
595
- Field,
596
- { label: "Description" },
597
- React.createElement(Input, {
598
- type: "text",
599
- size: "small",
600
- placeholder: "What does this profile do?",
601
- value: description,
602
- onChange: (v) => setDescription(v),
603
- defaultBoxStyle: { flex: 1, minWidth: 0 },
604
- inputBoxStyle: { maxWidth: "none" }
605
- })
606
- ),
607
- // ── Bot ──
608
- React.createElement(
609
- Field,
610
- { label: "Bot" },
611
- React.createElement(Input, {
612
- type: "select",
613
- size: "small",
614
- options: bots.map((b) => ({ id: b.id, label: b.name, icon: b.icon || "smartToy" })),
615
- optionId: botId,
616
- onChange: (id) => setBotId(id),
617
- disabled: mode === "edit",
618
- placeholder: "Select bot",
619
- defaultBoxStyle: { flex: 1, minWidth: 0 }
620
- })
621
- ),
622
- // ── Icon ──
623
- React.createElement(
624
- Field,
625
- { label: "Icon", align: "start" },
626
- React.createElement(IconPicker, {
627
- catalog: ICON_CATALOG,
628
- value: icon,
629
- onChange: (v) => setIcon(v),
630
- accentColor: color,
631
- variant: "inline"
632
- })
633
- ),
634
- // ── Color ──
635
- React.createElement(
636
- Field,
637
- { label: "Color", align: "start" },
638
- React.createElement(ColorPicker, {
639
- colors: BOT_COLORS,
640
- value: color,
641
- onChange: (v) => setColor(v),
642
- variant: "inline"
643
- })
644
- ),
645
- // ── Preset (ToggleGroup with Custom option) ──
646
- React.createElement(
647
- Field,
648
- { label: "Preset", align: "start" },
649
- React.createElement(ToggleGroup, {
650
- options: [
651
- { label: "Fast", value: "frugal", icon: "speed", selected: preset === "frugal" },
652
- { label: "Mid", value: "balanced", icon: "balance", selected: preset === "balanced" },
653
- { label: "Smart", value: "performance", icon: "psychology", selected: preset === "performance" },
654
- { label: "Custom", value: "custom", icon: "settings", selected: preset === "custom" }
655
- ],
656
- onSelect: handlePresetChange,
657
- size: "extraSmall"
658
- })
659
- ),
660
- // ── Phase Configuration (always visible, readOnly when preset) ──
661
- React.createElement(
662
- Field,
663
- { label: "Phases", align: "start" },
808
+ Flex,
809
+ {
810
+ variant: "column-stretch-start-nowrap-10",
811
+ style: { padding: "12px 16px" }
812
+ },
813
+ // ── Name ──
664
814
  React.createElement(
665
- Flex,
666
- {
667
- variant: "column-stretch-start-nowrap-6"
668
- },
669
- ...Object.entries(behavior).filter(([k]) => !["maxAttempts", "onExhausted", "blockedPaths", "allowedPaths"].includes(k)).map(([phaseName, phase]) => {
670
- const phaseObj = phase;
671
- const isLlm = phaseObj.tier !== void 0;
672
- const label = phaseName.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
673
- return phaseRow(phaseName, label, isLlm);
815
+ Field,
816
+ { label: "Name" },
817
+ React.createElement(Input, {
818
+ type: "text",
819
+ size: "small",
820
+ placeholder: "Profile name",
821
+ value: name,
822
+ onChange: (v) => setName(v),
823
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
824
+ inputBoxStyle: { maxWidth: "none" }
674
825
  })
675
- )
676
- ),
677
- // ── Escalation (always visible, readOnly when preset) ──
678
- React.createElement(
679
- Field,
680
- { label: "Retries", align: "start" },
826
+ ),
827
+ // ── Description ──
681
828
  React.createElement(
682
- Flex,
683
- {
684
- variant: "column-stretch-start-nowrap-8"
685
- },
829
+ Field,
830
+ { label: "Description" },
831
+ React.createElement(Input, {
832
+ type: "text",
833
+ size: "small",
834
+ placeholder: "What does this profile do?",
835
+ value: description,
836
+ onChange: (v) => setDescription(v),
837
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
838
+ inputBoxStyle: { maxWidth: "none" }
839
+ })
840
+ ),
841
+ // ── Bot ──
842
+ React.createElement(
843
+ Field,
844
+ { label: "Bot" },
845
+ React.createElement(Input, {
846
+ type: "select",
847
+ size: "small",
848
+ options: bots.map((b) => ({ id: b.id, label: b.name, icon: b.icon || "smartToy" })),
849
+ optionId: botId,
850
+ onChange: (id) => setBotId(id),
851
+ disabled: mode === "edit",
852
+ placeholder: "Select bot",
853
+ defaultBoxStyle: { flex: 1, minWidth: 0 }
854
+ })
855
+ ),
856
+ // ── Icon ──
857
+ React.createElement(
858
+ Field,
859
+ { label: "Icon", align: "start" },
860
+ React.createElement(IconPicker, {
861
+ catalog: ICON_CATALOG,
862
+ value: icon,
863
+ onChange: (v) => setIcon(v),
864
+ accentColor: color,
865
+ variant: "inline"
866
+ })
867
+ ),
868
+ // ── Color ──
869
+ React.createElement(
870
+ Field,
871
+ { label: "Color", align: "start" },
872
+ React.createElement(ColorPicker, {
873
+ colors: BOT_COLORS,
874
+ value: color,
875
+ onChange: (v) => setColor(v),
876
+ variant: "inline"
877
+ })
878
+ ),
879
+ // ── Preset (ToggleGroup with Custom option) ──
880
+ React.createElement(
881
+ Field,
882
+ { label: "Preset", align: "start" },
883
+ React.createElement(ToggleGroup, {
884
+ options: [
885
+ { label: "Fast", value: "frugal", icon: "speed", selected: preset === "frugal" },
886
+ { label: "Mid", value: "balanced", icon: "balance", selected: preset === "balanced" },
887
+ { label: "Smart", value: "performance", icon: "psychology", selected: preset === "performance" },
888
+ { label: "Custom", value: "custom", icon: "settings", selected: preset === "custom" }
889
+ ],
890
+ onSelect: handlePresetChange,
891
+ size: "extraSmall"
892
+ })
893
+ ),
894
+ // ── Knowledge Capabilities (checkbox grid) ──
895
+ React.createElement(
896
+ Field,
897
+ { label: "Knowledge", align: "start" },
686
898
  React.createElement(
687
- Field,
688
- { label: "Max attempts", labelWidth: 100, align: "center" },
899
+ Flex,
900
+ { variant: "column-stretch-start-nowrap-2" },
901
+ ...BUILT_IN_CAPABILITIES.map(
902
+ (cap) => React.createElement(
903
+ Flex,
904
+ {
905
+ key: cap.name,
906
+ variant: "row-center-start-nowrap-8",
907
+ style: { minHeight: 28, padding: "1px 0" }
908
+ },
909
+ React.createElement(Checkbox, {
910
+ checked: knowledgeCaps.includes(cap.name),
911
+ onChange: () => handleToggleKnowledgeCap(cap.name),
912
+ disabled: cap.name === "core" || !isCustom,
913
+ size: "sm"
914
+ }),
915
+ React.createElement(Typography, {
916
+ variant: "smallCaption-thick",
917
+ color: knowledgeCaps.includes(cap.name) ? "color-text-high" : "color-text-subtle",
918
+ style: { minWidth: 80 }
919
+ }, cap.name),
920
+ React.createElement(Typography, {
921
+ variant: "smallCaption-regular",
922
+ color: "color-text-subtle",
923
+ style: { flex: 1 }
924
+ }, cap.description)
925
+ )
926
+ )
927
+ )
928
+ ),
929
+ // ── Budget ──
930
+ React.createElement(
931
+ Field,
932
+ { label: "Budget", align: "center" },
933
+ React.createElement(
934
+ Flex,
935
+ { variant: "row-center-start-nowrap-6" },
936
+ React.createElement(Typography, {
937
+ variant: "smallCaption-regular",
938
+ color: "color-text-subtle"
939
+ }, "$"),
689
940
  React.createElement(Input, {
690
941
  type: "number",
691
942
  size: "small",
692
- value: String(behavior.maxAttempts),
943
+ value: String(budget),
693
944
  readOnly: !isCustom,
694
- onChange: isCustom ? (v) => updateEscalation("maxAttempts", Math.max(1, parseInt(v) || 1)) : void 0,
695
- defaultBoxStyle: { width: 60 }
696
- })
697
- ),
945
+ onChange: isCustom ? (v) => {
946
+ const n = parseFloat(v);
947
+ if (!isNaN(n) && n >= 0) setBudget(n);
948
+ } : void 0,
949
+ defaultBoxStyle: { width: 80 }
950
+ }),
951
+ React.createElement(Typography, {
952
+ variant: "smallCaption-regular",
953
+ color: "color-text-subtle"
954
+ }, "/ task")
955
+ )
956
+ ),
957
+ // ── Phase Configuration (always visible, readOnly when preset) ──
958
+ React.createElement(
959
+ Field,
960
+ { label: "Phases", align: "start" },
698
961
  React.createElement(
699
- Field,
700
- { label: "On exhausted", labelWidth: 100, align: "start" },
701
- React.createElement(ToggleGroup, {
702
- options: [
703
- { label: "Block", value: "block", icon: "block", selected: behavior.onExhausted === "block" },
704
- { label: "Reassign", value: "reassign", icon: "sync", selected: behavior.onExhausted === "reassign" },
705
- { label: "Notify", value: "notify", icon: "notifications", selected: behavior.onExhausted === "notify" }
706
- ],
707
- onSelect: (v) => updateEscalation("onExhausted", v),
708
- readOnly: !isCustom,
709
- size: "extraSmall"
962
+ Flex,
963
+ {
964
+ variant: "column-stretch-start-nowrap-6"
965
+ },
966
+ ...Object.entries(behavior).filter(([k]) => !["maxAttempts", "onExhausted", "blockedPaths", "allowedPaths"].includes(k)).map(([phaseName, phase]) => {
967
+ const phaseObj = phase;
968
+ const isLlm = phaseObj.tier !== void 0;
969
+ const label = phaseName.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
970
+ return phaseRow(phaseName, label, isLlm);
710
971
  })
711
972
  )
712
- )
713
- ),
714
- // ── Scope Constraints (collapsible) ──
715
- // ── Custom: Scope Constraints (inline) ──
716
- isCustom && React.createElement(
717
- Field,
718
- { label: "Scope", align: "start" },
973
+ ),
974
+ // ── Escalation (always visible, readOnly when preset) ──
719
975
  React.createElement(
720
- Flex,
721
- {
722
- variant: "column-stretch-start-nowrap-8"
723
- },
724
- // Blocked paths
976
+ Field,
977
+ { label: "Retries", align: "start" },
725
978
  React.createElement(
726
- Field,
727
- { label: "Blocked", labelWidth: 70, align: "start" },
979
+ Flex,
980
+ {
981
+ variant: "column-stretch-start-nowrap-8"
982
+ },
728
983
  React.createElement(
729
- Flex,
730
- { variant: "column-stretch-start-nowrap-4" },
984
+ Field,
985
+ { label: "Max attempts", labelWidth: 100, align: "center" },
986
+ React.createElement(Input, {
987
+ type: "number",
988
+ size: "small",
989
+ value: String(behavior.maxAttempts),
990
+ readOnly: !isCustom,
991
+ onChange: isCustom ? (v) => updateEscalation("maxAttempts", Math.max(1, parseInt(v) || 1)) : void 0,
992
+ defaultBoxStyle: { width: 60 }
993
+ })
994
+ ),
995
+ React.createElement(
996
+ Field,
997
+ { label: "On exhausted", labelWidth: 100, align: "start" },
998
+ React.createElement(ToggleGroup, {
999
+ options: [
1000
+ { label: "Block", value: "block", icon: "block", selected: behavior.onExhausted === "block" },
1001
+ { label: "Reassign", value: "reassign", icon: "sync", selected: behavior.onExhausted === "reassign" },
1002
+ { label: "Notify", value: "notify", icon: "notifications", selected: behavior.onExhausted === "notify" }
1003
+ ],
1004
+ onSelect: (v) => updateEscalation("onExhausted", v),
1005
+ readOnly: !isCustom,
1006
+ size: "extraSmall"
1007
+ })
1008
+ )
1009
+ )
1010
+ ),
1011
+ // ── Scope Constraints (collapsible) ──
1012
+ // ── Custom: Scope Constraints (inline) ──
1013
+ isCustom && React.createElement(
1014
+ Field,
1015
+ { label: "Scope", align: "start" },
1016
+ React.createElement(
1017
+ Flex,
1018
+ {
1019
+ variant: "column-stretch-start-nowrap-8"
1020
+ },
1021
+ // Blocked paths
1022
+ React.createElement(
1023
+ Field,
1024
+ { label: "Blocked", labelWidth: 70, align: "start" },
731
1025
  React.createElement(
732
1026
  Flex,
733
- { variant: "row-center-start-nowrap-4" },
734
- React.createElement(Input, {
735
- type: "text",
736
- size: "small",
737
- placeholder: "e.g. src/",
738
- value: blockedInput,
739
- onChange: (v) => setBlockedInput(v),
740
- onEnter: () => handleAddScopePath("blocked"),
741
- defaultBoxStyle: { flex: 1, minWidth: 0 },
742
- inputBoxStyle: { maxWidth: "none" }
743
- }),
744
- React.createElement(IconButton, {
745
- icon: "add",
746
- size: "sm",
747
- variant: "outlined",
748
- color: "primary",
749
- onClick: () => handleAddScopePath("blocked"),
750
- disabled: !blockedInput.trim()
751
- })
752
- ),
753
- ...behavior.blockedPaths.map(
754
- (p, i) => React.createElement(
1027
+ { variant: "column-stretch-start-nowrap-4" },
1028
+ React.createElement(
755
1029
  Flex,
756
- { key: `b-${p}-${i}`, variant: "row-center-start-nowrap-6" },
757
- React.createElement(Icon, { name: "block", size: 12, color: "color-status-negative" }),
758
- React.createElement(Typography, { variant: "smallCaption-regular", color: "color-text-high", style: { flex: 1 } }, p),
1030
+ { variant: "row-center-start-nowrap-4" },
1031
+ React.createElement(Input, {
1032
+ type: "text",
1033
+ size: "small",
1034
+ placeholder: "e.g. src/",
1035
+ value: blockedInput,
1036
+ onChange: (v) => setBlockedInput(v),
1037
+ onEnter: () => handleAddScopePath("blocked"),
1038
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
1039
+ inputBoxStyle: { maxWidth: "none" }
1040
+ }),
759
1041
  React.createElement(IconButton, {
760
- icon: "close",
761
- size: "xs",
762
- variant: "clear",
763
- color: "danger",
764
- onClick: () => handleRemoveScopePath("blocked", i)
1042
+ icon: "add",
1043
+ size: "sm",
1044
+ variant: "outlined",
1045
+ color: "primary",
1046
+ onClick: () => handleAddScopePath("blocked"),
1047
+ disabled: !blockedInput.trim()
765
1048
  })
1049
+ ),
1050
+ ...behavior.blockedPaths.map(
1051
+ (p, i) => React.createElement(
1052
+ Flex,
1053
+ { key: `b-${p}-${i}`, variant: "row-center-start-nowrap-6" },
1054
+ React.createElement(Icon, { name: "block", size: 12, color: "color-status-negative" }),
1055
+ React.createElement(Typography, { variant: "smallCaption-regular", color: "color-text-high", style: { flex: 1 } }, p),
1056
+ React.createElement(IconButton, {
1057
+ icon: "close",
1058
+ size: "xs",
1059
+ variant: "clear",
1060
+ color: "danger",
1061
+ onClick: () => handleRemoveScopePath("blocked", i)
1062
+ })
1063
+ )
766
1064
  )
767
1065
  )
768
- )
769
- ),
770
- // Allowed paths
771
- React.createElement(
772
- Field,
773
- { label: "Allowed", labelWidth: 70, align: "start" },
1066
+ ),
1067
+ // Allowed paths
774
1068
  React.createElement(
775
- Flex,
776
- { variant: "column-stretch-start-nowrap-4" },
1069
+ Field,
1070
+ { label: "Allowed", labelWidth: 70, align: "start" },
777
1071
  React.createElement(
778
1072
  Flex,
779
- { variant: "row-center-start-nowrap-4" },
780
- React.createElement(Input, {
781
- type: "text",
782
- size: "small",
783
- placeholder: "e.g. config/",
784
- value: allowedInput,
785
- onChange: (v) => setAllowedInput(v),
786
- onEnter: () => handleAddScopePath("allowed"),
787
- defaultBoxStyle: { flex: 1, minWidth: 0 },
788
- inputBoxStyle: { maxWidth: "none" }
789
- }),
790
- React.createElement(IconButton, {
791
- icon: "add",
792
- size: "sm",
793
- variant: "outlined",
794
- color: "primary",
795
- onClick: () => handleAddScopePath("allowed"),
796
- disabled: !allowedInput.trim()
797
- })
798
- ),
799
- ...behavior.allowedPaths.map(
800
- (p, i) => React.createElement(
1073
+ { variant: "column-stretch-start-nowrap-4" },
1074
+ React.createElement(
801
1075
  Flex,
802
- { key: `a-${p}-${i}`, variant: "row-center-start-nowrap-6" },
803
- React.createElement(Icon, { name: "checkCircle", size: 12, color: "color-status-positive" }),
804
- React.createElement(Typography, { variant: "smallCaption-regular", color: "color-text-high", style: { flex: 1 } }, p),
1076
+ { variant: "row-center-start-nowrap-4" },
1077
+ React.createElement(Input, {
1078
+ type: "text",
1079
+ size: "small",
1080
+ placeholder: "e.g. config/",
1081
+ value: allowedInput,
1082
+ onChange: (v) => setAllowedInput(v),
1083
+ onEnter: () => handleAddScopePath("allowed"),
1084
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
1085
+ inputBoxStyle: { maxWidth: "none" }
1086
+ }),
805
1087
  React.createElement(IconButton, {
806
- icon: "close",
807
- size: "xs",
808
- variant: "clear",
809
- color: "danger",
810
- onClick: () => handleRemoveScopePath("allowed", i)
1088
+ icon: "add",
1089
+ size: "sm",
1090
+ variant: "outlined",
1091
+ color: "primary",
1092
+ onClick: () => handleAddScopePath("allowed"),
1093
+ disabled: !allowedInput.trim()
811
1094
  })
1095
+ ),
1096
+ ...behavior.allowedPaths.map(
1097
+ (p, i) => React.createElement(
1098
+ Flex,
1099
+ { key: `a-${p}-${i}`, variant: "row-center-start-nowrap-6" },
1100
+ React.createElement(Icon, { name: "checkCircle", size: 12, color: "color-status-positive" }),
1101
+ React.createElement(Typography, { variant: "smallCaption-regular", color: "color-text-high", style: { flex: 1 } }, p),
1102
+ React.createElement(IconButton, {
1103
+ icon: "close",
1104
+ size: "xs",
1105
+ variant: "clear",
1106
+ color: "danger",
1107
+ onClick: () => handleRemoveScopePath("allowed", i)
1108
+ })
1109
+ )
812
1110
  )
813
1111
  )
814
1112
  )
815
1113
  )
816
- )
817
- ),
818
- // ── Capabilities ──
819
- React.createElement(
820
- Field,
821
- { label: "Capabilities", align: "start" },
1114
+ ),
1115
+ // ── Capabilities ──
822
1116
  React.createElement(
823
- Flex,
824
- { variant: "column-stretch-start-nowrap-6" },
825
- // Add row
1117
+ Field,
1118
+ { label: "Capabilities", align: "start" },
826
1119
  React.createElement(
827
1120
  Flex,
828
- { variant: "row-center-start-nowrap-4", style: { overflow: "hidden" } },
829
- React.createElement(Input, {
830
- type: "text",
831
- size: "small",
832
- placeholder: "Name",
833
- value: capName,
834
- onChange: (v) => setCapName(v),
835
- defaultBoxStyle: { flex: 1, minWidth: 0 },
836
- inputBoxStyle: { maxWidth: "none" }
837
- }),
838
- React.createElement(Input, {
839
- type: "text",
840
- size: "small",
841
- placeholder: "Description",
842
- value: capDescription,
843
- onChange: (v) => setCapDescription(v),
844
- onEnter: handleAddCapability,
845
- defaultBoxStyle: { flex: 2, minWidth: 0 },
846
- inputBoxStyle: { maxWidth: "none" }
847
- }),
848
- React.createElement(IconButton, {
849
- icon: "add",
850
- size: "sm",
851
- variant: "outlined",
852
- color: "primary",
853
- onClick: handleAddCapability,
854
- disabled: !capName.trim() || !capDescription.trim()
1121
+ { variant: "column-stretch-start-nowrap-6" },
1122
+ // Add row
1123
+ React.createElement(
1124
+ Flex,
1125
+ { variant: "row-center-start-nowrap-4", style: { minWidth: 0 } },
1126
+ React.createElement(Input, {
1127
+ type: "text",
1128
+ size: "small",
1129
+ placeholder: "Name",
1130
+ value: capName,
1131
+ onChange: (v) => setCapName(v),
1132
+ defaultBoxStyle: { flex: "0 1 120px", minWidth: 0 },
1133
+ inputBoxStyle: { maxWidth: "none", minWidth: 0 }
1134
+ }),
1135
+ React.createElement(Input, {
1136
+ type: "text",
1137
+ size: "small",
1138
+ placeholder: "Description",
1139
+ value: capDescription,
1140
+ onChange: (v) => setCapDescription(v),
1141
+ onEnter: handleAddCapability,
1142
+ defaultBoxStyle: { flex: "1 1 0", minWidth: 0 },
1143
+ inputBoxStyle: { maxWidth: "none", minWidth: 0 }
1144
+ }),
1145
+ React.createElement(IconButton, {
1146
+ icon: "add",
1147
+ size: "sm",
1148
+ variant: "outlined",
1149
+ color: "primary",
1150
+ onClick: handleAddCapability,
1151
+ disabled: !capName.trim() || !capDescription.trim(),
1152
+ style: { flexShrink: 0 }
1153
+ })
1154
+ ),
1155
+ // Table
1156
+ capabilities.length > 0 && React.createElement(Table, {
1157
+ size: "compact",
1158
+ showHeader: false,
1159
+ data: capabilities,
1160
+ getRowKey: (_row, i) => `cap-${i}`,
1161
+ columns: [
1162
+ {
1163
+ key: "name",
1164
+ header: "Name",
1165
+ width: "30%",
1166
+ render: (val) => React.createElement("div", { style: { lineHeight: "28px" } }, String(val))
1167
+ },
1168
+ {
1169
+ key: "description",
1170
+ header: "Description",
1171
+ render: (val) => React.createElement("div", { style: { lineHeight: "28px" } }, String(val))
1172
+ },
1173
+ {
1174
+ key: "actions",
1175
+ header: "",
1176
+ width: "32px",
1177
+ align: "right",
1178
+ render: (_val, _row, idx) => React.createElement(IconButton, {
1179
+ icon: "close",
1180
+ size: "xs",
1181
+ variant: "clear",
1182
+ color: "danger",
1183
+ onClick: () => handleRemoveCapability(idx)
1184
+ })
1185
+ }
1186
+ ]
855
1187
  })
856
- ),
857
- // Table
858
- capabilities.length > 0 && React.createElement(Table, {
859
- size: "compact",
860
- showHeader: false,
861
- data: capabilities,
862
- getRowKey: (_row, i) => `cap-${i}`,
863
- columns: [
864
- {
865
- key: "name",
866
- header: "Name",
867
- width: "30%",
868
- render: (val) => React.createElement("div", { style: { lineHeight: "28px" } }, String(val))
869
- },
870
- {
871
- key: "description",
872
- header: "Description",
873
- render: (val) => React.createElement("div", { style: { lineHeight: "28px" } }, String(val))
874
- },
875
- {
876
- key: "actions",
877
- header: "",
878
- width: "32px",
879
- align: "right",
880
- render: (_val, _row, idx) => React.createElement(IconButton, {
881
- icon: "close",
882
- size: "xs",
883
- variant: "clear",
884
- color: "danger",
885
- onClick: () => handleRemoveCapability(idx)
886
- })
887
- }
888
- ]
1188
+ )
1189
+ ),
1190
+ // ── Instructions (multiline) ──
1191
+ React.createElement(
1192
+ Field,
1193
+ { label: "Instructions", align: "start" },
1194
+ React.createElement(Input, {
1195
+ type: "text",
1196
+ size: "small",
1197
+ multiline: true,
1198
+ placeholder: "Behavioral protocol for this profile (optional)",
1199
+ value: instructions,
1200
+ onChange: (v) => setInstructions(v),
1201
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
1202
+ inputBoxStyle: { maxWidth: "none" }
889
1203
  })
1204
+ ),
1205
+ // ── Require Approval ──
1206
+ React.createElement(
1207
+ Field,
1208
+ { label: "" },
1209
+ React.createElement(Checkbox, {
1210
+ checked: requireApproval,
1211
+ onChange: (v) => setRequireApproval(v),
1212
+ label: "Require approval",
1213
+ size: "sm"
1214
+ })
1215
+ ),
1216
+ // ── Save button ──
1217
+ React.createElement(
1218
+ Flex,
1219
+ { variant: "row-center-end-nowrap-8" },
1220
+ React.createElement(Button, {
1221
+ size: "xs",
1222
+ variant: "fill",
1223
+ color: "primary",
1224
+ onClick: handleSave,
1225
+ disabled: !name.trim() || !botId || capabilities.length === 0
1226
+ }, mode === "create" ? "Create" : "Save")
890
1227
  )
891
- ),
892
- // ── Instructions (multiline) ──
893
- React.createElement(
894
- Field,
895
- { label: "Instructions", align: "start" },
896
- React.createElement(Input, {
897
- type: "text",
898
- size: "small",
899
- multiline: true,
900
- placeholder: "Behavioral protocol for this profile (optional)",
901
- value: instructions,
902
- onChange: (v) => setInstructions(v),
903
- defaultBoxStyle: { flex: 1, minWidth: 0 },
904
- inputBoxStyle: { maxWidth: "none" }
905
- })
906
- ),
907
- // ── Require Approval ──
908
- React.createElement(
909
- Field,
910
- { label: "" },
911
- React.createElement(Checkbox, {
912
- checked: requireApproval,
913
- onChange: (v) => setRequireApproval(v),
914
- label: "Require approval",
915
- size: "sm"
916
- })
917
- ),
918
- // ── Save button ──
919
- React.createElement(
920
- Flex,
921
- { variant: "row-center-end-nowrap-8" },
922
- React.createElement(Button, {
923
- size: "xs",
924
- variant: "fill",
925
- color: "primary",
926
- onClick: handleSave,
927
- disabled: !name.trim() || !botId || capabilities.length === 0
928
- }, mode === "create" ? "Create" : "Save")
929
1228
  )
930
1229
  )
931
1230
  );