@polderlabs/bizar 4.2.4 → 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 (63) hide show
  1. package/bizar-dash/src/server/api.mjs +2 -0
  2. package/bizar-dash/src/server/opencode-sdk.mjs +72 -0
  3. package/bizar-dash/src/server/routes/background.mjs +92 -0
  4. package/bizar-dash/src/server/routes/chat.mjs +300 -123
  5. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +452 -0
  6. package/bizar-dash/src/server/routes/tasks.mjs +59 -1
  7. package/bizar-dash/src/server/serve-info.mjs +124 -1
  8. package/bizar-dash/src/server/task-delegator.mjs +154 -8
  9. package/bizar-dash/src/server/tasks-store.mjs +50 -2
  10. package/bizar-dash/src/web/components/background/AttachButton.tsx +96 -0
  11. package/bizar-dash/src/web/components/background/TmuxAttachCard.tsx +122 -0
  12. package/bizar-dash/src/web/components/chat/AgentNode.tsx +127 -0
  13. package/bizar-dash/src/web/components/chat/AgentTree.tsx +80 -0
  14. package/bizar-dash/src/web/components/chat/ChatComposer.tsx +76 -0
  15. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +144 -0
  16. package/bizar-dash/src/web/components/chat/ChatRail.tsx +387 -0
  17. package/bizar-dash/src/web/components/chat/ChatThread.tsx +28 -7
  18. package/bizar-dash/src/web/components/chat/JumpToLatest.tsx +58 -0
  19. package/bizar-dash/src/web/components/chat/MessageBlock.tsx +260 -0
  20. package/bizar-dash/src/web/components/chat/SessionList.tsx +15 -5
  21. package/bizar-dash/src/web/components/chat/SessionRowMenu.tsx +206 -0
  22. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +33 -5
  23. package/bizar-dash/src/web/components/chat/_legacy.ts +30 -0
  24. package/bizar-dash/src/web/components/chat/index.ts +11 -0
  25. package/bizar-dash/src/web/components/chat/useChat.ts +426 -62
  26. package/bizar-dash/src/web/components/tasks/BacklogPanel.css +109 -0
  27. package/bizar-dash/src/web/components/tasks/BacklogPanel.tsx +209 -0
  28. package/bizar-dash/src/web/lib/types.ts +1 -0
  29. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +7 -5
  30. package/bizar-dash/src/web/styles/chat.css +1536 -133
  31. package/bizar-dash/src/web/views/BackgroundAgents.tsx +3 -0
  32. package/bizar-dash/src/web/views/Chat.tsx +147 -55
  33. package/bizar-dash/src/web/views/Tasks.tsx +23 -1
  34. package/bizar-dash/tests/opencode-sessions-detail.test.mjs +273 -0
  35. package/cli/bg.mjs +94 -71
  36. package/cli/bin.mjs +19 -7
  37. package/cli/service-controller.mjs +587 -0
  38. package/cli/service-controller.test.mjs +92 -0
  39. package/cli/service.mjs +162 -14
  40. package/config/agents/baldr.md +2 -0
  41. package/config/agents/browser-harness.md +2 -0
  42. package/config/agents/forseti.md +2 -0
  43. package/config/agents/frigg.md +2 -0
  44. package/config/agents/heimdall.md +2 -0
  45. package/config/agents/hermod.md +2 -0
  46. package/config/agents/mimir.md +2 -0
  47. package/config/agents/odin.md +2 -0
  48. package/config/agents/quick.md +2 -0
  49. package/config/agents/semble-search.md +2 -0
  50. package/config/agents/thor.md +2 -0
  51. package/config/agents/tyr.md +2 -0
  52. package/config/agents/vidarr.md +2 -0
  53. package/config/agents/vor.md +2 -0
  54. package/config/opencode.json.template +1 -0
  55. package/install.sh +448 -787
  56. package/package.json +2 -2
  57. package/packages/sdk/package.json +20 -0
  58. package/packages/sdk/src/client.ts +5 -0
  59. package/packages/sdk/src/errors.ts +11 -2
  60. package/packages/sdk/src/index.ts +19 -0
  61. package/packages/sdk/src/opencode-events.ts +134 -0
  62. package/packages/sdk/src/opencode-types.ts +66 -0
  63. package/packages/sdk/src/opencode.ts +335 -0
package/cli/bg.mjs CHANGED
@@ -15,10 +15,11 @@
15
15
  * kill <id> Kill a running agent
16
16
  * logs <id> Tail the agent's log file
17
17
  *
18
- * The "view" subcommand is the headline feature: it gives the user
19
- * one terminal window with all running agents visible at once via
20
- * tmux splits. This is the antidote to "is the agent doing
21
- * anything?" — you can SEE them all working in parallel.
18
+ * The "view" subcommand (v3.22) is the headline feature: it creates a
19
+ * tmux control session (`bizar-bg-view`) with one pane per running
20
+ * agent (each pane attached to the agent's own tmux session, tiled).
21
+ * This is the antidote to "is the agent doing anything?" — you can
22
+ * SEE them all working in parallel.
22
23
  */
23
24
  import { execFileSync, spawn, spawnSync } from 'node:child_process';
24
25
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
@@ -245,59 +246,112 @@ async function runKill(instanceId) {
245
246
  // --- view ----------------------------------------------------------------
246
247
 
247
248
  /**
248
- * Build a tmux "control session" that splits into N panes, each
249
- * showing the log of one running agent. The session name is fixed
250
- * (`bgr_view`) so subsequent `bizar bg view` calls reuse it.
249
+ * View subcommand (v3.22):
250
+ * - Detects tmux. If missing, prints per-instance `bizar bg logs <id>`
251
+ * fallback instructions and exits 0.
252
+ * - Creates a fresh tmux control session `bizar-bg-view` with one pane
253
+ * per running instance, each pane running `tmux attach -t <session>`.
254
+ * - Caps at 16 panes (beyond that, tmux tiled layout degrades).
255
+ * - Zero running instances → prints "no running agents" and exits 0.
251
256
  */
252
- function buildTmuxControlSession() {
257
+
258
+ async function runView() {
253
259
  const all = readAllBgInstances();
254
- const tmux = listBgrTmuxSessions();
260
+ const tmuxSessions = listBgrTmuxSessions();
255
261
  const running = all.filter(
262
+ (e) => e.data.status === 'running' || e.data.status === 'pending',
263
+ );
264
+
265
+ // Zero running agents — not an error.
266
+ if (running.length === 0) {
267
+ console.log(chalk.dim('\n No running background agents.\n'));
268
+ return 0;
269
+ }
270
+
271
+ // Tmux presence check. If missing, print fallback instructions and exit 0
272
+ // (not an error — the operator can still use per-instance logs).
273
+ if (!which('tmux')) {
274
+ console.log(chalk.yellow('\n ⚠ tmux is not installed on this host.\n'));
275
+ console.log(chalk.dim(' You can inspect individual agents via:\n'));
276
+ for (const r of running) {
277
+ console.log(chalk.cyan(` bizar bg logs ${r.data.instanceId}`));
278
+ }
279
+ console.log();
280
+ return 0;
281
+ }
282
+
283
+ // Filter to instances that actually have a live tmux session.
284
+ const active = running.filter(
256
285
  (e) =>
257
- (e.data.status === 'running' || e.data.status === 'pending') &&
258
286
  e.data.sessionId &&
259
- tmux.includes(tmuxSessionForSessionId(e.data.sessionId)),
287
+ tmuxSessions.includes(tmuxSessionForSessionId(e.data.sessionId)),
260
288
  );
261
289
 
262
- if (running.length === 0) {
263
- return { ok: false, reason: 'no-running-agents' };
290
+ if (active.length === 0) {
291
+ console.log(chalk.dim('\n No agents with active tmux sessions found.\n'));
292
+ console.log(chalk.dim(' State files exist but no tmux sessions are running.\n'));
293
+ console.log(chalk.dim(' Run `bizar bg list` to inspect state.\n'));
294
+ return 0;
264
295
  }
265
296
 
266
- // 1. Create a fresh control session (or replace the existing one).
297
+ // Cap at 16 panes beyond that, tiled layout is unusable.
298
+ const capped = active.slice(0, 16);
299
+ const overflow = active.length - capped.length;
300
+
301
+ // Kill any stale bizar-bg-view session, then create a fresh one.
267
302
  try {
268
- execFileSync('tmux', ['kill-session', '-t', 'bgr_view'], { stdio: 'pipe' });
303
+ execFileSync('tmux', ['kill-session', '-t', 'bizar-bg-view'], { stdio: 'pipe' });
269
304
  } catch {
270
- // didn't exist
305
+ /* didn't exist */
271
306
  }
272
307
  execFileSync('tmux', [
273
- 'new-session', '-d', '-s', 'bgr_view',
308
+ 'new-session', '-d', '-s', 'bizar-bg-view',
274
309
  '-x', '220', '-y', '50',
275
310
  ], { stdio: 'pipe' });
276
311
 
277
- // 2. For each running agent after the first, split the pane
278
- // and attach a tail in the new pane.
279
- for (let i = 1; i < running.length; i++) {
280
- const splitCmd = i % 2 === 1 ? 'split-window -h -t bgr_view' : 'split-window -v -t bgr_view';
281
- execFileSync('tmux', splitCmd.split(' '), { stdio: 'pipe' });
282
- }
283
- // Apply tiled layout for a clean grid.
284
- execFileSync('tmux', ['select-layout', '-t', 'bgr_view', 'tiled'], { stdio: 'pipe' });
285
-
286
- // 3. Send a label + tail command to each pane.
287
- for (let i = 0; i < running.length; i++) {
288
- const r = running[i];
289
- const tmuxName = tmuxSessionForSessionId(r.data.sessionId);
290
- const logPath = r.data.logPath || join(homedir(), '.cache', 'bizar', 'logs', `${r.data.sessionId}.log`);
291
- const header = `echo "═══ ${r.data.agent} │ ${r.data.instanceId} │ ${tmuxName} ═══"; tail -n 200 -F "${logPath}"`;
292
- // Use send-keys so the echo+tail are sent in sequence, then Enter
293
- // to run them. Target the pane by index in the bgr_view session.
312
+ // Pane 0 first instance
313
+ const firstSession = tmuxSessionForSessionId(capped[0].data.sessionId);
314
+ execFileSync('tmux', [
315
+ 'send-keys', '-t', 'bizar-bg-view:0.0',
316
+ `tmux attach -t ${firstSession}`, 'Enter',
317
+ ], { stdio: 'pipe' });
318
+
319
+ // Split for remaining instances (2..N)
320
+ for (let i = 1; i < capped.length; i++) {
321
+ execFileSync('tmux', ['split-window', '-v', '-t', 'bizar-bg-view'], { stdio: 'pipe' });
322
+ const sessionName = tmuxSessionForSessionId(capped[i].data.sessionId);
294
323
  execFileSync('tmux', [
295
- 'send-keys', '-t', `bgr_view:0.${i}`,
296
- header, 'Enter',
324
+ 'send-keys', '-t', `bizar-bg-view:0.${i}`,
325
+ `tmux attach -t ${sessionName}`, 'Enter',
297
326
  ], { stdio: 'pipe' });
298
327
  }
299
328
 
300
- return { ok: true, count: running.length, session: 'bgr_view' };
329
+ // Tiled layout for a clean grid.
330
+ execFileSync('tmux', ['select-layout', '-t', 'bizar-bg-view', 'tiled'], { stdio: 'pipe' });
331
+
332
+ console.log(chalk.green(`\n ✓ Built tmux view session with ${capped.length} pane(s)\n`));
333
+
334
+ if (overflow > 0) {
335
+ console.log(chalk.yellow(` ⚠ ${overflow} more agent(s) not shown (16-pane cap):\n`));
336
+ for (const r of active.slice(16)) {
337
+ const sn = tmuxSessionForSessionId(r.data.sessionId);
338
+ console.log(chalk.dim(` bizar bg logs ${r.data.instanceId} — tmux attach -t ${sn}`));
339
+ }
340
+ console.log();
341
+ }
342
+
343
+ // Open an OS terminal window attached to the control session.
344
+ const open = openTerminalAttached('bizar-bg-view');
345
+ if (open.ok) {
346
+ console.log(chalk.green(` ✓ Opened ${open.terminal} attached to "bizar-bg-view"\n`));
347
+ console.log(chalk.dim(' Tip: `tmux attach -t bizar-bg-view` from any terminal.\n'));
348
+ } else {
349
+ console.log(chalk.yellow(` ⚠ Could not open terminal: ${open.error}\n`));
350
+ console.log(chalk.dim(' Run in a terminal you can see:\n'));
351
+ console.log(chalk.cyan(` tmux attach -t bizar-bg-view\n`));
352
+ }
353
+
354
+ return 0;
301
355
  }
302
356
 
303
357
  /**
@@ -362,39 +416,7 @@ function openTerminalAttached(tmuxSession) {
362
416
  return { ok: false, error: `unsupported platform: ${platform}` };
363
417
  }
364
418
 
365
- async function runView() {
366
- if (!which('tmux')) {
367
- console.log(chalk.red('\n ✗ tmux is not installed. Install it and retry.'));
368
- console.log(chalk.dim(' macOS: brew install tmux'));
369
- console.log(chalk.dim(' Linux: apt-get install tmux (or your distro equivalent)'));
370
- console.log(chalk.dim(' Windows: choco install tmux\n'));
371
- return 1;
372
- }
373
-
374
- const build = buildTmuxControlSession();
375
- if (!build.ok) {
376
- if (build.reason === 'no-running-agents') {
377
- console.log(chalk.dim('\n No running background agents to view.\n'));
378
- console.log(chalk.dim(' Spawn one with `bizar_spawn_background` (Odin) or via the dashboard,\n'));
379
- console.log(chalk.dim(' then re-run this command.\n'));
380
- return 1;
381
- }
382
- console.log(chalk.red(`\n ✗ Failed to build control session: ${build.error || build.reason}\n`));
383
- return 1;
384
- }
385
419
 
386
- console.log(chalk.green(`\n ✓ Built tmux control session with ${build.count} pane(s)\n`));
387
- const open = openTerminalAttached(build.session);
388
- if (open.ok) {
389
- console.log(chalk.green(` ✓ Opened ${open.terminal} attached to tmux session "${build.session}"\n`));
390
- console.log(chalk.dim(` Tip: \`tmux attach -t ${build.session}\` from any terminal.\n`));
391
- } else {
392
- console.log(chalk.yellow(` ⚠ Could not open a terminal window: ${open.error}\n`));
393
- console.log(chalk.dim(' Run one of the following in a terminal you can see:\n'));
394
- console.log(chalk.cyan(` tmux attach -t ${build.session}\n`));
395
- }
396
- return 0;
397
- }
398
420
 
399
421
  // --- Help ----------------------------------------------------------------
400
422
 
@@ -416,8 +438,9 @@ function showHelp() {
416
438
  and a log file (default ~/.cache/bizar/logs/<sessionId>.log).
417
439
 
418
440
  The \`view\` subcommand is the headline feature: it creates a new
419
- tmux session (\`bgr_view\`) with one pane per running agent, then
420
- opens a new OS terminal window attached to it. You can see all
441
+ tmux session (\`bizar-bg-view\`) with one pane per running agent,
442
+ each pane attached to the agent's own tmux session (tiled layout),
443
+ then opens a new OS terminal window attached to it. You can see all
421
444
  your agents working in parallel at a glance.
422
445
  `);
423
446
  }
package/cli/bin.mjs CHANGED
@@ -291,17 +291,27 @@ function showServiceHelp() {
291
291
  bizar service — Manage the background service daemon
292
292
 
293
293
  Usage:
294
- bizar service start Start the service in background
295
- bizar service stop Stop the running service
296
- bizar service status Show whether the service is running
297
- bizar service logs Tail the service log
298
- bizar service follow Follow the service log until Ctrl-C
294
+ bizar service start Start the service in background
295
+ bizar service stop Stop the running service
296
+ bizar service status Show whether the service is running
297
+ bizar service logs Tail the service log
298
+ bizar service follow Follow the service log until Ctrl-C
299
+ bizar service install Register with systemd / launchd / scheduled task
300
+ bizar service install --force Re-install even when the unit matches
301
+ bizar service uninstall Remove the OS-level autostart
302
+ bizar service uninstall --force Force-uninstall even when nothing is registered
299
303
 
300
304
  Description:
301
305
  The service watches per-project schedules (cron / interval / once)
302
306
  and runs them at the right time. It logs to
303
307
  ${bizarConfigDir}/service.log and writes its PID to
304
308
  ${bizarConfigDir}/service.pid.
309
+
310
+ install registers the daemon under the OS init system — systemd user
311
+ unit on Linux, launchd LaunchAgent on macOS, scheduled task
312
+ ("BizarDashboardService", ONSTART, HIGHEST) on Windows. After
313
+ install, a normal user does not need to run \`bizar service start\`
314
+ for the dashboard background process — the OS does it at login.
305
315
  `);
306
316
  }
307
317
 
@@ -613,9 +623,11 @@ async function runTestGate() {
613
623
  }
614
624
 
615
625
  /**
616
- * Service commands — start / stop / status / logs.
626
+ * Service commands — start / stop / status / logs / install / uninstall.
617
627
  *
618
- * Implementation lives in cli/service.mjs (created below).
628
+ * Implementation lives in cli/service.mjs. The install / uninstall
629
+ * branches delegate to cli/service-controller.mjs for OS-level
630
+ * registration (systemd / launchd / schtasks).
619
631
  */
620
632
  async function runServiceCommand(sub) {
621
633
  const { runService } = await import('./service.mjs');