@polderlabs/bizar 4.3.0 → 4.4.1

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 +36 -8
  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 +2 -2
  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,72 @@
1
+ /**
2
+ * src/server/opencode-sdk.mjs
3
+ *
4
+ * Dashboard-side wrapper that reads serve-info.mjs, constructs the
5
+ * opencode SDK instance with auth, and exports a singleton `getOpencodeSdk()`.
6
+ *
7
+ * Also exports `pingOpencodeSdk(info)` which uses the SDK's `health.check()`
8
+ * (replaces the old `pingOpencodeServe` from serve-info.mjs; the old name
9
+ * is kept as a backwards-compatible alias).
10
+ *
11
+ * v0.1.0 — initial implementation
12
+ */
13
+
14
+ import { readServeInfo, pingOpencodeServe as _pingOpencodeServe } from "./serve-info.mjs";
15
+
16
+ let _sdk = null;
17
+ let _sdkInfo = null;
18
+
19
+ /**
20
+ * Lazily create and cache an opencode SDK instance from the active
21
+ * serve-info. Returns `null` when no serve-info is available.
22
+ *
23
+ * @returns {Promise<import('@polderlabs/bizar-sdk').OpencodeSdk | null>}
24
+ */
25
+ export async function getOpencodeSdk() {
26
+ const info = readServeInfo();
27
+ if (!info) return null;
28
+
29
+ // Re-use existing instance if serve-info hasn't changed.
30
+ if (_sdk && _sdkInfo && _sdkInfo.password === info.password && _sdkInfo.port === info.port) {
31
+ return _sdk;
32
+ }
33
+
34
+ const { createOpencodeSdk } = await import("@polderlabs/bizar-sdk/opencode");
35
+ const sdk = await createOpencodeSdk({
36
+ baseUrl: info.baseUrl,
37
+ password: info.password,
38
+ throwOnError: false,
39
+ });
40
+
41
+ _sdk = sdk;
42
+ _sdkInfo = { password: info.password, port: info.port };
43
+ return sdk;
44
+ }
45
+
46
+ /**
47
+ * Ping the opencode serve child using the SDK's health endpoint.
48
+ * Falls back to TCP-connect ping if the SDK health check fails.
49
+ *
50
+ * @param {import('./serve-info.mjs').ServeInfo} info
51
+ * @returns {Promise<boolean>}
52
+ */
53
+ export async function pingOpencodeSdk(info) {
54
+ const sdk = await getOpencodeSdk();
55
+ if (!sdk) return false;
56
+ try {
57
+ const result = await sdk.health.check();
58
+ if (result && typeof result === "object" && "ok" in result) {
59
+ return result.ok === true;
60
+ }
61
+ // BizarError shape — fall back to TCP ping
62
+ return pingOpencodeServe(info);
63
+ } catch {
64
+ return pingOpencodeServe(info);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Backwards-compatible alias for `pingOpencodeSdk`.
70
+ * @deprecated Use `pingOpencodeSdk` instead.
71
+ */
72
+ export const pingOpencodeServe = pingOpencodeSdk;
@@ -161,5 +161,97 @@ export function createBackgroundRouter({ broadcast }) {
161
161
  res.json(summary);
162
162
  }));
163
163
 
164
+ // v3.22.0 — Open-terminal endpoint. The frontend calls this when the
165
+ // operator clicks "Open in terminal" on a TmuxAttachCard. The server
166
+ // spawns the platform's terminal emulator with the tmux attach command.
167
+ // Whitelisted emulators: 'system' (auto-detect), 'tmux-iterm' (macOS
168
+ // iTerm2), 'tmux-wt' (Windows Terminal).
169
+ //
170
+ // The endpoint ALWAYS returns 200 with `{ ok, command }` or
171
+ // `{ ok: false, error }` — never a raw 5xx, so the frontend can fall
172
+ // back to clipboard copy.
173
+ router.post('/background/:id/open-terminal', wrap(async (req, res) => {
174
+ const { backgroundStore } = await import('../background-store.mjs');
175
+ const info = backgroundStore.tmuxAttachInfo(req.params.id);
176
+
177
+ if (!info || !info.session) {
178
+ res.json({ ok: false, error: 'no_tmux_session' });
179
+ return;
180
+ }
181
+
182
+ // Validate actual tmux session name matches the bgr_<hex> pattern.
183
+ if (!/^bgr_[A-Za-z0-9_-]{1,24}$/.test(info.session)) {
184
+ res.json({ ok: false, error: 'invalid_session_name' });
185
+ return;
186
+ }
187
+
188
+ // Whitelist emulator selection.
189
+ const emulator = (req.body?.emulator || 'system').toString();
190
+ const ALLOWED_EMULATORS = ['system', 'tmux-iterm', 'tmux-wt'];
191
+ if (!ALLOWED_EMULATORS.includes(emulator)) {
192
+ res.json({ ok: false, error: 'unknown_emulator', allowed: ALLOWED_EMULATORS });
193
+ return;
194
+ }
195
+
196
+ const { execFileSync, spawn } = await import('node:child_process');
197
+ const platform = process.platform;
198
+ const command = `tmux attach -t ${info.session}`;
199
+
200
+ if (emulator === 'system') {
201
+ // Auto-detect platform terminal.
202
+ if (platform === 'darwin') {
203
+ spawn('osascript', [
204
+ '-e', `tell application "Terminal" to do script "${command}"`,
205
+ '-e', 'activate application "Terminal"',
206
+ ], { detached: true, stdio: 'ignore' }).unref();
207
+ } else if (platform === 'linux') {
208
+ const candidates = [
209
+ ['gnome-terminal', '--', ...command.split(' ')],
210
+ ['konsole', '-e', command],
211
+ ['xterm', '-e', command],
212
+ ['x-terminal-emulator', '-e', command],
213
+ ];
214
+ let launched = false;
215
+ for (const [cmd, ...args] of candidates) {
216
+ try {
217
+ execFileSync('which', [cmd], { stdio: 'pipe' });
218
+ spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
219
+ launched = true;
220
+ break;
221
+ } catch { /* try next */ }
222
+ }
223
+ if (!launched) {
224
+ res.json({ ok: false, error: 'no_terminal_emulator_found' });
225
+ return;
226
+ }
227
+ } else if (platform === 'win32') {
228
+ try {
229
+ execFileSync('where', ['wt.exe'], { stdio: 'pipe' });
230
+ spawn('wt.exe', ['-e', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
231
+ } catch {
232
+ spawn('cmd', ['/c', 'start', '', 'cmd', '/k', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
233
+ }
234
+ } else {
235
+ res.json({ ok: false, error: `unsupported_platform: ${platform}` });
236
+ return;
237
+ }
238
+ } else if (emulator === 'tmux-iterm') {
239
+ if (platform !== 'darwin') {
240
+ res.json({ ok: false, error: 'tmux-iterm requires macOS' });
241
+ return;
242
+ }
243
+ // iTerm2 has a "tmux integration" mode; open -a iTerm sends the command.
244
+ spawn('open', ['-a', 'iTerm2', command], { detached: true, stdio: 'ignore' }).unref();
245
+ } else if (emulator === 'tmux-wt') {
246
+ if (platform !== 'win32') {
247
+ res.json({ ok: false, error: 'tmux-wt requires Windows' });
248
+ return;
249
+ }
250
+ spawn('wt.exe', ['-e', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
251
+ }
252
+
253
+ res.json({ ok: true, command });
254
+ }));
255
+
164
256
  return router;
165
257
  }