@arbidocs/blocks 0.4.135 → 0.4.137

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 (123) hide show
  1. package/dist/agents/index.cjs +422 -367
  2. package/dist/agents/index.js +8 -9
  3. package/dist/agents/skills/index.d.ts +2 -0
  4. package/dist/agents/skills/skillFiles.d.ts +65 -0
  5. package/dist/agents/skills/skillImport.d.ts +36 -0
  6. package/dist/agents/workflows/WorkflowDocumentsField.d.ts +42 -5
  7. package/dist/agents/workflows/index.d.ts +3 -3
  8. package/dist/agents/workflows/workflow.d.ts +19 -0
  9. package/dist/agents/workflows/workspaceScope.d.ts +12 -0
  10. package/dist/chat/index.cjs +138 -138
  11. package/dist/chat/index.js +3 -3
  12. package/dist/chunk-2KUCVSFF.js +51 -0
  13. package/dist/chunk-2KUCVSFF.js.map +1 -0
  14. package/dist/{chunk-GDBDHOI4.js → chunk-3RJN2VTL.js} +36 -6
  15. package/dist/chunk-3RJN2VTL.js.map +1 -0
  16. package/dist/{chunk-6AALMYPS.cjs → chunk-6F7SZCVL.cjs} +23 -2
  17. package/dist/chunk-6F7SZCVL.cjs.map +1 -0
  18. package/dist/{chunk-PUCY3UXN.js → chunk-7M4C6CK7.js} +3 -3
  19. package/dist/{chunk-PUCY3UXN.js.map → chunk-7M4C6CK7.js.map} +1 -1
  20. package/dist/{chunk-2PNNCEPH.js → chunk-ATJUVHVB.js} +1545 -8
  21. package/dist/chunk-ATJUVHVB.js.map +1 -0
  22. package/dist/{chunk-4S5IDT5F.cjs → chunk-BUNZWOPX.cjs} +5 -5
  23. package/dist/{chunk-4S5IDT5F.cjs.map → chunk-BUNZWOPX.cjs.map} +1 -1
  24. package/dist/{chunk-KYDRUFWT.cjs → chunk-C3ZRLULA.cjs} +5 -5
  25. package/dist/{chunk-KYDRUFWT.cjs.map → chunk-C3ZRLULA.cjs.map} +1 -1
  26. package/dist/{chunk-SARREDFP.cjs → chunk-CHVCTKF4.cjs} +1620 -47
  27. package/dist/chunk-CHVCTKF4.cjs.map +1 -0
  28. package/dist/chunk-CQCC7LE7.js +210 -0
  29. package/dist/chunk-CQCC7LE7.js.map +1 -0
  30. package/dist/{chunk-4UMXQPNA.js → chunk-E5OYHJMU.js} +3 -3
  31. package/dist/{chunk-4UMXQPNA.js.map → chunk-E5OYHJMU.js.map} +1 -1
  32. package/dist/{chunk-PJJY6CYL.cjs → chunk-EHOPZBAX.cjs} +11 -11
  33. package/dist/{chunk-PJJY6CYL.cjs.map → chunk-EHOPZBAX.cjs.map} +1 -1
  34. package/dist/{chunk-4QIIDXLF.cjs → chunk-HDYWXEQU.cjs} +8 -8
  35. package/dist/{chunk-4QIIDXLF.cjs.map → chunk-HDYWXEQU.cjs.map} +1 -1
  36. package/dist/{chunk-GZPTWHP5.cjs → chunk-HLB4DIYV.cjs} +5 -5
  37. package/dist/{chunk-GZPTWHP5.cjs.map → chunk-HLB4DIYV.cjs.map} +1 -1
  38. package/dist/{chunk-TT6XV6WM.cjs → chunk-JY73DJOS.cjs} +78 -1274
  39. package/dist/chunk-JY73DJOS.cjs.map +1 -0
  40. package/dist/{chunk-ARDHLU73.cjs → chunk-KDUGTKVI.cjs} +21 -2
  41. package/dist/chunk-KDUGTKVI.cjs.map +1 -0
  42. package/dist/{chunk-UMIY4SQS.js → chunk-KMNZ2RVO.js} +17 -1191
  43. package/dist/chunk-KMNZ2RVO.js.map +1 -0
  44. package/dist/{chunk-6S5FLMWG.js → chunk-KSHKTOY2.js} +203 -7
  45. package/dist/chunk-KSHKTOY2.js.map +1 -0
  46. package/dist/{chunk-4RLCRFZS.js → chunk-L2PAPOZ5.js} +21 -3
  47. package/dist/chunk-L2PAPOZ5.js.map +1 -0
  48. package/dist/{chunk-QYTYW74G.js → chunk-L3T7OJRX.js} +4 -4
  49. package/dist/{chunk-QYTYW74G.js.map → chunk-L3T7OJRX.js.map} +1 -1
  50. package/dist/{chunk-ENDP7JER.js → chunk-M33QUNFH.js} +3 -3
  51. package/dist/{chunk-ENDP7JER.js.map → chunk-M33QUNFH.js.map} +1 -1
  52. package/dist/chunk-RFIBUWLF.cjs +232 -0
  53. package/dist/chunk-RFIBUWLF.cjs.map +1 -0
  54. package/dist/{chunk-N26CXGXC.cjs → chunk-RQ2Y7USX.cjs} +201 -3
  55. package/dist/chunk-RQ2Y7USX.cjs.map +1 -0
  56. package/dist/chunk-UUHUQHXR.cjs +56 -0
  57. package/dist/chunk-UUHUQHXR.cjs.map +1 -0
  58. package/dist/{chunk-SZX44LCT.js → chunk-VMESMX7N.js} +3 -3
  59. package/dist/{chunk-SZX44LCT.js.map → chunk-VMESMX7N.js.map} +1 -1
  60. package/dist/{chunk-QDEODGXE.cjs → chunk-WS5EX7DN.cjs} +37 -6
  61. package/dist/chunk-WS5EX7DN.cjs.map +1 -0
  62. package/dist/{chunk-2DFDFOM7.js → chunk-YOMPLCP6.js} +22 -3
  63. package/dist/chunk-YOMPLCP6.js.map +1 -0
  64. package/dist/connect/docx.cjs +4 -4
  65. package/dist/connect/docx.js +3 -3
  66. package/dist/connect/index.cjs +463 -97
  67. package/dist/connect/index.cjs.map +1 -1
  68. package/dist/connect/index.d.ts +5 -2
  69. package/dist/connect/index.js +387 -49
  70. package/dist/connect/index.js.map +1 -1
  71. package/dist/connection/ConnectedSkillUpload.d.ts +26 -2
  72. package/dist/connection/ConnectedWorkflowBuilder.d.ts +24 -12
  73. package/dist/connection/ConnectedWorkflowLauncher.d.ts +35 -0
  74. package/dist/connection/ConnectedWorkflowsTable.d.ts +62 -0
  75. package/dist/connection/ConnectionProvider.d.ts +17 -0
  76. package/dist/connection/workspaceData.d.ts +45 -0
  77. package/dist/facts/index.cjs +1 -1
  78. package/dist/facts/index.js +1 -1
  79. package/dist/files/index.cjs +1 -1
  80. package/dist/files/index.js +1 -1
  81. package/dist/home/index.cjs +20 -21
  82. package/dist/home/index.cjs.map +1 -1
  83. package/dist/home/index.js +2 -3
  84. package/dist/home/index.js.map +1 -1
  85. package/dist/index.cjs +610 -555
  86. package/dist/index.cjs.map +1 -1
  87. package/dist/index.js +14 -15
  88. package/dist/index.js.map +1 -1
  89. package/dist/settings/index.cjs +11 -11
  90. package/dist/settings/index.js +2 -2
  91. package/dist/shell/index.cjs +1 -1
  92. package/dist/shell/index.js +1 -1
  93. package/dist/site/index.cjs +5 -5
  94. package/dist/site/index.js +1 -1
  95. package/dist/studio/index.cjs +7 -7
  96. package/dist/studio/index.js +5 -5
  97. package/dist/workflow-canvas/index.cjs +122 -96
  98. package/dist/workflow-canvas/index.cjs.map +1 -1
  99. package/dist/workflow-canvas/index.d.ts +1 -1
  100. package/dist/workflow-canvas/index.js +73 -48
  101. package/dist/workflow-canvas/index.js.map +1 -1
  102. package/node_modules/@arbidocs/coedit/package.json +1 -1
  103. package/package.json +4 -4
  104. package/dist/chunk-2DFDFOM7.js.map +0 -1
  105. package/dist/chunk-2PNNCEPH.js.map +0 -1
  106. package/dist/chunk-4RLCRFZS.js.map +0 -1
  107. package/dist/chunk-4XJZ6AAY.js +0 -407
  108. package/dist/chunk-4XJZ6AAY.js.map +0 -1
  109. package/dist/chunk-6AALMYPS.cjs.map +0 -1
  110. package/dist/chunk-6S5FLMWG.js.map +0 -1
  111. package/dist/chunk-7D737FLE.cjs +0 -43
  112. package/dist/chunk-7D737FLE.cjs.map +0 -1
  113. package/dist/chunk-ARDHLU73.cjs.map +0 -1
  114. package/dist/chunk-GDBDHOI4.js.map +0 -1
  115. package/dist/chunk-N26CXGXC.cjs.map +0 -1
  116. package/dist/chunk-QDEODGXE.cjs.map +0 -1
  117. package/dist/chunk-S5HZBXZA.cjs +0 -430
  118. package/dist/chunk-S5HZBXZA.cjs.map +0 -1
  119. package/dist/chunk-SARREDFP.cjs.map +0 -1
  120. package/dist/chunk-TT6XV6WM.cjs.map +0 -1
  121. package/dist/chunk-UMIY4SQS.js.map +0 -1
  122. package/dist/chunk-XD2M6LIG.js +0 -41
  123. package/dist/chunk-XD2M6LIG.js.map +0 -1
@@ -1,13 +1,21 @@
1
1
  "use client";
2
2
  import { UploadDropzone } from './chunk-EVUEIBNU.js';
3
- import { FoldSection } from './chunk-XD2M6LIG.js';
4
- import { workflowSteps, canAssign, nextAction, validateStep, assigneeName, runFieldsOf, failedUpstream, stepPhase, useElementSize, sendBackTargets, formatDate, shownValue, stepKindOf, stepLevels, waitingDocuments, stepDependencies, reviewInstructions, splitDocRef, isPersonStep, eventWaitingDocuments, waitingForDocumentsText, StepBadgeMark, StepChip, STEP_KIND_ICONS, DEFAULT_STEP_KIND_LABELS, composeStepPrompt, isRunFinished, runFields, isWorkflowScope } from './chunk-2DFDFOM7.js';
3
+ import { DocumentPicker, TriStateFilterMenu } from './chunk-KSHKTOY2.js';
4
+ import { EmptyState } from './chunk-P6GHJSLR.js';
5
+ import { FoldSection, QuickActionDialogHeader, AddFilesFirst, StartTiles, ScopePicker } from './chunk-3RJN2VTL.js';
6
+ import { SelectionBar, SelectionBarAction } from './chunk-NUNKSZKU.js';
7
+ import { FilterInput } from './chunk-YU2KUMGE.js';
8
+ import { GridView } from './chunk-N5YEF3LC.js';
9
+ import { TabHeader } from './chunk-4WWM3GQ5.js';
10
+ import { defaultInputs, validateInputs, workflowSteps, canAssign, fieldToFrontmatter, workflowFromFrontmatter, nextAction, validateStep, assigneeName, runFieldsOf, failedUpstream, stepPhase, useElementSize, sendBackTargets, formatDate, shownValue, stepKindOf, stepLevels, waitingDocuments, stepDependencies, reviewInstructions, splitDocRef, isPersonStep, eventWaitingDocuments, waitingForDocumentsText, StepBadgeMark, StepChip, buildLaunch, STEP_KIND_ICONS, DEFAULT_STEP_KIND_LABELS, composeStepPrompt, isRunFinished, runFields, isWorkflowScope } from './chunk-YOMPLCP6.js';
5
11
  import { TONE_DOT, TONE_SURFACE, TONE_SOLID_BORDER, TONE_BORDER } from './chunk-AUS3KBHJ.js';
6
12
  import { testIdOr, prefixedTestId } from './chunk-7F6CBP3Z.js';
7
- import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
8
- import { Loader2, UserRound, CornerUpLeft, ShieldCheck, MessageSquare, Check, Hourglass, Clock, PenLine, X, ExternalLink, Mail, Table2, CheckCircle2, ArrowLeft, ArrowRight, RotateCw, GitBranch, AlertTriangle, ChevronLeft, ChevronRight, FileText, ArrowUpRight, BellRing, Flag, RotateCcw, Play, Disc, Phone, UserRoundCog, Sparkles, MessageSquareReply, Square, KeyRound, Lock } from 'lucide-react';
9
- import { cn, Popover, PopoverTrigger, PopoverContent, Button, Textarea, Card, Label, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Switch } from '@arbidocs/react/ui';
13
+ import { initials } from './chunk-OXF2IFXG.js';
14
+ import { useState, useRef, useCallback, useId, useMemo, useEffect } from 'react';
15
+ import { Loader2, Play, FolderPlus, ImageIcon, X, UserRound, CornerUpLeft, ShieldCheck, MessageSquare, Check, Hourglass, Clock, PenLine, ExternalLink, Mail, Table2, CheckCircle2, ArrowLeft, ArrowRight, RotateCw, GitBranch, AlertTriangle, MoreHorizontal, History, Pencil, Trash2, Workflow, ChevronLeft, ChevronRight, FileText, Link2, Bell, MousePointerClick, ArrowUpRight, BellRing, Flag, RotateCcw, Disc, Phone, UserRoundCog, Sparkles, MessageSquareReply, Square, KeyRound, Lock } from 'lucide-react';
16
+ import { cn, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Dialog, DialogContent, Button, getEntityColor, Popover, PopoverTrigger, PopoverContent, Textarea, Card, UserEntity, getEntityColorStyles, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, badgeVariants, VisibilityToggle, Switch, Input } from '@arbidocs/react/ui';
10
17
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
18
+ import { unzip, strFromU8 } from 'fflate';
11
19
 
12
20
  var DEFAULT_SCHEMA_FORM_LABELS = {
13
21
  optional: "(optional)",
@@ -318,6 +326,610 @@ function SchemaForm({
318
326
  )
319
327
  ] });
320
328
  }
329
+ function useControllableState(value, defaultValue, onChange) {
330
+ const [inner, setInner] = useState(defaultValue);
331
+ const isControlled = value !== void 0;
332
+ const current = isControlled ? value : inner;
333
+ const onChangeRef = useRef(onChange);
334
+ onChangeRef.current = onChange;
335
+ const set = useCallback(
336
+ (next) => {
337
+ if (!isControlled) setInner(next);
338
+ onChangeRef.current?.(next);
339
+ },
340
+ [isControlled]
341
+ );
342
+ return [current, set];
343
+ }
344
+ var DEFAULT_AGENT_SELECT_LABELS = {
345
+ label: "Run as",
346
+ myAssistant: "My assistant"
347
+ };
348
+ var ME = "__me__";
349
+ function AgentSelect({
350
+ agents,
351
+ value,
352
+ defaultValue = "",
353
+ onValueChange,
354
+ hideLabel,
355
+ disabled,
356
+ className,
357
+ labels,
358
+ testId,
359
+ optionTestId
360
+ }) {
361
+ const l = { ...DEFAULT_AGENT_SELECT_LABELS, ...labels };
362
+ const triggerId = useId();
363
+ const [current, setCurrent] = useControllableState(value, defaultValue, onValueChange);
364
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", className), "data-slot": "agent-select", children: [
365
+ /* @__PURE__ */ jsx(Label, { htmlFor: triggerId, className: hideLabel ? "sr-only" : void 0, children: l.label }),
366
+ /* @__PURE__ */ jsxs(
367
+ Select,
368
+ {
369
+ value: current === "" ? ME : current,
370
+ onValueChange: (v) => setCurrent(v === ME ? "" : v),
371
+ disabled,
372
+ children: [
373
+ /* @__PURE__ */ jsx(
374
+ SelectTrigger,
375
+ {
376
+ id: triggerId,
377
+ "data-testid": testIdOr(testId, { testId: "agent-select" }),
378
+ className: "pointer-coarse:min-h-11",
379
+ children: /* @__PURE__ */ jsx(SelectValue, {})
380
+ }
381
+ ),
382
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
383
+ /* @__PURE__ */ jsx(
384
+ SelectItem,
385
+ {
386
+ value: ME,
387
+ "data-testid": prefixedTestId(optionTestId, "me", {
388
+ testId: "agent-select-option-me"
389
+ }),
390
+ children: l.myAssistant
391
+ }
392
+ ),
393
+ agents.map((agent) => /* @__PURE__ */ jsx(
394
+ SelectItem,
395
+ {
396
+ value: agent.id,
397
+ "data-testid": prefixedTestId(optionTestId, agent.id, {
398
+ testId: `agent-select-option-${agent.id}`
399
+ }),
400
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
401
+ agent.avatar,
402
+ /* @__PURE__ */ jsx("span", { children: agent.label }),
403
+ agent.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: agent.description })
404
+ ] })
405
+ },
406
+ agent.id
407
+ ))
408
+ ] })
409
+ ]
410
+ }
411
+ )
412
+ ] });
413
+ }
414
+ var DEFAULT_WORKFLOW_CONFIG_DIALOG_LABELS = {
415
+ start: "Start",
416
+ starting: "Starting\u2026",
417
+ addFilesReason: "This reads your files, and there is nothing to read yet.",
418
+ agentLabel: "Run as"
419
+ };
420
+ function WorkflowConfigDialog({
421
+ workflow,
422
+ open,
423
+ onOpenChange,
424
+ onLaunch,
425
+ initialInputs,
426
+ inputs: inputsProp,
427
+ onInputsChange,
428
+ scopeSource,
429
+ renderScope,
430
+ renderDocuments,
431
+ renderIcon,
432
+ addFilesAction,
433
+ agents,
434
+ agentId: agentIdProp,
435
+ onAgentChange,
436
+ isLaunching,
437
+ submitLabel,
438
+ fieldPlaceholder,
439
+ renderNotice,
440
+ labels,
441
+ formLabels,
442
+ agentLabels,
443
+ testId,
444
+ startTestId,
445
+ className
446
+ }) {
447
+ const l = { ...DEFAULT_WORKFLOW_CONFIG_DIALOG_LABELS, ...labels };
448
+ const initialKey = JSON.stringify(initialInputs ?? {});
449
+ const seedKey = JSON.stringify({ ...defaultInputs(workflow), ...JSON.parse(initialKey) });
450
+ const seed = useMemo(() => JSON.parse(seedKey), [seedKey]);
451
+ const [inputs, setInputs] = useControllableState(inputsProp, seed, onInputsChange);
452
+ const [agentId, setAgentId] = useControllableState(
453
+ agentIdProp,
454
+ workflow.defaultAgentId ?? "",
455
+ onAgentChange
456
+ );
457
+ useEffect(() => {
458
+ if (open && inputsProp === void 0) setInputs(seed);
459
+ }, [open, seed]);
460
+ const setField = (name, value) => setInputs({ ...inputs, [name]: value });
461
+ const errors = validateInputs(workflow, inputs);
462
+ const valid = Object.keys(errors).length === 0;
463
+ const visibleErrors = Object.fromEntries(
464
+ Object.entries(errors).filter(([, e]) => e !== "required")
465
+ );
466
+ const scopeField = workflow.inputs.find((f) => f.type === "scope");
467
+ const rawScope = scopeField ? inputs[scopeField.name] : void 0;
468
+ const scopeValue = rawScope && typeof rawScope === "object" ? rawScope : { folders: [], labels: [] };
469
+ const matchedCount = scopeSource && scopeField ? scopeSource.countMatching(scopeValue) : null;
470
+ const noFiles = Boolean(workflow.requiresFiles && scopeSource && scopeSource.totalCount === 0);
471
+ const blockedByScope = matchedCount !== null && matchedCount === 0 && Boolean(workflow.requiresFiles);
472
+ const showAgent = workflow.run.kind === "prompt" && workflow.options?.agent !== false && agents !== void 0 && agents.length > 0;
473
+ const launch = (values) => {
474
+ Promise.resolve(onLaunch(buildLaunch(workflow, values, agentId))).catch(() => {
475
+ });
476
+ };
477
+ const submit = () => {
478
+ if (!valid || noFiles || blockedByScope || isLaunching) return;
479
+ launch(inputs);
480
+ };
481
+ const fields = useMemo(
482
+ () => workflow.inputs.map((f) => {
483
+ const placeholder = fieldPlaceholder?.(f, inputs, workflow);
484
+ return placeholder === void 0 ? f : { ...f, placeholder };
485
+ }),
486
+ [workflow, fieldPlaceholder, inputs]
487
+ );
488
+ const id = testIdOr(testId, { testId: "workflow-dialog" });
489
+ const formRef = useRef(null);
490
+ const firstField = workflow.inputs.find((f) => !f.advanced);
491
+ const focusPromptFirst = firstField?.type === "text" || firstField?.type === "longtext";
492
+ const defaultScopeRender = scopeSource ? (_field, value, onChange) => /* @__PURE__ */ jsx(
493
+ ScopePicker,
494
+ {
495
+ folders: scopeSource.folders,
496
+ unfiledCount: scopeSource.unfiledCount,
497
+ labels: scopeSource.labels,
498
+ totalCount: scopeSource.totalCount,
499
+ matchedCount: scopeSource.countMatching(value),
500
+ scope: value,
501
+ onScopeChange: onChange
502
+ }
503
+ ) : void 0;
504
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
505
+ DialogContent,
506
+ {
507
+ presentation: "sheet",
508
+ className: `max-h-[calc(100dvh-2rem)] overflow-y-auto sm:max-w-xl ${className ?? ""}`,
509
+ "data-testid": id,
510
+ "data-workflow": workflow.id,
511
+ onOpenAutoFocus: (event) => {
512
+ if (!focusPromptFirst || !firstField) return;
513
+ const control = formRef.current?.querySelector(
514
+ `[data-testid="${id}-field-${firstField.name}"]`
515
+ );
516
+ if (!control) return;
517
+ event.preventDefault();
518
+ control.focus();
519
+ if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) {
520
+ const end = control.value.length;
521
+ control.setSelectionRange(end, end);
522
+ }
523
+ },
524
+ children: [
525
+ /* @__PURE__ */ jsx(
526
+ QuickActionDialogHeader,
527
+ {
528
+ icon: workflow.icon && renderIcon ? renderIcon(workflow.icon) : null,
529
+ title: workflow.title,
530
+ description: workflow.details ?? workflow.description,
531
+ descriptionTestId: `${id}-description`
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsx(
535
+ "form",
536
+ {
537
+ ref: formRef,
538
+ className: "mt-2 flex flex-col gap-4",
539
+ onSubmit: (event) => {
540
+ event.preventDefault();
541
+ submit();
542
+ },
543
+ children: noFiles ? /* @__PURE__ */ jsx(
544
+ AddFilesFirst,
545
+ {
546
+ reason: l.addFilesReason,
547
+ action: addFilesAction,
548
+ testId: `${id}-empty-state`
549
+ }
550
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
551
+ /* @__PURE__ */ jsx(
552
+ SchemaForm,
553
+ {
554
+ fields,
555
+ values: inputs,
556
+ onChange: setField,
557
+ errors: visibleErrors,
558
+ renderScope: renderScope ?? defaultScopeRender,
559
+ renderDocuments,
560
+ renderIcon,
561
+ labels: formLabels,
562
+ onEnterSubmit: submit,
563
+ testIdPrefix: `${id}-field`
564
+ }
565
+ ),
566
+ renderNotice?.({ workflow, inputs }),
567
+ /* @__PURE__ */ jsxs(
568
+ "div",
569
+ {
570
+ className: "flex flex-col gap-3 border-t border-border pt-3 sm:flex-row sm:items-end sm:justify-between",
571
+ "data-slot": "workflow-footer",
572
+ children: [
573
+ showAgent ? /* @__PURE__ */ jsx(
574
+ AgentSelect,
575
+ {
576
+ agents: agents ?? [],
577
+ value: agentId,
578
+ onValueChange: setAgentId,
579
+ labels: { label: l.agentLabel, ...agentLabels },
580
+ testId: `${id}-agent`,
581
+ className: "sm:w-56"
582
+ }
583
+ ) : /* @__PURE__ */ jsx("span", {}),
584
+ /* @__PURE__ */ jsxs(
585
+ Button,
586
+ {
587
+ type: "submit",
588
+ className: "gap-1.5 px-5 pointer-coarse:min-h-11",
589
+ disabled: !valid || blockedByScope || isLaunching,
590
+ "data-testid": testIdOr(startTestId, { testId: "workflow-start-button" }),
591
+ children: [
592
+ isLaunching ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Play, { className: "size-4" }),
593
+ isLaunching ? l.starting : submitLabel?.({ workflow, inputs, matchedCount }) ?? workflow.startLabel ?? l.start
594
+ ]
595
+ }
596
+ )
597
+ ]
598
+ }
599
+ )
600
+ ] })
601
+ }
602
+ )
603
+ ]
604
+ }
605
+ ) });
606
+ }
607
+ function WorkflowLauncher({
608
+ workflows,
609
+ tileWorkflows,
610
+ onLaunch,
611
+ showTiles = true,
612
+ caption,
613
+ captionTestId,
614
+ tileTestId,
615
+ disabledReason,
616
+ activeId: activeIdProp,
617
+ onActiveIdChange,
618
+ initialInputsFor,
619
+ testId,
620
+ extraTiles = [],
621
+ className,
622
+ children,
623
+ renderIcon,
624
+ ...dialog
625
+ }) {
626
+ const [activeId, setActiveId] = useControllableState(
627
+ activeIdProp,
628
+ null,
629
+ onActiveIdChange
630
+ );
631
+ const [isLaunching, setIsLaunching] = useState(false);
632
+ const active = workflows.find((w) => w.id === activeId) ?? null;
633
+ const run = async (launch) => {
634
+ setIsLaunching(true);
635
+ try {
636
+ await onLaunch(launch);
637
+ setActiveId(null);
638
+ } finally {
639
+ setIsLaunching(false);
640
+ }
641
+ };
642
+ const select = (workflow) => {
643
+ if (workflow.run.kind === "operation" && workflow.inputs.length === 0) {
644
+ const inputs = defaultInputs(workflow);
645
+ if (Object.keys(validateInputs(workflow, inputs)).length === 0) {
646
+ Promise.resolve(onLaunch(buildLaunch(workflow, inputs, void 0))).catch(() => {
647
+ });
648
+ return;
649
+ }
650
+ }
651
+ setActiveId(workflow.id);
652
+ };
653
+ const tiles = (tileWorkflows ?? workflows).map((workflow) => {
654
+ const reason = disabledReason?.(workflow);
655
+ return {
656
+ id: workflow.id,
657
+ iconNode: workflow.icon && renderIcon ? renderIcon(workflow.icon) : void 0,
658
+ title: workflow.title,
659
+ description: workflow.description,
660
+ onSelect: () => select(workflow),
661
+ disabled: reason !== void 0,
662
+ disabledReason: reason,
663
+ testId: tileTestId?.(workflow) ?? `workflow-tile-${workflow.id}`
664
+ };
665
+ });
666
+ return /* @__PURE__ */ jsxs("div", { className, "data-slot": "workflow-launcher", children: [
667
+ showTiles && /* @__PURE__ */ jsx(
668
+ StartTiles,
669
+ {
670
+ items: [...tiles, ...extraTiles],
671
+ caption,
672
+ captionTestId,
673
+ testId
674
+ }
675
+ ),
676
+ children,
677
+ active && /* @__PURE__ */ jsx(
678
+ WorkflowConfigDialog,
679
+ {
680
+ ...dialog,
681
+ renderIcon,
682
+ workflow: active,
683
+ open: true,
684
+ onOpenChange: (open) => !open && setActiveId(null),
685
+ onLaunch: run,
686
+ isLaunching,
687
+ initialInputs: initialInputsFor?.(active)
688
+ }
689
+ )
690
+ ] });
691
+ }
692
+ function documentScopeLabels(tags) {
693
+ return tags.filter((tag) => {
694
+ if (tag.kind === "search") return false;
695
+ const onDocs = (tag.doctag_count ?? 0) > 0;
696
+ const onFacts = (tag.facttag_count ?? 0) > 0;
697
+ return onDocs || !onFacts;
698
+ });
699
+ }
700
+ var canonical = (path) => (path ?? "").split("/").map((part) => part.trim()).filter(Boolean).join("/");
701
+ function matchWorkflowScope(docs, scope) {
702
+ if (scope.documents?.length) {
703
+ const ids = new Set(scope.documents);
704
+ return docs.filter((doc) => ids.has(doc.external_id));
705
+ }
706
+ if (!scope.folders.length && !scope.labels.length) return docs;
707
+ const folders = scope.folders.map(canonical);
708
+ const labels = new Set(scope.labels);
709
+ return docs.filter((doc) => {
710
+ const folder = canonical(doc.folder);
711
+ return folders.some(
712
+ (path) => path === "" ? folder === "" : folder === path || folder.startsWith(`${path}/`)
713
+ ) || doc.doctags?.some((tag) => labels.has(tag.tag_ext_id));
714
+ });
715
+ }
716
+ function buildWorkflowScopeSource(docs, labels = []) {
717
+ const counts = /* @__PURE__ */ new Map();
718
+ for (const doc of docs) {
719
+ const parts = canonical(doc.folder).split("/").filter(Boolean);
720
+ for (let i = 1; i <= parts.length; i++) {
721
+ const path = parts.slice(0, i).join("/");
722
+ counts.set(path, (counts.get(path) ?? 0) + 1);
723
+ }
724
+ }
725
+ return {
726
+ folders: [...counts].sort(([a], [b]) => a.localeCompare(b)).map(([path, count]) => ({
727
+ path,
728
+ count,
729
+ label: path.split("/").at(-1),
730
+ depth: path.split("/").length - 1
731
+ })),
732
+ unfiledCount: docs.filter((doc) => !canonical(doc.folder)).length,
733
+ labels: labels.map((label) => ({
734
+ id: label.external_id,
735
+ name: label.name,
736
+ color: label.color ?? getEntityColor(label.external_id, "medium"),
737
+ count: docs.filter((doc) => doc.doctags?.some((tag) => tag.tag_ext_id === label.external_id)).length
738
+ })),
739
+ totalCount: docs.length,
740
+ countMatching: (scope) => matchWorkflowScope(docs, scope).length,
741
+ resolveDocuments: (scope) => matchWorkflowScope(docs, scope).map((doc) => doc.external_id)
742
+ };
743
+ }
744
+ var DEFAULT_WORKFLOW_DOCUMENTS_FIELD_LABELS = {
745
+ pickOne: "Pick a file",
746
+ pickMany: "Pick files",
747
+ allDocuments: "All documents",
748
+ addFiles: "Add files",
749
+ adding: "Adding\u2026",
750
+ empty: "No files in this workspace yet. Drop one here, or add it.",
751
+ remove: (name) => `Remove ${name}`,
752
+ pending: (name, status) => `${name}: ${status}. You can still pick it.`,
753
+ status: {
754
+ queued: "Queued",
755
+ uploading: "Uploading",
756
+ encrypting: "Encrypting",
757
+ processing: "Processing",
758
+ parsing: "Processing",
759
+ indexing: "Indexing",
760
+ analysing: "Analysing"
761
+ }
762
+ };
763
+ var isReady = (status) => !status || ["completed", "done", "ready", "success"].includes(status.toLowerCase());
764
+ var byTitle = (a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase());
765
+ function WorkflowDocumentsField({
766
+ field,
767
+ value,
768
+ onChange,
769
+ documents,
770
+ loaded = true,
771
+ thumbnailOf,
772
+ onAddFiles,
773
+ adding,
774
+ labels
775
+ }) {
776
+ const l = { ...DEFAULT_WORKFLOW_DOCUMENTS_FIELD_LABELS, ...labels };
777
+ const ids = value.documents ?? [];
778
+ const max = field.max ?? 1;
779
+ const multiple = max > 1;
780
+ const [isOver, setIsOver] = useState(false);
781
+ const docOf = (id) => documents.find((d) => d.id === id);
782
+ const nameOf = (id) => docOf(id)?.title || id;
783
+ const set = (next) => onChange({ ...value, documents: next });
784
+ const toggle = (id) => set(ids.includes(id) ? ids.filter((d) => d !== id) : [...ids, id].slice(-max));
785
+ const offered = useMemo(
786
+ () => field.accept === "image" ? documents.filter((d) => d.isImage) : documents,
787
+ [documents, field.accept]
788
+ );
789
+ const awaitingAdd = useRef(false);
790
+ const known = useRef(null);
791
+ const latest = useRef({ ids, set });
792
+ latest.current = { ids, set };
793
+ useEffect(() => {
794
+ if (!loaded) return;
795
+ const current = new Set(documents.map((d) => d.id));
796
+ const before = known.current;
797
+ known.current = current;
798
+ if (!before || !awaitingAdd.current) return;
799
+ const fresh = [...current].filter((id) => !before.has(id));
800
+ if (fresh.length === 0) return;
801
+ awaitingAdd.current = false;
802
+ latest.current.set([...latest.current.ids, ...fresh].slice(-max));
803
+ }, [documents, loaded, max]);
804
+ const addFiles = (files) => {
805
+ if (!onAddFiles) return;
806
+ awaitingAdd.current = true;
807
+ onAddFiles(files?.length ? files : void 0);
808
+ };
809
+ const dropProps = onAddFiles ? {
810
+ onDragOver: (e) => {
811
+ e.preventDefault();
812
+ setIsOver(true);
813
+ },
814
+ onDragLeave: () => setIsOver(false),
815
+ onDrop: (e) => {
816
+ e.preventDefault();
817
+ setIsOver(false);
818
+ const files = Array.from(e.dataTransfer?.files ?? []);
819
+ if (files.length) addFiles(files);
820
+ }
821
+ } : {};
822
+ const dropClass = isOver ? "ring-2 ring-primary" : "";
823
+ const addButton = (className, variant = "outline") => onAddFiles && /* @__PURE__ */ jsx(
824
+ Button,
825
+ {
826
+ type: "button",
827
+ size: "sm",
828
+ variant,
829
+ className,
830
+ disabled: adding,
831
+ onClick: () => addFiles(),
832
+ "data-testid": "workflow-add-files",
833
+ children: adding ? l.adding : l.addFiles
834
+ }
835
+ );
836
+ if (loaded && documents.length === 0) {
837
+ return /* @__PURE__ */ jsxs(
838
+ "div",
839
+ {
840
+ className: cn(
841
+ "flex flex-col items-center gap-2 rounded-lg border border-dashed border-border bg-muted/40 px-4 py-6 text-center",
842
+ dropClass
843
+ ),
844
+ "data-testid": "workflow-documents-empty",
845
+ ...dropProps,
846
+ children: [
847
+ /* @__PURE__ */ jsx(FolderPlus, { className: "size-5 text-muted-foreground", "aria-hidden": true }),
848
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: l.empty }),
849
+ addButton(void 0, "default")
850
+ ]
851
+ }
852
+ );
853
+ }
854
+ const pending = ids.flatMap((id) => {
855
+ const status = docOf(id)?.status;
856
+ return isReady(status) ? [] : [{ id, label: l.status[String(status).toLowerCase()] ?? String(status) }];
857
+ });
858
+ const selected = multiple ? void 0 : docOf(ids[0] ?? "");
859
+ return /* @__PURE__ */ jsxs(
860
+ "div",
861
+ {
862
+ className: cn("flex flex-col gap-3 rounded-lg", dropClass),
863
+ "data-testid": `workflow-documents-${field.name}`,
864
+ ...dropProps,
865
+ children: [
866
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
867
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
868
+ DocumentPicker,
869
+ {
870
+ groups: [{ id: "all", heading: l.allDocuments, docs: [...offered].sort(byTitle) }],
871
+ value: multiple ? "" : ids[0] ?? "",
872
+ onValueChange: (id) => multiple ? toggle(id) : set([id]),
873
+ selected,
874
+ selectedIds: multiple ? ids : void 0,
875
+ labels: { placeholder: field.placeholder ?? (multiple ? l.pickMany : l.pickOne) }
876
+ }
877
+ ) }),
878
+ addButton("shrink-0 pointer-coarse:h-11")
879
+ ] }),
880
+ pending.map((p) => /* @__PURE__ */ jsx(
881
+ "p",
882
+ {
883
+ className: "text-xs text-muted-foreground",
884
+ "data-testid": `workflow-document-status-${p.id}`,
885
+ children: l.pending(nameOf(p.id), p.label)
886
+ },
887
+ p.id
888
+ )),
889
+ multiple && ids.length > 0 && /* @__PURE__ */ jsx(
890
+ "div",
891
+ {
892
+ className: "grid grid-cols-2 gap-3 sm:grid-cols-3",
893
+ "data-testid": "workflow-documents-gallery",
894
+ children: ids.map((id) => {
895
+ const thumbnail = thumbnailOf?.(id);
896
+ return /* @__PURE__ */ jsxs(
897
+ "figure",
898
+ {
899
+ className: "relative overflow-hidden rounded-lg border border-border bg-muted/40",
900
+ "data-testid": `workflow-document-${id}`,
901
+ children: [
902
+ thumbnail ? /* @__PURE__ */ jsx(
903
+ "img",
904
+ {
905
+ src: thumbnail,
906
+ alt: nameOf(id),
907
+ className: "aspect-square w-full object-cover"
908
+ }
909
+ ) : /* @__PURE__ */ jsx("div", { className: "flex aspect-square w-full items-center justify-center text-muted-foreground/50", children: /* @__PURE__ */ jsx(ImageIcon, { className: "size-8" }) }),
910
+ /* @__PURE__ */ jsx("figcaption", { className: "truncate bg-background/90 px-2 py-1 text-xs text-muted-foreground", children: nameOf(id) }),
911
+ /* @__PURE__ */ jsx(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ onClick: () => toggle(id),
916
+ "aria-label": l.remove(nameOf(id)),
917
+ className: "absolute right-1 top-1 flex size-6 items-center justify-center rounded-full bg-background/80 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
918
+ "data-testid": `workflow-document-remove-${id}`,
919
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
920
+ }
921
+ )
922
+ ]
923
+ },
924
+ id
925
+ );
926
+ })
927
+ }
928
+ )
929
+ ]
930
+ }
931
+ );
932
+ }
321
933
 
322
934
  // src/agents/workflows/planSteps.ts
323
935
  function toPlanAssignee(a) {
@@ -569,6 +1181,47 @@ function fromStepField(f) {
569
1181
  ...f.max != null ? { max: f.max } : {}
570
1182
  };
571
1183
  }
1184
+
1185
+ // src/agents/workflows/skillMarkdown.ts
1186
+ var JSON_KEYS = /* @__PURE__ */ new Set(["inputs"]);
1187
+ var oneLine = (text) => text.replace(/\s*\n\s*/g, " ").trim();
1188
+ var skillSlug = (raw) => raw.toLowerCase().replace(/[^a-z0-9-]/g, "");
1189
+ function workflowToSkillMarkdown(workflow) {
1190
+ const { run } = workflow;
1191
+ if (run.kind !== "prompt") throw new Error("Only skills that start a chat can be copied");
1192
+ const lines = [
1193
+ "---",
1194
+ `name: ${skillSlug(workflow.id)}`,
1195
+ `description: ${oneLine(workflow.description)}`
1196
+ ];
1197
+ if (workflow.icon) lines.push(`icon: ${workflow.icon}`);
1198
+ if (workflow.landing !== "chat") lines.push(`landing: ${workflow.landing}`);
1199
+ if (workflow.requiresFiles) lines.push("requires_files: true");
1200
+ if (workflow.inputs.length > 0) {
1201
+ lines.push(`inputs: ${JSON.stringify(workflow.inputs.map(fieldToFrontmatter))}`);
1202
+ }
1203
+ lines.push("---", "", run.promptTemplate.trim(), "");
1204
+ return lines.join("\n");
1205
+ }
1206
+ function workflowFromSkillMarkdown(id, text) {
1207
+ const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text);
1208
+ const frontmatter = {};
1209
+ const body = match ? match[2] : text;
1210
+ for (const line of (match ? match[1] : "").split("\n")) {
1211
+ const i = line.indexOf(":");
1212
+ if (i < 1) continue;
1213
+ const key = line.slice(0, i).trim();
1214
+ const value = line.slice(i + 1).trim();
1215
+ if (JSON_KEYS.has(key)) {
1216
+ try {
1217
+ frontmatter[key] = JSON.parse(value);
1218
+ } catch {
1219
+ }
1220
+ } else if (value === "true" || value === "false") frontmatter[key] = value === "true";
1221
+ else frontmatter[key] = value;
1222
+ }
1223
+ return workflowFromFrontmatter(id, frontmatter, body);
1224
+ }
572
1225
  var errorMessage = (e) => e instanceof Error ? e.message : typeof e === "string" ? e : "Something went wrong.";
573
1226
  function firstOpen(steps, states) {
574
1227
  const open = steps.find((s) => !["done", "skipped"].includes(states[s.id]?.status ?? "pending"));
@@ -2319,6 +2972,890 @@ function StepNotices({
2319
2972
  ))
2320
2973
  ] });
2321
2974
  }
2975
+
2976
+ // src/agents/skills/skillFiles.ts
2977
+ var SKILL_TYPES = ["markdown", "python", "shell", "javascript", "typescript"];
2978
+ var SKILL_MAIN_FILENAME = "SKILL.md";
2979
+ var MAIN_NAMES = /* @__PURE__ */ new Set(["skill.md", "skill.py", "skill.sh", "skill.js", "skill.ts"]);
2980
+ function isMainSkillFile(filename, skillName) {
2981
+ const lower = filename.toLowerCase();
2982
+ if (MAIN_NAMES.has(lower)) return true;
2983
+ const stem = lower.includes(".") ? lower.slice(0, lower.lastIndexOf(".")) : lower;
2984
+ return stem === skillName;
2985
+ }
2986
+ function detectTypeFromContent(content, filename) {
2987
+ if (filename) {
2988
+ const lower = filename.toLowerCase();
2989
+ if (lower.endsWith(".py")) return "python";
2990
+ if (lower.endsWith(".sh")) return "shell";
2991
+ if (lower.endsWith(".js")) return "javascript";
2992
+ if (lower.endsWith(".ts")) return "typescript";
2993
+ }
2994
+ if (content) {
2995
+ const firstLine = content.split("\n", 1)[0].trim();
2996
+ if (firstLine.startsWith("#!")) {
2997
+ if (firstLine.includes("python")) return "python";
2998
+ if (firstLine.includes("bash") || firstLine.includes("/sh")) return "shell";
2999
+ if (firstLine.includes("ts-node")) return "typescript";
3000
+ if (firstLine.includes("node")) return "javascript";
3001
+ }
3002
+ }
3003
+ return "markdown";
3004
+ }
3005
+ function parseSkillFrontmatter(raw, filename = "") {
3006
+ const config = {};
3007
+ let name = "";
3008
+ let description = "";
3009
+ let skillType = "markdown";
3010
+ if (!raw.startsWith("---")) {
3011
+ return {
3012
+ content: raw,
3013
+ name,
3014
+ description,
3015
+ config,
3016
+ skillType: detectTypeFromContent(raw, filename)
3017
+ };
3018
+ }
3019
+ const end = raw.indexOf("---", 3);
3020
+ if (end === -1) {
3021
+ return {
3022
+ content: raw,
3023
+ name,
3024
+ description,
3025
+ config,
3026
+ skillType: detectTypeFromContent(raw, filename)
3027
+ };
3028
+ }
3029
+ const frontmatter = raw.slice(3, end).trim();
3030
+ const body = raw.slice(end + 3).trim();
3031
+ for (const rawLine of frontmatter.split("\n")) {
3032
+ const line = rawLine.trim();
3033
+ if (!line || !line.includes(":")) continue;
3034
+ const idx = line.indexOf(":");
3035
+ const key = line.slice(0, idx).trim().toLowerCase();
3036
+ const value = line.slice(idx + 1).trim();
3037
+ if (key === "name" && value) {
3038
+ name = value;
3039
+ } else if (key === "description" && value) {
3040
+ description = value;
3041
+ } else if (key === "model" && value) {
3042
+ config.model = value;
3043
+ } else if (key === "temperature") {
3044
+ const t = Number.parseFloat(value);
3045
+ if (!Number.isNaN(t) && t >= 0 && t <= 2) config.temperature = t;
3046
+ } else if (key === "agent") {
3047
+ config.agent = ["true", "yes", "1"].includes(value.toLowerCase());
3048
+ } else if (key === "tools" && value) {
3049
+ config.tools = value.split(",").map((t) => t.trim()).filter(Boolean);
3050
+ } else if (key === "type" && value) {
3051
+ const v = value.toLowerCase();
3052
+ if (v === "python" || v === "py") skillType = "python";
3053
+ else if (v === "shell" || v === "sh" || v === "bash") skillType = "shell";
3054
+ else if (v === "javascript" || v === "js" || v === "node") skillType = "javascript";
3055
+ else if (v === "typescript" || v === "ts") skillType = "typescript";
3056
+ }
3057
+ }
3058
+ if (skillType === "markdown") {
3059
+ skillType = detectTypeFromContent(body, filename);
3060
+ }
3061
+ return { content: body, name, description, config, skillType };
3062
+ }
3063
+ function serializeSkill(input) {
3064
+ const lines = ["---", `name: ${input.name}`, `description: ${input.description}`];
3065
+ if (input.type && input.type !== "markdown") {
3066
+ lines.push(`type: ${input.type}`);
3067
+ }
3068
+ lines.push("---", "", input.body.trim(), "");
3069
+ return lines.join("\n");
3070
+ }
3071
+ var SKILL_FOLDER_PREFIX = "skills/";
3072
+ function stripExt(name) {
3073
+ const i = name.lastIndexOf(".");
3074
+ return i > 0 ? name.slice(0, i) : name;
3075
+ }
3076
+ function isJunkPath(path) {
3077
+ if (path.endsWith("/")) return true;
3078
+ const parts = path.split("/");
3079
+ if (parts.some((p) => p === "__MACOSX")) return true;
3080
+ return parts[parts.length - 1].startsWith(".");
3081
+ }
3082
+ function folderFor(slug, relDir) {
3083
+ const root = `${SKILL_FOLDER_PREFIX}${slug}`;
3084
+ return relDir ? `${root}/${relDir}` : root;
3085
+ }
3086
+ function importMarkdownSkill(fileName, text) {
3087
+ const parsed = parseSkillFrontmatter(text, fileName);
3088
+ const slug = skillSlug(parsed.name) || skillSlug(stripExt(fileName));
3089
+ if (!slug) {
3090
+ throw new Error(
3091
+ "Could not derive a skill name. Add a `name:` to the frontmatter or rename the file."
3092
+ );
3093
+ }
3094
+ return {
3095
+ slug,
3096
+ entries: [{ folder: folderFor(slug, ""), fileName: SKILL_MAIN_FILENAME, content: text }]
3097
+ };
3098
+ }
3099
+ function commonTopDir(paths) {
3100
+ const tops = /* @__PURE__ */ new Set();
3101
+ for (const p of paths) {
3102
+ const idx = p.indexOf("/");
3103
+ if (idx === -1) return null;
3104
+ tops.add(p.slice(0, idx));
3105
+ }
3106
+ return tops.size === 1 ? [...tops][0] : null;
3107
+ }
3108
+ function buildZipSkillImport(archiveName, rawEntries) {
3109
+ const entries = rawEntries.filter((e) => e.path && !isJunkPath(e.path));
3110
+ if (entries.length === 0) throw new Error("The archive contains no importable skill files.");
3111
+ const top = commonTopDir(entries.map((e) => e.path));
3112
+ const rel = (p) => top ? p.slice(top.length + 1) : p;
3113
+ let slug = top ? skillSlug(top) : "";
3114
+ if (!slug) {
3115
+ const mainGuess = entries.find((e) => {
3116
+ const r = rel(e.path);
3117
+ return !r.includes("/") && isMainSkillFile(r, "__never__");
3118
+ }) ?? entries.find((e) => !rel(e.path).includes("/"));
3119
+ if (mainGuess) slug = skillSlug(parseSkillFrontmatter(mainGuess.text, rel(mainGuess.path)).name);
3120
+ }
3121
+ if (!slug) slug = skillSlug(stripExt(archiveName));
3122
+ if (!slug) throw new Error("Could not derive a skill name from the archive.");
3123
+ const rootFiles = entries.filter((e) => !rel(e.path).includes("/"));
3124
+ const mainEntry = rootFiles.find((e) => isMainSkillFile(rel(e.path), slug)) ?? (rootFiles.length === 1 ? rootFiles[0] : void 0);
3125
+ return {
3126
+ slug,
3127
+ entries: entries.map((e) => {
3128
+ const r = rel(e.path);
3129
+ const lastSlash = r.lastIndexOf("/");
3130
+ return {
3131
+ folder: folderFor(slug, lastSlash === -1 ? "" : r.slice(0, lastSlash)),
3132
+ fileName: mainEntry && e.path === mainEntry.path ? SKILL_MAIN_FILENAME : lastSlash === -1 ? r : r.slice(lastSlash + 1),
3133
+ content: e.text
3134
+ };
3135
+ })
3136
+ };
3137
+ }
3138
+ async function readSkillArchive(file) {
3139
+ const data = new Uint8Array(await file.arrayBuffer());
3140
+ let files;
3141
+ try {
3142
+ files = await new Promise(
3143
+ (resolve, reject) => unzip(
3144
+ data,
3145
+ { filter: (f) => !isJunkPath(f.name) },
3146
+ (err, out) => err ? reject(err) : resolve(out)
3147
+ )
3148
+ );
3149
+ } catch {
3150
+ throw new Error("The archive is empty or could not be read.");
3151
+ }
3152
+ const entries = Object.entries(files).map(([path, bytes]) => ({ path, text: strFromU8(bytes) }));
3153
+ if (entries.length === 0) throw new Error("The archive is empty or could not be read.");
3154
+ return entries;
3155
+ }
3156
+ async function importSkillFile(file) {
3157
+ const lower = file.name.toLowerCase();
3158
+ if (lower.endsWith(".zip")) return buildZipSkillImport(file.name, await readSkillArchive(file));
3159
+ if (lower.endsWith(".md") || lower.endsWith(".markdown")) {
3160
+ return importMarkdownSkill(file.name, await file.text());
3161
+ }
3162
+ throw new Error("Unsupported file type. Choose a .md skill or a .zip archive.");
3163
+ }
3164
+ async function importSkillFromUrl(url) {
3165
+ let parsed;
3166
+ try {
3167
+ parsed = new URL(url.trim());
3168
+ } catch {
3169
+ throw new Error("That does not look like a link.");
3170
+ }
3171
+ const response = await fetch(parsed.toString());
3172
+ if (!response.ok) throw new Error(`Could not open the link (${response.status}).`);
3173
+ const text = await response.text();
3174
+ const last = parsed.pathname.split("/").filter(Boolean).pop() ?? "skill";
3175
+ return importMarkdownSkill(last.includes(".") ? last : `${last}.md`, text);
3176
+ }
3177
+ function PersonPill({ person, size = "sm", testId }) {
3178
+ return /* @__PURE__ */ jsx("span", { "data-testid": testId, className: "inline-flex min-w-0", children: /* @__PURE__ */ jsx(
3179
+ UserEntity,
3180
+ {
3181
+ variant: "pill",
3182
+ size,
3183
+ interactive: false,
3184
+ showTooltip: false,
3185
+ initials: initials(person.label),
3186
+ displayName: person.label,
3187
+ avatarStyle: person.id ? getEntityColorStyles(person.id) : void 0,
3188
+ isAgent: person.isAgent,
3189
+ isMe: person.isMe
3190
+ }
3191
+ ) });
3192
+ }
3193
+ var DEFAULT_TASK_TRIGGER_CHIP_LABELS = {
3194
+ after: (name) => `After \u201C${name}\u201D is done`,
3195
+ afterUnknown: "After another task is done",
3196
+ onDemand: "Manual"
3197
+ };
3198
+ function TriggerIcon({ type, chained }) {
3199
+ const cls = "size-3.5 shrink-0 text-muted-foreground";
3200
+ if (chained) return /* @__PURE__ */ jsx(Link2, { className: cls, "aria-hidden": true });
3201
+ switch (type) {
3202
+ case "on_demand":
3203
+ return /* @__PURE__ */ jsx(MousePointerClick, { className: cls, "aria-hidden": true });
3204
+ case "recurring":
3205
+ return /* @__PURE__ */ jsx(Clock, { className: cls, "aria-hidden": true });
3206
+ case "event":
3207
+ return /* @__PURE__ */ jsx(Bell, { className: cls, "aria-hidden": true });
3208
+ default:
3209
+ return /* @__PURE__ */ jsx(Clock, { className: cls, "aria-hidden": true });
3210
+ }
3211
+ }
3212
+ function TaskTriggerChip({
3213
+ id,
3214
+ triggerType,
3215
+ summary,
3216
+ afterTaskId,
3217
+ afterTaskName,
3218
+ testId,
3219
+ labels
3220
+ }) {
3221
+ const l = { ...DEFAULT_TASK_TRIGGER_CHIP_LABELS, ...labels };
3222
+ const chained = Boolean(afterTaskId);
3223
+ const text = chained ? afterTaskName ? l.after(afterTaskName) : l.afterUnknown : triggerType === "on_demand" ? l.onDemand : summary;
3224
+ return /* @__PURE__ */ jsxs(
3225
+ "span",
3226
+ {
3227
+ className: "inline-flex min-w-0 max-w-full items-center gap-1.5 text-sm font-normal text-foreground",
3228
+ "data-testid": testIdOr(testId, { testId: `task-trigger-${id}` }),
3229
+ "data-trigger": chained ? "task" : triggerType,
3230
+ children: [
3231
+ /* @__PURE__ */ jsx(TriggerIcon, { type: triggerType, chained }),
3232
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: text })
3233
+ ]
3234
+ }
3235
+ );
3236
+ }
3237
+
3238
+ // src/agents/tasks/workflowRows.ts
3239
+ var isSkillRow = (row) => row.kind === "skill";
3240
+ var hasTrigger = (row) => !row.on_demand && Boolean(row.rrule || row.run_at || row.event_kind || row.after_conversation_ext_id);
3241
+ function workflowStatus(row) {
3242
+ if (isSkillRow(row) || !hasTrigger(row)) return "on_demand";
3243
+ return row.active ? "active" : "paused";
3244
+ }
3245
+ function workflowTriggerType(workflow) {
3246
+ if (workflow.on_demand) return "on_demand";
3247
+ if (workflow.after_conversation_ext_id) return "after";
3248
+ if (workflow.event_kind) return "event";
3249
+ if (workflow.rrule) return "recurring";
3250
+ if (workflow.run_at) return "one_time";
3251
+ return "on_demand";
3252
+ }
3253
+ function workflowRunStatusLabel(status) {
3254
+ if (!status) return "None";
3255
+ const labels = {
3256
+ completed: "Success",
3257
+ success: "Success",
3258
+ failed: "Failed",
3259
+ running: "In progress",
3260
+ awaiting_authorization: "Awaiting access"
3261
+ };
3262
+ return labels[status] ?? status.replaceAll("_", " ");
3263
+ }
3264
+ function listedRunStatus(status, threadStatus) {
3265
+ if (threadStatus === "done" && status === "running") return "completed";
3266
+ return status;
3267
+ }
3268
+
3269
+ // src/agents/tasks/workflowColumns.tsx
3270
+ var WORKFLOW_COLUMN_ORDER = [
3271
+ "name",
3272
+ "trigger",
3273
+ "last_run",
3274
+ "result",
3275
+ "status",
3276
+ "shared",
3277
+ "actions"
3278
+ ];
3279
+ function cell(render) {
3280
+ return (params) => params.data ? render(params.data) : null;
3281
+ }
3282
+ var time = (iso) => iso ? Date.parse(iso) : Number.POSITIVE_INFINITY;
3283
+ function createWorkflowColumns(o) {
3284
+ const l = o.labels;
3285
+ const base = {
3286
+ sortable: false,
3287
+ cellStyle: { display: "flex", alignItems: "center" }
3288
+ };
3289
+ return [
3290
+ {
3291
+ ...base,
3292
+ colId: "name",
3293
+ sortable: true,
3294
+ headerName: l.colName,
3295
+ flex: 3,
3296
+ minWidth: 210,
3297
+ valueGetter: (p) => p.data?.name.toLowerCase() ?? "",
3298
+ cellRenderer: cell(o.renderName)
3299
+ },
3300
+ {
3301
+ ...base,
3302
+ colId: "trigger",
3303
+ headerName: l.colTrigger,
3304
+ flex: 2.4,
3305
+ minWidth: 185,
3306
+ valueGetter: (p) => p.data ? o.getTriggerText(p.data) : "",
3307
+ cellRenderer: cell(o.renderTrigger)
3308
+ },
3309
+ {
3310
+ ...base,
3311
+ colId: "last_run",
3312
+ sortable: true,
3313
+ headerName: l.colLastRun,
3314
+ flex: 1.4,
3315
+ minWidth: 140,
3316
+ cellStyle: { display: "flex", alignItems: "center", padding: 0 },
3317
+ valueGetter: (p) => time(p.data?.last_fired_at),
3318
+ cellRenderer: cell(o.renderLastRun)
3319
+ },
3320
+ {
3321
+ ...base,
3322
+ colId: "result",
3323
+ headerName: "Result",
3324
+ width: 130,
3325
+ valueGetter: (p) => p.data?.last_run_status ?? "",
3326
+ cellStyle: { display: "flex", alignItems: "center", padding: 0 },
3327
+ cellRenderer: cell(o.renderResult)
3328
+ },
3329
+ {
3330
+ ...base,
3331
+ colId: "status",
3332
+ sortable: true,
3333
+ headerName: l.colStatus,
3334
+ width: 120,
3335
+ valueGetter: (p) => p.data ? workflowStatus(p.data) : "",
3336
+ cellRenderer: cell(o.renderStatus)
3337
+ },
3338
+ {
3339
+ ...base,
3340
+ colId: "shared",
3341
+ headerName: l.colShared,
3342
+ headerTooltip: l.shared,
3343
+ width: 96,
3344
+ cellStyle: { display: "flex", alignItems: "center", justifyContent: "center" },
3345
+ valueGetter: (p) => p.data?.shared ?? false,
3346
+ cellRenderer: cell(o.renderShared)
3347
+ },
3348
+ {
3349
+ ...base,
3350
+ colId: "actions",
3351
+ headerName: "",
3352
+ width: 180,
3353
+ minWidth: 180,
3354
+ cellStyle: { display: "flex", alignItems: "center", justifyContent: "flex-end" },
3355
+ cellRenderer: cell(o.renderActions)
3356
+ }
3357
+ ];
3358
+ }
3359
+
3360
+ // src/agents/tasks/workflowTableLabels.ts
3361
+ var DEFAULT_WORKFLOW_LIST_LABELS = {
3362
+ colName: "Name",
3363
+ colType: "Type",
3364
+ colTrigger: "When",
3365
+ colAction: "What it does",
3366
+ colAgent: "Agent",
3367
+ colStatus: "Active",
3368
+ colShared: "Shared",
3369
+ colLastRun: "Last run",
3370
+ colNextRun: "Next run",
3371
+ shared: "Shared with the workspace",
3372
+ sharedOwnerOnly: "Only the creator can change who can see this",
3373
+ deleteOwnerOnly: "Only the creator or workspace owner can delete this",
3374
+ typeSkill: "Skill",
3375
+ typeWorkflow: "Workflow",
3376
+ statusActive: "Active",
3377
+ statusPaused: "Inactive",
3378
+ statusOnDemand: "On demand",
3379
+ activeSwitch: (name) => `Active: \u201C${name}\u201D`,
3380
+ agent: "Agent",
3381
+ myAssistant: "My assistant",
3382
+ me: "Me",
3383
+ someone: "Someone",
3384
+ never: "Never",
3385
+ none: "\u2014",
3386
+ reminder: "Reminder",
3387
+ failed: "Failed",
3388
+ lastRunFailed: "Last run failed",
3389
+ openLastRun: "Open the task this run created",
3390
+ editWorkflow: "Edit",
3391
+ run: "Run",
3392
+ runLater: "Run later",
3393
+ runNow: "Run now",
3394
+ runs: "Runs",
3395
+ delete: "Delete",
3396
+ moreActions: "More actions",
3397
+ searchLabel: "Search workflows",
3398
+ searchPlaceholder: "Search workflows and skills\u2026",
3399
+ emptyTitle: "Nothing to run yet",
3400
+ emptyMessage: "Skills and workflows you can run will appear here.",
3401
+ firstRun: "Run a skill now, or choose Run later to save it as a workflow that runs on its own.",
3402
+ noMatches: "Nothing in this view.",
3403
+ filterAll: "All",
3404
+ filterSkills: "Skills",
3405
+ filterWorkflows: "Workflows",
3406
+ filterActive: "Active",
3407
+ filterPaused: "Inactive",
3408
+ makeActive: "Make active",
3409
+ makePaused: "Deactivate",
3410
+ selectAll: "Select all",
3411
+ selectWorkflow: (name) => `Select ${name}`
3412
+ };
3413
+ var ROW_HEIGHT = 40;
3414
+ function WorkflowsTable(props) {
3415
+ const { workflows, isLoading, busyId, header, testId } = props;
3416
+ const l = { ...DEFAULT_WORKFLOW_LIST_LABELS, ...props.labels };
3417
+ const [filterStates, setFilterStates] = useState(/* @__PURE__ */ new Map());
3418
+ const [filterOpen, setFilterOpen] = useState(false);
3419
+ const [query, setQuery] = useState("");
3420
+ const [selected, setSelected] = useState([]);
3421
+ const apiRef = useRef(null);
3422
+ const q = query.trim().toLowerCase();
3423
+ const matches = (w) => !q || `${w.name} ${w.description ?? ""} ${w.prompt ?? ""} ${w.skill_ref ?? ""}`.toLowerCase().includes(q);
3424
+ const inFilter = (w, f) => f === "all" || f === "skills" && isSkillRow(w) || f === "workflows" && !isSkillRow(w) || f === "active" && workflowStatus(w) === "active" || f === "paused" && workflowStatus(w) === "paused";
3425
+ const counts = useMemo(() => {
3426
+ const out = { all: 0, skills: 0, workflows: 0, active: 0, paused: 0 };
3427
+ for (const w of workflows) {
3428
+ if (!matches(w)) continue;
3429
+ for (const f of Object.keys(out)) if (inFilter(w, f)) out[f] += 1;
3430
+ }
3431
+ return out;
3432
+ }, [workflows, q]);
3433
+ const shown = useMemo(
3434
+ () => workflows.filter((w) => {
3435
+ if (!matches(w)) return false;
3436
+ for (const group of [
3437
+ ["skills", "workflows"],
3438
+ ["active", "paused"]
3439
+ ]) {
3440
+ const included = group.filter((f) => filterStates.get(f) === "include");
3441
+ if (included.length && !included.some((f) => inFilter(w, f))) return false;
3442
+ if (group.some((f) => filterStates.get(f) === "exclude" && inFilter(w, f))) return false;
3443
+ }
3444
+ return true;
3445
+ }),
3446
+ [workflows, filterStates, q]
3447
+ );
3448
+ const renderPerson = props.renderPerson ?? ((p) => /* @__PURE__ */ jsx(PersonPill, { person: p }));
3449
+ const actionText = (w) => {
3450
+ if (w.action)
3451
+ return w.action.type === "email_documents" ? "Email latest documents" : "AI Review";
3452
+ if (isSkillRow(w))
3453
+ return w.description || w.skill_ref && props.skillNames?.[w.skill_ref] || w.name;
3454
+ return w.skill_ref ? props.skillNames?.[w.skill_ref] ?? w.skill_ref : w.prompt?.trim() || l.reminder;
3455
+ };
3456
+ const latest = useRef({ props, l, renderPerson, actionText });
3457
+ latest.current = { props, l, renderPerson, actionText };
3458
+ const columnDefs = useMemo(
3459
+ () => createWorkflowColumns({
3460
+ labels: l,
3461
+ getTriggerText: (w) => isSkillRow(w) ? l.statusOnDemand : latest.current.props.describeTrigger(w),
3462
+ renderName: (w) => {
3463
+ const { l: lb } = latest.current;
3464
+ const skill = isSkillRow(w);
3465
+ const text = skill ? latest.current.actionText(w) : w.description || latest.current.actionText(w);
3466
+ const stem = w.name.replace(/…$/, "").trim();
3467
+ const about = text && text.trim() !== w.name.trim() && text.trim() !== stem ? text : "";
3468
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-0 leading-tight", children: [
3469
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3470
+ /* @__PURE__ */ jsx(
3471
+ "span",
3472
+ {
3473
+ className: cn(
3474
+ "truncate text-sm font-normal",
3475
+ workflowStatus(w) === "paused" && "text-muted-foreground"
3476
+ ),
3477
+ "data-testid": `workflow-name-${w.external_id}`,
3478
+ children: w.name
3479
+ }
3480
+ ),
3481
+ skill && /* @__PURE__ */ jsx(
3482
+ "span",
3483
+ {
3484
+ className: "shrink-0 rounded-full border border-border px-1.5 text-[11px] text-muted-foreground",
3485
+ "data-testid": `workflow-type-${w.external_id}`,
3486
+ children: lb.typeSkill
3487
+ }
3488
+ )
3489
+ ] }),
3490
+ about && !latest.current.props.automationsOnly && /* @__PURE__ */ jsx(
3491
+ "p",
3492
+ {
3493
+ className: "truncate text-xs text-muted-foreground",
3494
+ "data-testid": `workflow-action-${w.external_id}`,
3495
+ children: about
3496
+ }
3497
+ )
3498
+ ] });
3499
+ },
3500
+ renderTrigger: (w) => {
3501
+ const { props: p } = latest.current;
3502
+ const after = w.after_conversation_ext_id;
3503
+ const skill = isSkillRow(w);
3504
+ return /* @__PURE__ */ jsx("div", { className: "min-w-0 leading-tight", children: /* @__PURE__ */ jsx(
3505
+ TaskTriggerChip,
3506
+ {
3507
+ id: w.external_id,
3508
+ testId: `workflow-trigger-${w.external_id}`,
3509
+ triggerType: workflowTriggerType(w),
3510
+ summary: skill ? "" : p.describeTrigger(w),
3511
+ afterTaskId: after,
3512
+ afterTaskName: after ? p.taskNames?.[after] : void 0,
3513
+ labels: p.triggerChipLabels
3514
+ }
3515
+ ) });
3516
+ },
3517
+ renderStatus: (w) => {
3518
+ const { props: p, l: lb } = latest.current;
3519
+ const canEdit = w.is_owner !== false || Boolean(p.canEditShared);
3520
+ return /* @__PURE__ */ jsx(
3521
+ Switch,
3522
+ {
3523
+ checked: hasTrigger(w) && !isSkillRow(w) && Boolean(w.active),
3524
+ disabled: !hasTrigger(w) || isSkillRow(w) || !canEdit || p.busyId === w.external_id,
3525
+ onCheckedChange: (next) => p.onToggleActive(w, next),
3526
+ className: "disabled:data-[state=unchecked]:bg-muted dark:disabled:data-[state=unchecked]:bg-muted",
3527
+ "aria-label": lb.activeSwitch(w.name),
3528
+ "data-testid": `workflow-active-${w.external_id}`
3529
+ }
3530
+ );
3531
+ },
3532
+ renderShared: (w) => {
3533
+ const { props: p, l: lb } = latest.current;
3534
+ if (!p.onToggleShared || isSkillRow(w)) return null;
3535
+ const isCreator = w.is_owner !== false;
3536
+ return /* @__PURE__ */ jsx(
3537
+ VisibilityToggle,
3538
+ {
3539
+ items: { shared: w.shared ?? false },
3540
+ onToggle: (share) => latest.current.props.onToggleShared?.(w, share),
3541
+ disabled: !isCreator || p.busyId === w.external_id,
3542
+ disabledTooltip: lb.sharedOwnerOnly,
3543
+ showLabel: false,
3544
+ "data-testid": `workflow-shared-${w.external_id}`
3545
+ }
3546
+ );
3547
+ },
3548
+ renderLastRun: (w) => {
3549
+ const { props: p, l: lb } = latest.current;
3550
+ const at = w.last_fired_at ?? p.lastRuns?.[w.external_id]?.at;
3551
+ return /* @__PURE__ */ jsx(
3552
+ "button",
3553
+ {
3554
+ type: "button",
3555
+ className: "h-full w-full truncate px-4 text-left text-sm hover:text-primary",
3556
+ onClick: () => p.onViewRuns?.(w),
3557
+ title: "View run history",
3558
+ "data-testid": `workflow-last-run-${w.external_id}`,
3559
+ children: at ? p.formatDate(at) : lb.never
3560
+ }
3561
+ );
3562
+ },
3563
+ renderResult: (w) => {
3564
+ const status = listedRunStatus(
3565
+ w.last_run_status,
3566
+ latest.current.props.lastRuns?.[w.external_id]?.threadStatus
3567
+ );
3568
+ const label = workflowRunStatusLabel(status);
3569
+ return /* @__PURE__ */ jsx(
3570
+ "button",
3571
+ {
3572
+ type: "button",
3573
+ onClick: () => latest.current.props.onViewRuns?.(w),
3574
+ title: "View run history",
3575
+ className: cn(
3576
+ badgeVariants({ variant: "outline" }),
3577
+ "mx-4 cursor-pointer text-xs font-medium",
3578
+ status === "failed" ? "border-red-border bg-red-bg text-red-text hover:bg-red-bg/80" : status === "completed" || status === "success" ? "border-green-border bg-green-bg text-green-text hover:bg-green-bg/80" : status ? "border-primary/20 bg-primary/10 text-primary hover:bg-primary/15" : "border-border bg-muted text-muted-foreground hover:bg-muted/80"
3579
+ ),
3580
+ "data-testid": `workflow-result-${w.external_id}`,
3581
+ children: label
3582
+ }
3583
+ );
3584
+ },
3585
+ renderActions: (w) => {
3586
+ const { props: p, l: lb } = latest.current;
3587
+ const id = w.external_id;
3588
+ const skill = isSkillRow(w);
3589
+ const busy = p.busyId === id;
3590
+ const isOwner = w.is_owner !== false;
3591
+ const canEdit = isOwner || Boolean(p.canEditShared);
3592
+ const canAdmin = isOwner || Boolean(p.isWorkspaceOwner);
3593
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
3594
+ (skill || canEdit) && /* @__PURE__ */ jsxs(
3595
+ Button,
3596
+ {
3597
+ size: "sm",
3598
+ variant: "outline",
3599
+ className: "h-8 gap-1.5 pointer-coarse:h-11",
3600
+ disabled: busy,
3601
+ onClick: () => p.onRun(w),
3602
+ "data-testid": `workflow-run-btn-${id}`,
3603
+ children: [
3604
+ /* @__PURE__ */ jsx(Play, { className: "size-3.5", "aria-hidden": true }),
3605
+ lb.run
3606
+ ]
3607
+ }
3608
+ ),
3609
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
3610
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
3611
+ Button,
3612
+ {
3613
+ variant: "ghost",
3614
+ size: "icon",
3615
+ className: "size-8 pointer-coarse:size-11",
3616
+ "aria-label": lb.moreActions,
3617
+ "data-testid": `workflow-menu-${id}`,
3618
+ children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4", "aria-hidden": true })
3619
+ }
3620
+ ) }),
3621
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
3622
+ skill && p.onRunLater && /* @__PURE__ */ jsxs(
3623
+ DropdownMenuItem,
3624
+ {
3625
+ onSelect: () => p.onRunLater?.(w),
3626
+ "data-testid": `workflow-run-later-${id}`,
3627
+ children: [
3628
+ /* @__PURE__ */ jsx(History, { className: "mr-2 size-4", "aria-hidden": true }),
3629
+ lb.runLater
3630
+ ]
3631
+ }
3632
+ ),
3633
+ !skill && canEdit && /* @__PURE__ */ jsxs(
3634
+ DropdownMenuItem,
3635
+ {
3636
+ onSelect: () => p.onEdit(w),
3637
+ "data-testid": `workflow-edit-${id}`,
3638
+ children: [
3639
+ /* @__PURE__ */ jsx(Pencil, { className: "mr-2 size-4", "aria-hidden": true }),
3640
+ lb.editWorkflow
3641
+ ]
3642
+ }
3643
+ ),
3644
+ !skill && p.onViewRuns && /* @__PURE__ */ jsxs(
3645
+ DropdownMenuItem,
3646
+ {
3647
+ onSelect: () => p.onViewRuns?.(w),
3648
+ "data-testid": `workflow-runs-${id}`,
3649
+ children: [
3650
+ /* @__PURE__ */ jsx(History, { className: "mr-2 size-4", "aria-hidden": true }),
3651
+ lb.runs
3652
+ ]
3653
+ }
3654
+ ),
3655
+ !skill && canEdit && /* @__PURE__ */ jsxs(Fragment, { children: [
3656
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
3657
+ /* @__PURE__ */ jsxs(
3658
+ DropdownMenuItem,
3659
+ {
3660
+ disabled: busy || !canAdmin,
3661
+ onSelect: () => p.onDelete(w),
3662
+ title: canAdmin ? void 0 : lb.deleteOwnerOnly,
3663
+ className: "text-destructive focus:text-destructive",
3664
+ "data-testid": `workflow-delete-${id}`,
3665
+ children: [
3666
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 size-4", "aria-hidden": true }),
3667
+ lb.delete
3668
+ ]
3669
+ }
3670
+ )
3671
+ ] })
3672
+ ] })
3673
+ ] })
3674
+ ] });
3675
+ }
3676
+ }),
3677
+ // Stable on purpose: only the header texts rebuild the columns (see `latest`).
3678
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3679
+ [
3680
+ l.colName,
3681
+ l.colType,
3682
+ l.colTrigger,
3683
+ l.colAction,
3684
+ l.colAgent,
3685
+ l.colStatus,
3686
+ l.colShared,
3687
+ l.shared,
3688
+ l.colLastRun,
3689
+ l.colNextRun
3690
+ ]
3691
+ );
3692
+ useEffect(() => {
3693
+ apiRef.current?.refreshCells({ force: true });
3694
+ }, [
3695
+ busyId,
3696
+ props.agentNames,
3697
+ props.skillNames,
3698
+ props.taskNames,
3699
+ props.lastRuns,
3700
+ props.renderPerson,
3701
+ props.labels,
3702
+ props.canEditShared,
3703
+ props.isWorkspaceOwner
3704
+ ]);
3705
+ const mine = selected.filter((w) => !isSkillRow(w));
3706
+ const adminSelected = mine.filter((w) => w.is_owner !== false || props.isWorkspaceOwner);
3707
+ const editableSelected = mine.filter(
3708
+ (w) => hasTrigger(w) && (w.is_owner !== false || props.canEditShared)
3709
+ );
3710
+ const clearSelection = () => setSelected([]);
3711
+ return /* @__PURE__ */ jsxs(
3712
+ "div",
3713
+ {
3714
+ className: "flex min-h-0 flex-1 flex-col",
3715
+ "data-testid": testIdOr(testId, { testId: "workflow-list" }),
3716
+ children: [
3717
+ /* @__PURE__ */ jsx(
3718
+ TabHeader,
3719
+ {
3720
+ testId: "automations-tab-header",
3721
+ tabs: [
3722
+ {
3723
+ id: "automations",
3724
+ label: "",
3725
+ count: shown.length,
3726
+ countIcon: /* @__PURE__ */ jsx(Workflow, { className: "size-3.5" }),
3727
+ countPlacement: "leading",
3728
+ presentation: "count",
3729
+ hasActiveFilters: !!q || [...filterStates.values()].some(Boolean)
3730
+ },
3731
+ ...props.tabs ?? []
3732
+ ],
3733
+ activeTabId: props.activeTabId ?? "automations",
3734
+ onTabChange: props.onTabChange ?? (() => {
3735
+ }),
3736
+ firstTabContent: /* @__PURE__ */ jsx(
3737
+ FilterInput,
3738
+ {
3739
+ value: query,
3740
+ onChange: setQuery,
3741
+ fields: ["text"],
3742
+ onFieldsChange: () => {
3743
+ },
3744
+ hideFieldPicker: true,
3745
+ ariaLabel: l.searchLabel,
3746
+ testIds: { input: "workflow-search" },
3747
+ filter: /* @__PURE__ */ jsx(
3748
+ TriStateFilterMenu,
3749
+ {
3750
+ testId: "automations-filter-button",
3751
+ open: filterOpen,
3752
+ onOpenChange: setFilterOpen,
3753
+ filterStates,
3754
+ onFiltersChange: setFilterStates,
3755
+ triggerClassName: "flex h-5 items-center gap-0.5 rounded px-1 text-muted-foreground hover:text-foreground pointer-coarse:h-11! pointer-coarse:min-w-11 pointer-coarse:justify-center",
3756
+ renderChevron: true,
3757
+ disableHoverOpen: true,
3758
+ options: [
3759
+ ...!props.automationsOnly ? [
3760
+ { id: "skills", label: l.filterSkills, count: counts.skills },
3761
+ {
3762
+ id: "workflows",
3763
+ label: l.filterWorkflows,
3764
+ count: counts.workflows
3765
+ }
3766
+ ] : [],
3767
+ { id: "active", label: l.filterActive, count: counts.active },
3768
+ { id: "paused", label: l.filterPaused, count: counts.paused }
3769
+ ]
3770
+ }
3771
+ )
3772
+ }
3773
+ ),
3774
+ rightContent: header
3775
+ }
3776
+ ),
3777
+ props.content ? props.content : workflows.length === 0 && !isLoading ? /* @__PURE__ */ jsx(
3778
+ EmptyState,
3779
+ {
3780
+ icon: Workflow,
3781
+ title: l.emptyTitle,
3782
+ description: l.emptyMessage,
3783
+ testId: "workflow-list-empty"
3784
+ }
3785
+ ) : /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
3786
+ GridView,
3787
+ {
3788
+ testId: "workflow-grid",
3789
+ gridId: props.gridId ?? "workflows-grid-v2",
3790
+ items: shown,
3791
+ columnDefs,
3792
+ getItemId: (w) => w.external_id,
3793
+ getRowTestId: (w) => `workflow-row-${w.external_id}`,
3794
+ isLoading,
3795
+ showEmpty: shown.length === 0,
3796
+ emptyState: { title: l.noMatches },
3797
+ rowHeight: ROW_HEIGHT,
3798
+ height: "100%",
3799
+ onGridReady: ({ api }) => {
3800
+ apiRef.current = api;
3801
+ },
3802
+ onRowClick: (row) => props.onOpenRow?.(row),
3803
+ selection: { mode: "multiple", selected, onSelectionChange: setSelected },
3804
+ gridOptions: GRID_OPTIONS
3805
+ }
3806
+ ) }),
3807
+ !props.content && /* @__PURE__ */ jsx(SelectionBar, { count: selected.length, onClear: clearSelection, children: (isCompact) => /* @__PURE__ */ jsxs(Fragment, { children: [
3808
+ props.onToggleActiveMany && /* @__PURE__ */ jsxs(Fragment, { children: [
3809
+ /* @__PURE__ */ jsx(
3810
+ SelectionBarAction,
3811
+ {
3812
+ icon: Play,
3813
+ label: l.makeActive,
3814
+ onClick: () => {
3815
+ props.onToggleActiveMany?.(editableSelected, true);
3816
+ clearSelection();
3817
+ },
3818
+ disabled: editableSelected.length === 0,
3819
+ isCompact,
3820
+ testId: "workflow-bulk-activate"
3821
+ }
3822
+ ),
3823
+ /* @__PURE__ */ jsx(
3824
+ SelectionBarAction,
3825
+ {
3826
+ icon: Workflow,
3827
+ label: l.makePaused,
3828
+ onClick: () => {
3829
+ props.onToggleActiveMany?.(editableSelected, false);
3830
+ clearSelection();
3831
+ },
3832
+ disabled: editableSelected.length === 0,
3833
+ isCompact,
3834
+ testId: "workflow-bulk-pause"
3835
+ }
3836
+ )
3837
+ ] }),
3838
+ props.onDeleteMany && /* @__PURE__ */ jsx(
3839
+ SelectionBarAction,
3840
+ {
3841
+ icon: Trash2,
3842
+ label: l.delete,
3843
+ onClick: () => {
3844
+ props.onDeleteMany?.(adminSelected);
3845
+ clearSelection();
3846
+ },
3847
+ disabled: adminSelected.length === 0,
3848
+ isCompact,
3849
+ className: "border-red-border text-red-text hover:bg-red-bg hover:text-red-text",
3850
+ testId: "workflow-bulk-delete"
3851
+ }
3852
+ )
3853
+ ] }) })
3854
+ ]
3855
+ }
3856
+ );
3857
+ }
3858
+ var GRID_OPTIONS = { suppressClickEdit: true };
2322
3859
  var DEFAULT_TASK_ACTIVITY_LABELS = {
2323
3860
  title: "Activity",
2324
3861
  empty: "Nothing has happened yet.",
@@ -2511,6 +4048,6 @@ function TaskActivityTimeline({
2511
4048
  }) });
2512
4049
  }
2513
4050
 
2514
- export { ActionStepBody, ApprovalEvidence, AssigneeLine, DEFAULT_SCHEMA_FORM_LABELS, DEFAULT_STEP_BAR_LABELS, DEFAULT_TASK_ACTIVITY_LABELS, DEFAULT_WORKFLOW_RUNNER_LABELS, DEFAULT_WORKFLOW_STEP_PARTS_LABELS, STEP_LABEL, STEP_LABEL_CURRENT, STEP_LABEL_MAX, SchemaForm, SendBackControl, StepBarFrame, TaskActivityTimeline, WorkflowRunner, WorkflowStepBar, activeStepId, fromPlanAssignee, fromPlanDocumentRef, fromPlanSteps, isIncompleteSignal, layoutFromPlanSteps, stepWindow, taskActivityHeadline, toPlanAssignee, toPlanDocumentRef, toPlanSteps, useWorkflowView };
2515
- //# sourceMappingURL=chunk-2PNNCEPH.js.map
2516
- //# sourceMappingURL=chunk-2PNNCEPH.js.map
4051
+ export { ActionStepBody, AgentSelect, ApprovalEvidence, AssigneeLine, DEFAULT_AGENT_SELECT_LABELS, DEFAULT_SCHEMA_FORM_LABELS, DEFAULT_STEP_BAR_LABELS, DEFAULT_TASK_ACTIVITY_LABELS, DEFAULT_TASK_TRIGGER_CHIP_LABELS, DEFAULT_WORKFLOW_CONFIG_DIALOG_LABELS, DEFAULT_WORKFLOW_DOCUMENTS_FIELD_LABELS, DEFAULT_WORKFLOW_LIST_LABELS, DEFAULT_WORKFLOW_RUNNER_LABELS, DEFAULT_WORKFLOW_STEP_PARTS_LABELS, PersonPill, SKILL_MAIN_FILENAME, SKILL_TYPES, STEP_LABEL, STEP_LABEL_CURRENT, STEP_LABEL_MAX, SchemaForm, SendBackControl, StepBarFrame, TaskActivityTimeline, TaskTriggerChip, WORKFLOW_COLUMN_ORDER, WorkflowConfigDialog, WorkflowDocumentsField, WorkflowLauncher, WorkflowRunner, WorkflowStepBar, WorkflowsTable, activeStepId, buildWorkflowScopeSource, buildZipSkillImport, createWorkflowColumns, documentScopeLabels, fromPlanAssignee, fromPlanDocumentRef, fromPlanSteps, hasTrigger, importMarkdownSkill, importSkillFile, importSkillFromUrl, isIncompleteSignal, isMainSkillFile, isSkillRow, layoutFromPlanSteps, matchWorkflowScope, parseSkillFrontmatter, readSkillArchive, serializeSkill, skillSlug, stepWindow, taskActivityHeadline, toPlanAssignee, toPlanDocumentRef, toPlanSteps, useControllableState, useWorkflowView, workflowFromSkillMarkdown, workflowRunStatusLabel, workflowStatus, workflowToSkillMarkdown, workflowTriggerType };
4052
+ //# sourceMappingURL=chunk-ATJUVHVB.js.map
4053
+ //# sourceMappingURL=chunk-ATJUVHVB.js.map