@polderlabs/bizar 5.0.0 → 5.0.2

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 (97) hide show
  1. package/bizar-dash/dist/assets/{icons-CFqu2M-c.js → icons-Bo0iH9EC.js} +166 -121
  2. package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
  3. package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
  4. package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
  5. package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
  6. package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
  7. package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
  8. package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js → mobile-DD-FZrTC.js} +1 -1
  9. package/bizar-dash/dist/assets/{mobile-DHXXbn1A.js.map → mobile-DD-FZrTC.js.map} +1 -1
  10. package/bizar-dash/dist/index.html +4 -4
  11. package/bizar-dash/dist/mobile.html +3 -3
  12. package/bizar-dash/node_modules/.package-lock.json +6 -0
  13. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  14. package/bizar-dash/package-lock.json +6 -0
  15. package/bizar-dash/src/server/api.mjs +7 -0
  16. package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
  17. package/bizar-dash/src/server/memory-store.mjs +46 -0
  18. package/bizar-dash/src/server/providers-store.mjs +11 -4
  19. package/bizar-dash/src/server/routes/_shared.mjs +2 -2
  20. package/bizar-dash/src/server/routes/doctor.mjs +71 -0
  21. package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
  22. package/bizar-dash/src/server/routes/memory.mjs +3 -0
  23. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
  24. package/bizar-dash/src/server/routes/schedules.mjs +55 -0
  25. package/bizar-dash/src/server/serve-info.mjs +172 -0
  26. package/bizar-dash/src/web/App.tsx +48 -11
  27. package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
  28. package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
  29. package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
  30. package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
  31. package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
  32. package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
  33. package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
  34. package/bizar-dash/src/web/components/Topbar.tsx +11 -0
  35. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
  36. package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
  37. package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
  38. package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
  39. package/bizar-dash/src/web/lib/types.ts +134 -0
  40. package/bizar-dash/src/web/styles/chat.css +8 -5
  41. package/bizar-dash/src/web/styles/main.css +77 -2
  42. package/bizar-dash/src/web/styles/settings.css +265 -0
  43. package/bizar-dash/src/web/views/Chat.tsx +15 -1
  44. package/bizar-dash/src/web/views/Doctor.tsx +317 -0
  45. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
  46. package/bizar-dash/src/web/views/Schedules.tsx +94 -12
  47. package/bizar-dash/src/web/views/Settings.tsx +76 -46
  48. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
  49. package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
  50. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
  51. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
  52. package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
  53. package/bizar-dash/tests/autosave.test.tsx +276 -0
  54. package/bizar-dash/tests/chat-composer.test.tsx +140 -0
  55. package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
  56. package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
  57. package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
  58. package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
  59. package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
  60. package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
  61. package/bizar-dash/tests/minimax-models.test.mjs +123 -0
  62. package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
  63. package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
  64. package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
  65. package/bizar-dash/tests/settings-layout.test.tsx +129 -0
  66. package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
  67. package/bizar-dash/tests/settings-nav.test.tsx +126 -0
  68. package/cli/commands/dash.mjs +5 -1
  69. package/cli/commands/minimax.mjs +5 -2
  70. package/cli/tests/minimax-cli.test.mjs +79 -0
  71. package/config/agents/frigg.md +1 -1
  72. package/config/agents/heimdall.md +1 -1
  73. package/config/agents/mimir.md +1 -1
  74. package/config/agents/quick.md +1 -1
  75. package/config/agents/semble-search.md +1 -1
  76. package/config/agents/vor.md +1 -1
  77. package/config/opencode.json +28 -21
  78. package/config/opencode.json.template +8 -1
  79. package/package.json +1 -1
  80. package/plugins/bizar/index.ts +77 -0
  81. package/plugins/bizar/src/compaction.d.mts +48 -0
  82. package/plugins/bizar/src/compaction.mjs +192 -0
  83. package/plugins/bizar/tests/compaction.test.ts +264 -0
  84. package/templates/schedules/daily-backup.json +12 -0
  85. package/templates/schedules/daily-cleanup.json +12 -0
  86. package/templates/schedules/hourly-health-check.json +12 -0
  87. package/templates/schedules/webhook-on-push.json +13 -0
  88. package/templates/schedules/weekly-digest.json +13 -0
  89. package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +0 -1
  90. package/bizar-dash/dist/assets/main-Dl8yY5_H.js +0 -16
  91. package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +0 -1
  92. package/bizar-dash/dist/assets/main-ZAfGKENE.css +0 -1
  93. package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +0 -2
  94. package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +0 -1
  95. package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
  96. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
  97. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
@@ -38,6 +38,8 @@ import { Memory } from './views/Memory';
38
38
  import { History } from './views/History';
39
39
  import { MiniMaxUsage } from './views/MiniMaxUsage';
40
40
  import { BackgroundAgents } from './views/BackgroundAgents';
41
+ import { Doctor } from './views/Doctor';
42
+ import { SettingsNav } from './components/SettingsNav';
41
43
  import { Spinner } from './components/Spinner';
42
44
  import { Button } from './components/Button';
43
45
  import { AlertTriangle, Globe, LayoutTemplate, X } from 'lucide-react';
@@ -49,6 +51,17 @@ type ViewProps = {
49
51
  activeTab: string;
50
52
  setActiveTab: (id: string) => void;
51
53
  refreshSnapshot: () => Promise<void>;
54
+ /**
55
+ * v4.9.0 — When true, the sidebar shows the full settings navigation
56
+ * instead of the normal tab rail. Toggled by clicking the Settings tab.
57
+ */
58
+ settingsMode: boolean;
59
+ /**
60
+ * v4.9.0 — The active settings section highlighted in the sidebar nav.
61
+ * Null means "show all". Managed at App level so Sidebar can read it.
62
+ */
63
+ settingsActiveSection: string | null;
64
+ setSettingsActiveSection: (id: string | null) => void;
52
65
  /**
53
66
  * v3.3.0 — Cross-view scratch state. The "Open in chat" button
54
67
  * on the Agents tab sets `pendingInitialAgent`, which the Chat
@@ -79,6 +92,10 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
79
92
  skills: Skills,
80
93
  history: History,
81
94
  minimax: MiniMaxUsage,
95
+ // v6.0.0 — Doctor page (system health + diagnostics surface).
96
+ // Registered here so the tab id 'doctor' from Topbar.tsx TABS
97
+ // resolves to a real view instead of falling back to Overview.
98
+ doctor: Doctor,
82
99
  };
83
100
 
84
101
  const VERSION = 'v4.5.0';
@@ -196,6 +213,11 @@ function Shell() {
196
213
  const [settings, setSettings] = useState<Settings | null>(null);
197
214
  const [wsStatus, setWsStatus] = useState<WsStatus>('connecting');
198
215
  const [bootError, setBootError] = useState<string | null>(null);
216
+ // v4.9.0 — Settings mode: when true, sidebar shows all settings sections
217
+ const [settingsMode, setSettingsMode] = useState(false);
218
+ // v4.9.0 — The active settings section shown in the sidebar nav.
219
+ // Null means "show all sections". Managed here so Sidebar can read it.
220
+ const [settingsActiveSection, setSettingsActiveSection] = useState<string | null>(null);
199
221
  const [authPromptOpen, setAuthPromptOpen] = useState(false);
200
222
  const [searchOpen, setSearchOpen] = useState(false);
201
223
  const [stuckAgents, setStuckAgents] = useState<{ name: string }[]>([]);
@@ -592,7 +614,7 @@ function Shell() {
592
614
  const id = map[e.key];
593
615
  if (id) {
594
616
  e.preventDefault();
595
- setActiveTab(id);
617
+ handleTabChange(id);
596
618
  }
597
619
  };
598
620
  document.addEventListener('keydown', handler);
@@ -611,6 +633,14 @@ function Shell() {
611
633
  [toast],
612
634
  );
613
635
 
636
+ // v4.9.0 — Settings mode: entering settings tab activates the sidebar layout.
637
+ // Navigating to any other tab exits settings mode.
638
+ const handleTabChange = useCallback((id: string) => {
639
+ setActiveTab(id);
640
+ if (id === 'settings') setSettingsMode(true);
641
+ else setSettingsMode(false);
642
+ }, []);
643
+
614
644
  // v3.20.3 — Merge built-in tabs with installed mod views. Mod views
615
645
  // are appended after the built-in tabs and use the same rendering
616
646
  // surface; their tab ids are mod view ids (e.g. 'graphify:web').
@@ -634,13 +664,16 @@ function Shell() {
634
664
  snapshot,
635
665
  settings,
636
666
  activeTab,
637
- setActiveTab: setActiveTab,
667
+ setActiveTab: handleTabChange,
638
668
  refreshSnapshot,
669
+ settingsMode,
670
+ settingsActiveSection,
671
+ setSettingsActiveSection,
639
672
  };
640
673
  return renderActiveView(activeTab, viewProps, modViews, modViewsReloadKey);
641
674
  }
642
675
  return null;
643
- }, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot]);
676
+ }, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot, settingsMode, settingsActiveSection, setSettingsActiveSection, handleTabChange]);
644
677
 
645
678
  const refreshProjects = async () => {
646
679
  try {
@@ -671,10 +704,10 @@ function Shell() {
671
704
 
672
705
  const onSearchSelect = (r: SearchResult) => {
673
706
  const t = r.type;
674
- if (t === 'agent') setActiveTab('agents');
675
- else if (t === 'task') setActiveTab('tasks');
676
- else if (t === 'mod') setActiveTab('mods');
677
- else if (t === 'schedule') setActiveTab('schedules');
707
+ if (t === 'agent') handleTabChange('agents');
708
+ else if (t === 'task') handleTabChange('tasks');
709
+ else if (t === 'mod') handleTabChange('mods');
710
+ else if (t === 'schedule') handleTabChange('schedules');
678
711
  else if (t === 'project') {
679
712
  const id = (r.item as ProjectRecord).id;
680
713
  onActivateProject(id);
@@ -686,7 +719,7 @@ function Shell() {
686
719
  const settingId = (r.item as { id?: string; path?: string }).id
687
720
  || (r.item as { path?: string }).path
688
721
  || '';
689
- setActiveTab('settings');
722
+ handleTabChange('settings');
690
723
  // Defer the scroll to allow the view to mount, then apply a brief
691
724
  // CSS highlight. We poll a few times because the Settings view
692
725
  // mounts lazily after tab switch.
@@ -725,7 +758,7 @@ function Shell() {
725
758
  {showHeader && (
726
759
  <Topbar
727
760
  activeTab={activeTab}
728
- onTabChange={setActiveTab}
761
+ onTabChange={handleTabChange}
729
762
  wsStatus={wsStatus}
730
763
  version={VERSION}
731
764
  activeProject={snapshot?.activeProject || null}
@@ -752,7 +785,7 @@ function Shell() {
752
785
  variant="secondary"
753
786
  size="sm"
754
787
  onClick={() => {
755
- setActiveTab('agents');
788
+ handleTabChange('agents');
756
789
  setStuckBannerDismissed(true);
757
790
  }}
758
791
  >
@@ -773,7 +806,11 @@ function Shell() {
773
806
  <Sidebar
774
807
  tabs={mergedTabs}
775
808
  activeTab={activeTab}
776
- onTabChange={setActiveTab}
809
+ onTabChange={handleTabChange}
810
+ settingsMode={settingsMode}
811
+ settingsActiveSection={settingsActiveSection}
812
+ onSettingsSectionChange={setSettingsActiveSection}
813
+ onExitSettings={() => handleTabChange('overview')}
777
814
  />
778
815
  )}
779
816
  <main className="content" id="main-content" tabIndex={-1}>
@@ -0,0 +1,50 @@
1
+ // src/web/components/AutosaveField.tsx
2
+ // Generic auto-saving field wrapper with inline save status animation.
3
+
4
+ import React from 'react';
5
+ import { Check, Loader, AlertCircle } from 'lucide-react';
6
+ import { useAutosave } from '../hooks/useAutosave';
7
+
8
+ export type { AutosaveStatus } from '../hooks/useAutosave';
9
+
10
+ export function AutosaveField<T = string>({
11
+ initialValue,
12
+ saveFn,
13
+ delay = 800,
14
+ render,
15
+ className,
16
+ }: {
17
+ initialValue: T;
18
+ saveFn: (value: T) => Promise<void>;
19
+ delay?: number;
20
+ render: (props: {
21
+ value: T;
22
+ onChange: (v: T) => void;
23
+ onBlur: () => void;
24
+ }) => React.ReactNode;
25
+ className?: string;
26
+ }) {
27
+ const { value, setValue, status, save, clearDebounce, hasPendingDebounceRef, pendingValueRef } = useAutosave(initialValue, saveFn, { delay });
28
+
29
+ const handleBlur = React.useCallback(() => {
30
+ // If a debounce is pending (user was actively typing), clear the timeout
31
+ // and save immediately — the user is done typing.
32
+ if (hasPendingDebounceRef.current) {
33
+ clearDebounce();
34
+ // pendingValueRef.current holds the typed value not yet saved by debounce
35
+ if (pendingValueRef.current !== null) save(pendingValueRef.current);
36
+ }
37
+ // If no debounce is pending: do nothing (value was already saved or unchanged).
38
+ }, [clearDebounce, hasPendingDebounceRef, save, pendingValueRef]);
39
+
40
+ return (
41
+ <div className={`autosave-field ${className || ''} ${status}`}>
42
+ {render({ value, onChange: setValue, onBlur: handleBlur })}
43
+ <span className="autosave-status" role="status" aria-live="polite">
44
+ {status === 'saving' && <Loader className="spinning" size={12} />}
45
+ {status === 'saved' && <Check size={12} />}
46
+ {status === 'error' && <AlertCircle size={12} />}
47
+ </span>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,160 @@
1
+ // src/components/DoctorPanel.tsx — v6.0.0 reusable Doctor panel.
2
+ //
3
+ // A Card-wrapped section of the Doctor page that renders a list of
4
+ // `DoctorCheck` rows with status pills, plus an optional info list
5
+ // (e.g. counts) below the checks. Each `category` carries a default
6
+ // icon and title so callers only need to pass the data.
7
+
8
+ import React from 'react';
9
+ import {
10
+ Activity,
11
+ AlertOctagon,
12
+ CheckCircle2,
13
+ CircleDot,
14
+ type LucideIcon,
15
+ Server,
16
+ Settings2,
17
+ Wrench,
18
+ Zap,
19
+ } from 'lucide-react';
20
+ import { Card, CardTitle, CardMeta } from './Card';
21
+ import { StatusBadge } from './StatusBadge';
22
+ import { EmptyState } from './EmptyState';
23
+ import { cn } from '../lib/utils';
24
+ import type {
25
+ DoctorCategory,
26
+ DoctorCheck,
27
+ DoctorPanelProps,
28
+ } from '../lib/types';
29
+
30
+ const CATEGORY_DEFAULTS: Record<
31
+ DoctorCategory,
32
+ { title: string; icon: LucideIcon; emptyTitle: string; emptyMessage: string }
33
+ > = {
34
+ system: {
35
+ title: 'System health',
36
+ icon: Server,
37
+ emptyTitle: 'No system checks',
38
+ emptyMessage: 'System checks did not return any rows.',
39
+ },
40
+ services: {
41
+ title: 'Services',
42
+ icon: Zap,
43
+ emptyTitle: 'No service probes',
44
+ emptyMessage: 'Service probes did not return any rows.',
45
+ },
46
+ counts: {
47
+ title: 'Counts',
48
+ icon: Activity,
49
+ emptyTitle: 'No counts',
50
+ emptyMessage: 'No counts reported by the dashboard stores.',
51
+ },
52
+ errors: {
53
+ title: 'Recent errors',
54
+ icon: AlertOctagon,
55
+ emptyTitle: 'No recent errors',
56
+ emptyMessage: 'No error lines in the service log within the last hour.',
57
+ },
58
+ actions: {
59
+ title: 'Actions',
60
+ icon: Wrench,
61
+ emptyTitle: 'No actions',
62
+ emptyMessage: 'No actions available.',
63
+ },
64
+ };
65
+
66
+ function statusIcon(status: DoctorCheck['status']) {
67
+ if (status === 'ok') return CheckCircle2;
68
+ if (status === 'warn') return CircleDot;
69
+ return AlertOctagon;
70
+ }
71
+
72
+ function statusLabel(status: DoctorCheck['status']) {
73
+ if (status === 'ok') return 'ok';
74
+ if (status === 'warn') return 'warn';
75
+ return 'fail';
76
+ }
77
+
78
+ /**
79
+ * Reusable Doctor panel. Used by both the Doctor page and any future
80
+ * settings sub-panels that want a consistent status-list render.
81
+ *
82
+ * When `children` is supplied, the default check-list render is
83
+ * skipped — useful for the "Actions" category where the body is
84
+ * a row of buttons, not a list of checks.
85
+ */
86
+ export function DoctorPanel({
87
+ category,
88
+ title,
89
+ icon,
90
+ checks,
91
+ info,
92
+ meta,
93
+ children,
94
+ className,
95
+ }: DoctorPanelProps) {
96
+ const def = CATEGORY_DEFAULTS[category];
97
+ const Icon = icon ?? def.icon;
98
+ const heading = title ?? def.title;
99
+ const hasContent = checks.length > 0 || (info && info.length > 0) || children;
100
+ return (
101
+ <Card className={cn('doctor-panel', `doctor-panel-${category}`, className)}>
102
+ <CardTitle>
103
+ <Icon size={14} aria-hidden />
104
+ <span>{heading}</span>
105
+ {meta ? <span className="card-meta-inline muted">{meta}</span> : null}
106
+ </CardTitle>
107
+ {!hasContent ? (
108
+ <EmptyState
109
+ icon={<Icon size={28} aria-hidden />}
110
+ title={def.emptyTitle}
111
+ message={def.emptyMessage}
112
+ />
113
+ ) : (
114
+ <div className="doctor-panel-body">
115
+ {children}
116
+ {checks.length > 0 ? (
117
+ <ul className="doctor-check-list" role="list">
118
+ {checks.map((c) => {
119
+ const CheckIcon = statusIcon(c.status);
120
+ return (
121
+ <li key={c.name} className={cn('doctor-check-row', `doctor-check-${c.status}`)}>
122
+ <span className="doctor-check-icon" aria-hidden>
123
+ <CheckIcon
124
+ size={14}
125
+ className={cn(
126
+ 'doctor-check-icon-svg',
127
+ c.status === 'ok' && 'doctor-check-icon-ok',
128
+ c.status === 'warn' && 'doctor-check-icon-warn',
129
+ c.status === 'fail' && 'doctor-check-icon-fail',
130
+ )}
131
+ />
132
+ </span>
133
+ <span className="doctor-check-name mono">{c.name}</span>
134
+ <span className="doctor-check-message">{c.message}</span>
135
+ <StatusBadge kind={c.status} dot>
136
+ {statusLabel(c.status)}
137
+ </StatusBadge>
138
+ </li>
139
+ );
140
+ })}
141
+ </ul>
142
+ ) : null}
143
+ {info && info.length > 0 ? (
144
+ <dl className="doctor-info-list">
145
+ {info.map((row) => (
146
+ <React.Fragment key={row.label}>
147
+ <dt>{row.label}</dt>
148
+ <dd className="tabular-nums">{row.value}</dd>
149
+ </React.Fragment>
150
+ ))}
151
+ </dl>
152
+ ) : null}
153
+ </div>
154
+ )}
155
+ </Card>
156
+ );
157
+ }
158
+
159
+ // Re-export the Settings icon so callers can override without an extra import.
160
+ export { Settings2 };