@polderlabs/bizar 4.3.0 → 4.4.0

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 (57) hide show
  1. package/bizar-dash/src/server/opencode-sdk.mjs +72 -0
  2. package/bizar-dash/src/server/routes/background.mjs +92 -0
  3. package/bizar-dash/src/server/routes/chat.mjs +300 -123
  4. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +26 -0
  5. package/bizar-dash/src/server/routes/tasks.mjs +59 -1
  6. package/bizar-dash/src/server/task-delegator.mjs +154 -8
  7. package/bizar-dash/src/server/tasks-store.mjs +50 -2
  8. package/bizar-dash/src/web/components/background/AttachButton.tsx +96 -0
  9. package/bizar-dash/src/web/components/background/TmuxAttachCard.tsx +122 -0
  10. package/bizar-dash/src/web/components/chat/AgentNode.tsx +127 -0
  11. package/bizar-dash/src/web/components/chat/AgentTree.tsx +80 -0
  12. package/bizar-dash/src/web/components/chat/ChatComposer.tsx +76 -0
  13. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +144 -0
  14. package/bizar-dash/src/web/components/chat/ChatRail.tsx +387 -0
  15. package/bizar-dash/src/web/components/chat/ChatThread.tsx +28 -7
  16. package/bizar-dash/src/web/components/chat/JumpToLatest.tsx +58 -0
  17. package/bizar-dash/src/web/components/chat/MessageBlock.tsx +260 -0
  18. package/bizar-dash/src/web/components/chat/SessionRowMenu.tsx +206 -0
  19. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +33 -5
  20. package/bizar-dash/src/web/components/chat/_legacy.ts +30 -0
  21. package/bizar-dash/src/web/components/chat/index.ts +11 -0
  22. package/bizar-dash/src/web/components/chat/useChat.ts +345 -167
  23. package/bizar-dash/src/web/components/tasks/BacklogPanel.css +109 -0
  24. package/bizar-dash/src/web/components/tasks/BacklogPanel.tsx +209 -0
  25. package/bizar-dash/src/web/styles/chat.css +1536 -133
  26. package/bizar-dash/src/web/views/BackgroundAgents.tsx +3 -0
  27. package/bizar-dash/src/web/views/Chat.tsx +147 -57
  28. package/bizar-dash/src/web/views/Tasks.tsx +23 -1
  29. package/cli/bg.mjs +94 -71
  30. package/cli/bin.mjs +19 -7
  31. package/cli/service-controller.mjs +587 -0
  32. package/cli/service-controller.test.mjs +92 -0
  33. package/cli/service.mjs +162 -14
  34. package/config/agents/baldr.md +2 -0
  35. package/config/agents/browser-harness.md +2 -0
  36. package/config/agents/forseti.md +2 -0
  37. package/config/agents/frigg.md +2 -0
  38. package/config/agents/heimdall.md +2 -0
  39. package/config/agents/hermod.md +2 -0
  40. package/config/agents/mimir.md +2 -0
  41. package/config/agents/odin.md +2 -0
  42. package/config/agents/quick.md +2 -0
  43. package/config/agents/semble-search.md +2 -0
  44. package/config/agents/thor.md +2 -0
  45. package/config/agents/tyr.md +2 -0
  46. package/config/agents/vidarr.md +2 -0
  47. package/config/agents/vor.md +2 -0
  48. package/config/opencode.json.template +1 -0
  49. package/install.sh +448 -787
  50. package/package.json +1 -1
  51. package/packages/sdk/package.json +20 -0
  52. package/packages/sdk/src/client.ts +5 -0
  53. package/packages/sdk/src/errors.ts +11 -2
  54. package/packages/sdk/src/index.ts +19 -0
  55. package/packages/sdk/src/opencode-events.ts +134 -0
  56. package/packages/sdk/src/opencode-types.ts +66 -0
  57. package/packages/sdk/src/opencode.ts +335 -0
@@ -0,0 +1,109 @@
1
+ /* src/web/components/tasks/BacklogPanel.css — v3.22 */
2
+
3
+ .backlog-panel {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 8px;
7
+ padding: 12px 16px;
8
+ background: var(--card-bg, #1a1a2e);
9
+ border-bottom: 1px solid var(--border-color, #2d2d44);
10
+ }
11
+
12
+ .backlog-panel-header {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: space-between;
16
+ gap: 8px;
17
+ }
18
+
19
+ .backlog-panel-title {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 6px;
23
+ font-size: 13px;
24
+ font-weight: 600;
25
+ color: var(--text-secondary, #a0a0b8);
26
+ }
27
+
28
+ .backlog-panel-header-actions {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 4px;
32
+ }
33
+
34
+ .backlog-list {
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 4px;
38
+ max-height: 280px;
39
+ overflow-y: auto;
40
+ }
41
+
42
+ .backlog-item {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 4px;
46
+ padding: 8px 10px;
47
+ background: var(--bg-secondary, #16162a);
48
+ border: 1px solid var(--border-color, #2d2d44);
49
+ border-radius: 6px;
50
+ }
51
+
52
+ .backlog-item-head {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 6px;
56
+ flex-wrap: wrap;
57
+ }
58
+
59
+ .backlog-item-title {
60
+ font-size: 13px;
61
+ font-weight: 500;
62
+ color: var(--text-primary, #e0e0f0);
63
+ flex: 1;
64
+ min-width: 0;
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ white-space: nowrap;
68
+ }
69
+
70
+ .backlog-item-badge {
71
+ font-size: 10px;
72
+ padding: 1px 5px;
73
+ border-radius: 3px;
74
+ background: var(--bg-tertiary, #1e1e38);
75
+ color: var(--text-muted, #7070a0);
76
+ white-space: nowrap;
77
+ }
78
+
79
+ .backlog-item-desc {
80
+ font-size: 12px;
81
+ color: var(--text-muted, #7070a0);
82
+ line-height: 1.4;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ white-space: nowrap;
86
+ }
87
+
88
+ .backlog-item-footer {
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: space-between;
92
+ gap: 8px;
93
+ }
94
+
95
+ .backlog-item-actions {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 2px;
99
+ }
100
+
101
+ .backlog-empty {
102
+ display: flex;
103
+ flex-direction: column;
104
+ align-items: center;
105
+ gap: 6px;
106
+ padding: 20px 0;
107
+ color: var(--text-muted, #7070a0);
108
+ font-size: 13px;
109
+ }
@@ -0,0 +1,209 @@
1
+ // src/web/components/tasks/BacklogPanel.tsx — v3.22
2
+ // Backlog panel: lists parked tasks, promote individually or all-at-once.
3
+ import { useEffect, useState } from 'react';
4
+ import {
5
+ ArrowUp,
6
+ Trash2,
7
+ Sparkles,
8
+ RefreshCw,
9
+ Inbox,
10
+ } from 'lucide-react';
11
+ import { Button } from '../Button';
12
+ import { useToast } from '../Toast';
13
+ import { api } from '../../lib/api';
14
+ import { formatRelative } from '../../lib/utils';
15
+ import type { Task, Agent } from '../../lib/types';
16
+
17
+ type BacklogItemProps = {
18
+ task: Task;
19
+ agents: Agent[];
20
+ onPromote: (id: string) => void;
21
+ onDelete: (id: string) => void;
22
+ onEdit: (task: Task) => void;
23
+ onRefresh: () => Promise<void>;
24
+ };
25
+
26
+ function BacklogItem({ task, agents, onPromote, onDelete, onEdit, onRefresh }: BacklogItemProps) {
27
+ const toast = useToast();
28
+
29
+ const handlePromote = async () => {
30
+ try {
31
+ await api.post(`/tasks/${encodeURIComponent(task.id)}/promote`);
32
+ toast.success('Promoted to queued.', 1500);
33
+ onPromote(task.id);
34
+ } catch (err) {
35
+ toast.error(`Promote failed: ${(err as Error).message}`);
36
+ }
37
+ };
38
+
39
+ const handleDelete = async () => {
40
+ if (!confirm('Delete this task?')) return;
41
+ try {
42
+ await api.del(`/tasks/${encodeURIComponent(task.id)}`);
43
+ toast.success('Deleted.', 1500);
44
+ onDelete(task.id);
45
+ } catch (err) {
46
+ toast.error(`Delete failed: ${(err as Error).message}`);
47
+ }
48
+ };
49
+
50
+ const priorityDot: Record<string, string> = {
51
+ high: 'var(--warning)',
52
+ normal: 'var(--info)',
53
+ low: 'var(--muted)',
54
+ };
55
+
56
+ return (
57
+ <div className="backlog-item" data-task-id={task.id}>
58
+ <div className="backlog-item-head">
59
+ <span
60
+ className="priority-dot"
61
+ style={{ background: priorityDot[task.priority] || 'var(--info)' }}
62
+ />
63
+ <span className="backlog-item-title">{task.title}</span>
64
+ {task.assignee && (
65
+ <span className="backlog-item-badge">@{task.assignee}</span>
66
+ )}
67
+ {task.tags?.length > 0 && (
68
+ <span className="backlog-item-badge">{task.tags.join(', ')}</span>
69
+ )}
70
+ </div>
71
+ {task.description && (
72
+ <div className="backlog-item-desc">{task.description.slice(0, 120)}</div>
73
+ )}
74
+ <div className="backlog-item-footer">
75
+ <span className="muted text-sm tabular-nums">
76
+ {formatRelative(task.createdAt)}
77
+ </span>
78
+ <div className="backlog-item-actions">
79
+ <button
80
+ type="button"
81
+ className="icon-btn"
82
+ title="Promote to queued"
83
+ onClick={handlePromote}
84
+ >
85
+ <ArrowUp size={13} />
86
+ </button>
87
+ <button
88
+ type="button"
89
+ className="icon-btn"
90
+ title="Edit"
91
+ onClick={() => onEdit(task)}
92
+ >
93
+ <Sparkles size={13} />
94
+ </button>
95
+ <button
96
+ type="button"
97
+ className="icon-btn icon-btn-danger"
98
+ title="Delete"
99
+ onClick={handleDelete}
100
+ >
101
+ <Trash2 size={13} />
102
+ </button>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ type Props = {
110
+ /** Current list of agents for the edit modal. */
111
+ agents: Agent[];
112
+ /** Called after any mutation so the parent can refresh snapshot. */
113
+ onRefresh: () => Promise<void>;
114
+ };
115
+
116
+ export function BacklogPanel({ agents, onRefresh }: Props) {
117
+ const toast = useToast();
118
+ const [tasks, setTasks] = useState<Task[]>([]);
119
+ const [loading, setLoading] = useState(false);
120
+
121
+ const load = async () => {
122
+ try {
123
+ setLoading(true);
124
+ const r = await api.get<{ tasks: Task[] }>('/tasks/backlog');
125
+ setTasks(Array.isArray(r.tasks) ? r.tasks : []);
126
+ } catch (err) {
127
+ toast.error(`Backlog load failed: ${(err as Error).message}`);
128
+ } finally {
129
+ setLoading(false);
130
+ }
131
+ };
132
+
133
+ useEffect(() => { load(); }, []);
134
+
135
+ const handlePromote = async (id: string) => {
136
+ setTasks((cur) => cur.filter((t) => t.id !== id));
137
+ await onRefresh();
138
+ };
139
+
140
+ const handleDelete = async (id: string) => {
141
+ setTasks((cur) => cur.filter((t) => t.id !== id));
142
+ await onRefresh();
143
+ };
144
+
145
+ const handleEdit = (_task: Task) => {
146
+ // Editing is wired through the parent Tasks view's modal helper.
147
+ // Keep this no-op so the icon click is at least visible; the parent
148
+ // provides the real edit affordance via the kanban.
149
+ };
150
+
151
+ const handlePromoteAll = async () => {
152
+ if (tasks.length === 0) return;
153
+ try {
154
+ const r = await api.post<{ affected: { id: string; ok: boolean }[] }>('/tasks/promote-batch', {
155
+ ids: tasks.map((t) => t.id),
156
+ });
157
+ const ok = r.affected?.filter((a) => a.ok).length ?? 0;
158
+ toast.success(`Promoted ${ok} task(s).`, 2000);
159
+ setTasks([]);
160
+ await onRefresh();
161
+ } catch (err) {
162
+ toast.error(`Promote all failed: ${(err as Error).message}`);
163
+ }
164
+ };
165
+
166
+ return (
167
+ <div className="backlog-panel">
168
+ <div className="backlog-panel-header">
169
+ <span className="backlog-panel-title">
170
+ <Inbox size={15} />
171
+ Backlog ({tasks.length})
172
+ </span>
173
+ <div className="backlog-panel-header-actions">
174
+ {tasks.length > 0 && (
175
+ <Button variant="ghost" size="sm" onClick={handlePromoteAll} title="Promote all to queued">
176
+ <ArrowUp size={12} /> Promote all
177
+ </Button>
178
+ )}
179
+ <Button variant="ghost" size="sm" onClick={load} title="Refresh backlog" aria-label="Refresh backlog">
180
+ <RefreshCw size={12} className={loading ? 'animate-spin' : ''} />
181
+ </Button>
182
+ </div>
183
+ </div>
184
+
185
+ {loading ? (
186
+ <div className="backlog-empty">Loading…</div>
187
+ ) : tasks.length === 0 ? (
188
+ <div className="backlog-empty">
189
+ <Inbox size={28} />
190
+ <span>Backlog is empty.</span>
191
+ </div>
192
+ ) : (
193
+ <div className="backlog-list">
194
+ {tasks.map((t) => (
195
+ <BacklogItem
196
+ key={t.id}
197
+ task={t}
198
+ agents={agents}
199
+ onPromote={handlePromote}
200
+ onDelete={handleDelete}
201
+ onEdit={handleEdit}
202
+ onRefresh={onRefresh}
203
+ />
204
+ ))}
205
+ </div>
206
+ )}
207
+ </div>
208
+ );
209
+ }