@polderlabs/bizar 5.4.0 → 5.5.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 (99) hide show
  1. package/bizar-dash/dist/assets/{EnvVarsSection-DqvB8ZcJ.js → EnvVarsSection-L413CQEI.js} +3 -3
  2. package/bizar-dash/dist/assets/{EnvVarsSection-DqvB8ZcJ.js.map → EnvVarsSection-L413CQEI.js.map} +1 -1
  3. package/bizar-dash/dist/assets/{MobileChat-DRXP2ERQ.js → MobileChat-0kGQb1S6.js} +1 -1
  4. package/bizar-dash/dist/assets/{MobileChat-DRXP2ERQ.js.map → MobileChat-0kGQb1S6.js.map} +1 -1
  5. package/bizar-dash/dist/assets/MobileSettings-Cq1oFIdO.js +1 -0
  6. package/bizar-dash/dist/assets/{MobileSettings-CVW9VhEo.js.map → MobileSettings-Cq1oFIdO.js.map} +1 -1
  7. package/bizar-dash/dist/assets/{Toast-CcZVVN4i.js → Toast-C6f1zFJ2.js} +1 -1
  8. package/bizar-dash/dist/assets/{Toast-CcZVVN4i.js.map → Toast-C6f1zFJ2.js.map} +1 -1
  9. package/bizar-dash/dist/assets/{icons-OMEGx5KA.js → icons-BsWXWkEF.js} +121 -126
  10. package/bizar-dash/dist/assets/icons-BsWXWkEF.js.map +1 -0
  11. package/bizar-dash/dist/assets/main-BklYy02e.css +1 -0
  12. package/bizar-dash/dist/assets/main-CBTgYSeG.js +18 -0
  13. package/bizar-dash/dist/assets/main-CBTgYSeG.js.map +1 -0
  14. package/bizar-dash/dist/assets/mobile-CsaN4CxH.js +1 -0
  15. package/bizar-dash/dist/assets/{mobile-XN2P7YfM.js.map → mobile-CsaN4CxH.js.map} +1 -1
  16. package/bizar-dash/dist/assets/{mobile-layout-D6V-7WnA.css → mobile-layout-CJnZNLy3.css} +1 -1
  17. package/bizar-dash/dist/assets/mobile-layout-iFBdjEE4.js +2 -0
  18. package/bizar-dash/dist/assets/mobile-layout-iFBdjEE4.js.map +1 -0
  19. package/bizar-dash/dist/assets/{useSlashCommands-iyoW95UB.js → useSlashCommands-DGJcC9iQ.js} +2 -2
  20. package/bizar-dash/dist/assets/{useSlashCommands-iyoW95UB.js.map → useSlashCommands-DGJcC9iQ.js.map} +1 -1
  21. package/bizar-dash/dist/index.html +8 -8
  22. package/bizar-dash/dist/mobile.html +4 -4
  23. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  24. package/bizar-dash/src/server/backup-store.mjs +6 -1
  25. package/bizar-dash/src/server/bg-spawner.mjs +574 -0
  26. package/bizar-dash/src/server/memory-lightrag.mjs +242 -0
  27. package/bizar-dash/src/server/memory-store.mjs +43 -3
  28. package/bizar-dash/src/server/routes/background.mjs +143 -0
  29. package/bizar-dash/src/server/routes/lightrag.mjs +10 -0
  30. package/bizar-dash/src/server/routes/memory.mjs +8 -0
  31. package/bizar-dash/src/server/server.mjs +22 -0
  32. package/bizar-dash/src/web/App.tsx +3 -0
  33. package/bizar-dash/src/web/MobileApp.tsx +1 -10
  34. package/bizar-dash/src/web/components/InstallConfirmDialog.tsx +1 -1
  35. package/bizar-dash/src/web/components/SettingsNav.tsx +1 -10
  36. package/bizar-dash/src/web/lib/types.ts +39 -1
  37. package/bizar-dash/src/web/styles/main.css +43 -0
  38. package/bizar-dash/src/web/styles/mobile-layout.css +7 -5
  39. package/bizar-dash/src/web/styles/settings.css +46 -0
  40. package/bizar-dash/src/web/views/BackgroundAgents.tsx +399 -30
  41. package/bizar-dash/src/web/views/Settings.tsx +12 -12
  42. package/bizar-dash/src/web/views/SpawnAgentModal.tsx +221 -0
  43. package/bizar-dash/tests/BackgroundAgents.test.tsx +160 -0
  44. package/bizar-dash/tests/background-pause-resume.test.mjs +46 -0
  45. package/bizar-dash/tests/background-spawn-from-ui.test.mjs +36 -0
  46. package/bizar-dash/tests/background-steer.test.mjs +42 -0
  47. package/bizar-dash/tests/lightrag-startup-hook.test.mjs +206 -0
  48. package/bizar-dash/tests/memory-auto-reindex.test.mjs +196 -0
  49. package/bizar-dash/tests/memory-cli.test.mjs +1 -1
  50. package/bizar-dash/tests/memory-default-vault.test.mjs +90 -3
  51. package/bizar-dash/tests/memory-lightrag-extended.test.mjs +131 -0
  52. package/bizar-dash/tests/mobile-layout.test.tsx +36 -0
  53. package/bizar-dash/tests/settings-layout.test.tsx +3 -3
  54. package/bizar-dash/tests/settings-nav.test.tsx +7 -5
  55. package/cli/bin.mjs +16 -0
  56. package/cli/commands/deploy/docker.mjs +2 -1
  57. package/cli/commands/lightrag.mjs +171 -0
  58. package/cli/commands/service.mjs +7 -0
  59. package/cli/commands/util.mjs +8 -1
  60. package/cli/init.mjs +1 -1
  61. package/cli/post-install-smoke.mjs +230 -0
  62. package/cli/provision.mjs +177 -0
  63. package/cli/provision.test.mjs +180 -0
  64. package/cli/service-controller.mjs +378 -28
  65. package/cli/service-controller.test.mjs +100 -0
  66. package/cli/service-env.mjs +139 -0
  67. package/cli/service.mjs +23 -0
  68. package/config/agents/_shared/AGENT_BASELINE.md +6 -6
  69. package/install.sh +87 -6
  70. package/package.json +1 -1
  71. package/plugins/bizar/index.ts +147 -0
  72. package/plugins/bizar/src/background-state.ts +84 -5
  73. package/plugins/bizar/src/background.ts +388 -10
  74. package/plugins/bizar/src/hooks/memory-inject.ts +247 -0
  75. package/plugins/bizar/src/hooks/memory-write-on-end.ts +188 -0
  76. package/plugins/bizar/src/opencode-runner.ts +87 -1
  77. package/plugins/bizar/src/tools/bg-pause.ts +77 -0
  78. package/plugins/bizar/src/tools/bg-report-progress.ts +101 -0
  79. package/plugins/bizar/src/tools/bg-resume.ts +72 -0
  80. package/plugins/bizar/src/tools/bg-send-message.ts +105 -0
  81. package/plugins/bizar/src/tools/bg-spawn.ts +13 -6
  82. package/plugins/bizar/src/tools/bg-status.ts +10 -0
  83. package/plugins/bizar/src/tools/memory-list.ts +135 -0
  84. package/plugins/bizar/src/tools/memory-read.ts +142 -0
  85. package/plugins/bizar/src/tools/memory-search.ts +228 -0
  86. package/plugins/bizar/src/tools/memory-write.ts +183 -0
  87. package/plugins/bizar/tests/memory-write-on-end.test.ts +92 -0
  88. package/plugins/bizar/tests/tools/bg-pause.test.ts +64 -0
  89. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +82 -0
  90. package/plugins/bizar/tests/tools/bg-resume.test.ts +43 -0
  91. package/plugins/bizar/tests/tools/bg-send-message.test.ts +46 -0
  92. package/bizar-dash/dist/assets/MobileSettings-CVW9VhEo.js +0 -1
  93. package/bizar-dash/dist/assets/icons-OMEGx5KA.js.map +0 -1
  94. package/bizar-dash/dist/assets/main-CMAGZouj.js +0 -16
  95. package/bizar-dash/dist/assets/main-CMAGZouj.js.map +0 -1
  96. package/bizar-dash/dist/assets/main-O2tZVdHm.css +0 -1
  97. package/bizar-dash/dist/assets/mobile-XN2P7YfM.js +0 -1
  98. package/bizar-dash/dist/assets/mobile-layout-DqTxvXZp.js +0 -2
  99. package/bizar-dash/dist/assets/mobile-layout-DqTxvXZp.js.map +0 -1
@@ -0,0 +1,139 @@
1
+ /**
2
+ * cli/service-env.mjs
3
+ *
4
+ * v5.x — Central env file builder for the Bizar service.
5
+ *
6
+ * Produces the content for `~/.config/bizar/service.env` (mode 0600),
7
+ * which is sourced by the systemd/launchd service and the headroom companion.
8
+ *
9
+ * All BIZAR_* vars are written here so the service has a consistent view
10
+ * of the environment. Values already present in `process.env` are honoured
11
+ * (never overwritten); everything else gets a safe default.
12
+ *
13
+ * Public API:
14
+ * buildServiceEnvFile({ bizarHome, repoPath, options })
15
+ * → string (the env file content, ready to write)
16
+ * defaultBizarEnv()
17
+ * → Record<string, string> (all defaults merged)
18
+ */
19
+
20
+ import { execSync } from 'node:child_process';
21
+ import { existsSync } from 'node:fs';
22
+ import { homedir } from 'node:os';
23
+ import { join } from 'node:path';
24
+
25
+ /** Default values for every BIZAR_* env var. */
26
+ export function defaultBizarEnv() {
27
+ const home = homedir();
28
+ return {
29
+ BIZAR_HOME: join(home, '.config', 'bizar'),
30
+ BIZAR_REPO: '',
31
+ BIZAR_DASHBOARD_PORT: '4097',
32
+ BIZAR_DASHBOARD_HOST: '127.0.0.1',
33
+ BIZAR_LOG_LEVEL: 'info',
34
+ BIZAR_HEADROOM_AUTOSTART: '1',
35
+ BIZAR_LIGHTRAG_AUTOSTART: '1',
36
+ BIZAR_MEMORY_VAULT: join(home, '.bizar_memory'),
37
+ OPENCODE_SERVER_PASSWORD: '',
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Generate a fresh `OPENCODE_SERVER_PASSWORD` using the platform's
43
+ * secure random source.
44
+ */
45
+ function generatePassword() {
46
+ try {
47
+ if (process.platform === 'win32') {
48
+ // PowerShell
49
+ const out = execSync(
50
+ 'powershell -Command "[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))"',
51
+ { encoding: 'utf8', timeout: 5000 },
52
+ );
53
+ return out.trim().replace(/\n/g, '');
54
+ }
55
+ // Linux/macOS
56
+ const out = execSync('openssl rand -hex 32', { encoding: 'utf8', timeout: 5000 });
57
+ return out.trim();
58
+ } catch {
59
+ // Fallback: rough approximation using /dev/urandom
60
+ try {
61
+ const { readFileSync } = require?.('node:fs') ?? (() => { throw new Error('no fs'); })();
62
+ const fd = require?.('node:fs')?.openSync?.('/dev/urandom', 'r');
63
+ if (fd !== undefined) {
64
+ const buf = Buffer.alloc(32);
65
+ require('node:fs').readSync(fd, buf, 0, 32);
66
+ require('node:fs').closeSync?.(fd);
67
+ return buf.toString('hex');
68
+ }
69
+ } catch { /* ignore */ }
70
+ // Last-resort: use Date.now() + PID — not great but better than nothing
71
+ return `${Date.now()}-${process.pid}`.split('').reverse().join('').slice(0, 64);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Build the service.env file content.
77
+ *
78
+ * @param {object} opts
79
+ * @param {string} [opts.bizarHome] – default: ~/.config/bizar
80
+ * @param {string} [opts.repoPath] – absolute path to the repo root
81
+ * @param {boolean} [opts.headroomAutoStart] – default: true
82
+ * @param {boolean} [opts.lightragAutoStart] – default: true
83
+ * @returns {string} env file content (does NOT write to disk)
84
+ */
85
+ export function buildServiceEnvFile({ bizarHome, repoPath, headroomAutoStart = true, lightragAutoStart = true } = {}) {
86
+ const home = homedir();
87
+ const defaults = defaultBizarEnv();
88
+
89
+ const env = {
90
+ BIZAR_HOME: bizarHome || defaults.BIZAR_HOME,
91
+ BIZAR_REPO: repoPath || process.env.BIZAR_REPO || '',
92
+ BIZAR_DASHBOARD_PORT: process.env.BIZAR_DASHBOARD_PORT || defaults.BIZAR_DASHBOARD_PORT,
93
+ BIZAR_DASHBOARD_HOST: process.env.BIZAR_DASHBOARD_HOST || defaults.BIZAR_DASHBOARD_HOST,
94
+ BIZAR_LOG_LEVEL: process.env.BIZAR_LOG_LEVEL || defaults.BIZAR_LOG_LEVEL,
95
+ BIZAR_HEADROOM_AUTOSTART: String(
96
+ process.env.BIZAR_HEADROOM_AUTOSTART !== undefined
97
+ ? (process.env.BIZAR_HEADROOM_AUTOSTART === '1' || process.env.BIZAR_HEADROOM_AUTOSTART === 'true' ? '1' : '0')
98
+ : (headroomAutoStart ? '1' : '0')
99
+ ),
100
+ BIZAR_LIGHTRAG_AUTOSTART: String(
101
+ process.env.BIZAR_LIGHTRAG_AUTOSTART !== undefined
102
+ ? (process.env.BIZAR_LIGHTRAG_AUTOSTART === '1' || process.env.BIZAR_LIGHTRAG_AUTOSTART === 'true' ? '1' : '0')
103
+ : (lightragAutoStart ? '1' : '0')
104
+ ),
105
+ BIZAR_MEMORY_VAULT: process.env.BIZAR_MEMORY_VAULT || defaults.BIZAR_MEMORY_VAULT,
106
+ };
107
+
108
+ // Always add ~/.local/bin to PATH for uv tool binaries (lightrag-server, etc.)
109
+ const localBin = join(home, '.local', 'bin');
110
+ const existingPath = process.env.PATH || '/usr/local/bin:/usr/bin:/bin';
111
+ env.PATH = existingPath.includes(localBin) ? existingPath : `${localBin}:${existingPath}`;
112
+
113
+ // OPENCODE_SERVER_PASSWORD: honour existing env, otherwise generate fresh
114
+ const password = process.env.OPENCODE_SERVER_PASSWORD
115
+ || process.env.BIZAR_REDACT_PASSWORD === '1' ? ''
116
+ : (process.env.OPENCODE_SERVER_PASSWORD || generatePassword());
117
+ if (password) {
118
+ env.OPENCODE_SERVER_PASSWORD = password;
119
+ }
120
+
121
+ const lines = [
122
+ '# Generated by bizar service install — edit with care.',
123
+ `# Created: ${new Date().toISOString()}`,
124
+ `BIZAR_HOME=${env.BIZAR_HOME}`,
125
+ `BIZAR_REPO=${env.BIZAR_REPO}`,
126
+ `PATH=${env.PATH}`,
127
+ `BIZAR_DASHBOARD_PORT=${env.BIZAR_DASHBOARD_PORT}`,
128
+ `BIZAR_DASHBOARD_HOST=${env.BIZAR_DASHBOARD_HOST}`,
129
+ `BIZAR_LOG_LEVEL=${env.BIZAR_LOG_LEVEL}`,
130
+ `BIZAR_HEADROOM_AUTOSTART=${env.BIZAR_HEADROOM_AUTOSTART}`,
131
+ `BIZAR_LIGHTRAG_AUTOSTART=${env.BIZAR_LIGHTRAG_AUTOSTART}`,
132
+ `BIZAR_MEMORY_VAULT=${env.BIZAR_MEMORY_VAULT}`,
133
+ ];
134
+ if (password) {
135
+ lines.push(`OPENCODE_SERVER_PASSWORD=${password}`);
136
+ }
137
+
138
+ return lines.join('\n') + '\n';
139
+ }
package/cli/service.mjs CHANGED
@@ -377,6 +377,24 @@ export async function runService(sub, _rest) {
377
377
  }
378
378
  return;
379
379
  }
380
+ if (sub === 'restart') {
381
+ // v5.x — issue #7. Used by `bizar update` to make sure the service
382
+ // picks up the freshly-installed binary without leaving the old one
383
+ // running with stale inodes.
384
+ const { restartService } = await import('./service-controller.mjs');
385
+ const force = Array.isArray(_rest) && (_rest.includes('--force') || _rest.includes('-f'));
386
+ const dryRun = Array.isArray(_rest) && (_rest.includes('--dry-run'));
387
+ const r = restartService({ force, dryRun });
388
+ if (r.ok) {
389
+ console.log('[bizar-service] restarted.');
390
+ if (r.unitPath) console.log(`[bizar-service] unit: ${r.unitPath}`);
391
+ if (r.note) console.log(`[bizar-service] ${r.note}`);
392
+ } else {
393
+ console.error(`[bizar-service] restart failed: ${r.error}`);
394
+ process.exitCode = 1;
395
+ }
396
+ return;
397
+ }
380
398
  if (sub === 'uninstall') {
381
399
  const { uninstallService } = await import('./service-controller.mjs');
382
400
  const force = Array.isArray(_rest) && (_rest.includes('--force') || _rest.includes('-f'));
@@ -400,6 +418,7 @@ export async function runService(sub, _rest) {
400
418
  bizar service logs
401
419
  bizar service follow
402
420
  bizar service install [--force] [--dry-run]
421
+ bizar service restart [--force] [--dry-run]
403
422
  bizar service uninstall [--force]
404
423
 
405
424
  Description:
@@ -408,6 +427,10 @@ export async function runService(sub, _rest) {
408
427
  user login. install is idempotent — when the on-disk unit matches
409
428
  the desired content, it returns without restarting the service.
410
429
  Pass --force to overwrite or to drop a stale registration.
430
+
431
+ restart stops the daemon, reinstalls the unit, and starts it again.
432
+ Used by \`bizar update\` to make sure the service picks up the new
433
+ code without leaving the old binary running.
411
434
  `);
412
435
  }
413
436
 
@@ -223,10 +223,10 @@ Bizar stores long-term memory in a **project memory vault** — Markdown files m
223
223
 
224
224
  ### Session Start
225
225
 
226
- 1. Run `bizar memory search "<topic>"` to find relevant prior context.
227
- 2. Read project-level index entries: `bizar memory search "project_index"` or browse notes with `bizar memory list`.
228
- 3. Read the most recent session summaries: `bizar memory search "session_summary"`.
229
- 4. If a relevant decision note exists, read it by path with `cat <vault-path>/projects/<projectId>/decisions/<name>.md`.
226
+ 1. Run `bizar_memory_search({ query: "<topic>" })` to find relevant prior context (the plugin's session-start hook also auto-injects relevant memory at session creation).
227
+ 2. Read project-level index entries: `bizar_memory_search({ query: "project_index" })` or browse notes with `bizar_memory_list({})`.
228
+ 3. Read the most recent session summaries: `bizar_memory_search({ query: "session_summary" })`.
229
+ 4. If a relevant decision note exists, read it by path with `bizar_memory_read({ path: "projects/<projectId>/decisions/<name>.md" })`.
230
230
 
231
231
  ### During Work
232
232
 
@@ -388,7 +388,7 @@ The following rules apply to every agent at all times. They are the single sourc
388
388
  - Bizar does not have a single knowledge cutoff shared by all models. Subagents may run on DeepSeek V4 Flash (opencode-zen, free tier) or MiniMax M2.7 / M3, each with their own training window.
389
389
  - For facts that change quickly (current positions, prices, breaking news) or anything that could have changed recently, **search before answering**: use `websearch` and `webfetch` or delegate to `@mimir` for deep research.
390
390
  - For stable technical knowledge (language semantics, well-established APIs, mathematical truths), answer directly without search.
391
- - Default to running `bizar memory search "<topic>"` at session start to retrieve prior project context before answering anything project-specific.
391
+ - Default to running `bizar_memory_search({ query: "<topic>" })` at session start to retrieve prior project context before answering anything project-specific. (The plugin also auto-injects relevant memory via the session-start hook — agents don't need to manually call this if the hook is active.)
392
392
  - When formulating date-sensitive queries, use the actual current date (Bizar's opencode environment provides this). Do not hardcode years.
393
393
  - Do not over-rely on memory; if uncertain, search. Confabulating costs the user more than searching.
394
394
 
@@ -629,7 +629,7 @@ Rules:
629
629
  The memory vault uses three namespaces: `projects/<projectId>/` (project-specific), `global/bizar/` (cross-project), and `users/<userId>/` (personal). Run `bizar memory status` from the project root to see the active mode and resolved path. The user has been working on this project — their notes contain the real context, the gotchas, the failed approaches, the preferred patterns. **Read the relevant vault entries before making any non-trivial decision.**
630
630
 
631
631
  **When to read:**
632
- - At the start of every session: `bizar memory search "<topic>"` for relevant notes.
632
+ - At the start of every session: the plugin's session-start hook auto-injects relevant memory context. You can also call `bizar_memory_search({ query: "<topic>" })` explicitly.
633
633
  - Before any non-trivial implementation decision: check for ADRs or design notes.
634
634
  - When you're about to suggest something the user has already tried.
635
635
  - When the codebase feels like it's working around something you don't understand.
package/install.sh CHANGED
@@ -1,11 +1,17 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # install.sh — Cross-platform BizarHarness installer (v4.4.7 — thin shell wrapper).
3
+ # install.sh — Cross-platform BizarHarness installer (v5.x — thin shell wrapper).
4
4
  #
5
- # v4.4.7Unified installer/updater. Almost all logic now lives in
6
- # `cli/provision.mjs:runProvision`, which is shared between `bizar install`
7
- # and `bizar update`. This bash script exists only for the platform-specific
8
- # steps that need sudo + a system package manager:
5
+ # v5.xUpdated for issue #7. The install/update flow now:
6
+ # 1. On first install: registers the system service that auto-starts
7
+ # the dashboard (`bizar service install`).
8
+ # 2. On update: stops the running service before files are replaced
9
+ # (`bizar service stop` → npm upgrade → `bizar service restart`).
10
+ #
11
+ # Almost all logic still lives in `cli/provision.mjs:runProvision`, which
12
+ # is shared between `bizar install` and `bizar update`. This bash script
13
+ # exists only for the platform-specific steps that need sudo + a system
14
+ # package manager:
9
15
  #
10
16
  # - Linux: ensure node is installed (apt/dnf/pacman/zypper), install uv,
11
17
  # python3.12, jq, gh, browser-harness, Chrome runtime libs.
@@ -20,7 +26,7 @@
20
26
  # --non-interactive skip prompts (CI safe)
21
27
  # --dry-run print actions, make no changes
22
28
  # --force overwrite existing files
23
- # --update this is a re-install (currently a no-op alias)
29
+ # --update this is a re-install
24
30
  # --mode=install|update|install-only-system
25
31
  # install = full flow (the default)
26
32
  # update = alias for install (provisioner auto-detects)
@@ -145,6 +151,21 @@ ensure_node() {
145
151
  action "Installing Node.js via zypper..."
146
152
  dry $SUDO zypper install -y nodejs20 npm20
147
153
  ;;
154
+ alpine)
155
+ action "Installing Node.js via apk..."
156
+ dry $SUDO apk add --no-cache nodejs npm
157
+ ;;
158
+ nixos)
159
+ action "Detected NixOS — Node.js must be installed via nix-shell"
160
+ warn "Run the following, then re-run this installer:"
161
+ warn " nix-shell -p nodejs python3 jq gh git"
162
+ warn " node cli/provision.mjs"
163
+ exit 0
164
+ ;;
165
+ void)
166
+ action "Installing Node.js via xbps..."
167
+ dry $SUDO xbps-install -S nodejs
168
+ ;;
148
169
  *)
149
170
  err "Unsupported distro: $ID"
150
171
  err "Install Node.js 18+ manually: https://nodejs.org/"
@@ -168,6 +189,29 @@ check_deps() {
168
189
  fi
169
190
  }
170
191
 
192
+ install_lightrag() {
193
+ # LightRAG is optional but recommended. Install via uv tool.
194
+ # uv tools install to ~/.local/bin/ — ensure that's on PATH.
195
+ if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
196
+ export PATH="$HOME/.local/bin:$PATH"
197
+ fi
198
+ if have_cmd lightrag-server; then
199
+ note "LightRAG $(lightrag-server --version 2>/dev/null || echo 'server') present"
200
+ return 0
201
+ fi
202
+ action "Installing LightRAG via uv tool..."
203
+ if dry uv tool install "lightrag-hku[api]" 2>&1; then
204
+ note "LightRAG installed"
205
+ # Verify reachable
206
+ if [ -f "$HOME/.local/bin/lightrag-server" ]; then
207
+ note "LightRAG binary found at ~/.local/bin/"
208
+ fi
209
+ else
210
+ warn "LightRAG install failed — the memory graph will not be available"
211
+ warn " Install later: uv tool install \"lightrag-hku[api]\""
212
+ fi
213
+ }
214
+
171
215
  install_missing_deps_linux() {
172
216
  . /etc/os-release 2>/dev/null || return 0
173
217
  sudo_if_needed
@@ -206,7 +250,32 @@ install_missing_deps_linux() {
206
250
  note "uv installed via astral.sh"
207
251
  fi
208
252
  ;;
253
+ alpine)
254
+ action "Installing uv, python3, jq, gh via apk..."
255
+ dry $SUDO apk add --no-cache python3 py3-pip jq git curl
256
+ if ! have_cmd uv; then
257
+ dry curl -LsSf https://astral.sh/uv/install.sh | sh
258
+ note "uv installed via astral.sh"
259
+ fi
260
+ ;;
261
+ void)
262
+ action "Installing uv, python3, jq, gh via xbps..."
263
+ dry $SUDO xbps-install -S python3 python3-pip jq git curl
264
+ if ! have_cmd uv; then
265
+ dry curl -LsSf https://astral.sh/uv/install.sh | sh
266
+ note "uv installed via astral.sh"
267
+ fi
268
+ ;;
269
+ nixos)
270
+ warn "NixOS detected — LightRAG and other uv tools require manual setup"
271
+ warn " After nix-shell: nix-shell -p nodejs python3 jq gh git"
272
+ warn " Then run: uv tool install \"lightrag-hku[api]\""
273
+ warn " And re-run this installer: node cli/provision.mjs"
274
+ return 0
275
+ ;;
209
276
  esac
277
+ # LightRAG via uv tool (requires uv to be on PATH)
278
+ install_lightrag
210
279
  }
211
280
 
212
281
  install_missing_deps_macos() {
@@ -222,6 +291,8 @@ install_missing_deps_macos() {
222
291
  fi
223
292
  action "Installing uv, python3.12, jq, gh via brew..."
224
293
  dry brew install uv jq gh 2>/dev/null || true
294
+ # LightRAG via uv tool (requires uv to be on PATH)
295
+ install_lightrag
225
296
  }
226
297
 
227
298
  # ── Service registration (delegated to Node) ────────────────────────────────
@@ -230,6 +301,12 @@ install_service() {
230
301
  # The unified provisioner does this via `cli/service-controller.mjs`.
231
302
  # We still call it from here because the bash script may run standalone
232
303
  # (e.g. for first-boot provisioning before the npm package is set up).
304
+ #
305
+ # v5.x — issue #7: On first install, register the service so the
306
+ # dashboard auto-starts at login. On update, the running service is
307
+ # stopped by the provisioner before files are replaced, then restarted
308
+ # by the provisioner after — see cli/provision.mjs. This function
309
+ # covers the FIRST install path only.
233
310
  local bin="$REPO_DIR/cli/bin.mjs"
234
311
  if [ ! -f "$bin" ]; then
235
312
  warn "cli/bin.mjs not found — service registration deferred"
@@ -240,6 +317,10 @@ install_service() {
240
317
  dim " would run: node $bin service install"
241
318
  return 0
242
319
  fi
320
+ if [ "$UPDATE_MODE" -eq 1 ]; then
321
+ dim " update mode: service restart is handled by the Node provisioner"
322
+ return 0
323
+ fi
243
324
  if ! node "$bin" service install 2>&1; then
244
325
  warn "service registration had issues — see output above"
245
326
  dim " manual command: node $bin service install"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -119,8 +119,18 @@ import { createBgSpawnTool } from "./src/tools/bg-spawn.js";
119
119
  import { createBgStatusTool } from "./src/tools/bg-status.js";
120
120
  import { createBgCollectTool } from "./src/tools/bg-collect.js";
121
121
  import { createBgKillTool } from "./src/tools/bg-kill.js";
122
+ import { createBgPauseTool } from "./src/tools/bg-pause.js";
123
+ import { createBgResumeTool } from "./src/tools/bg-resume.js";
124
+ import { createBgReportProgressTool } from "./src/tools/bg-report-progress.js";
125
+ import { createBgSendMessageTool } from "./src/tools/bg-send-message.js";
122
126
  import { createBgGetCommentsTool } from "./src/tools/bg-get-comments.js";
123
127
  import { createOpenKbTool } from "./src/tools/open-kb.js";
128
+ import { createMemorySearchTool } from "./src/tools/memory-search.js";
129
+ import { createMemoryReadTool } from "./src/tools/memory-read.js";
130
+ import { createMemoryWriteTool } from "./src/tools/memory-write.js";
131
+ import { createMemoryListTool } from "./src/tools/memory-list.js";
132
+ import { createMemoryInject, popMemoryContext } from "./src/hooks/memory-inject.js";
133
+ import { createMemoryWriteOnEnd } from "./src/hooks/memory-write-on-end.js";
124
134
 
125
135
  // v0.4.0 — visual plan flow: settings, slash commands, plan tools
126
136
  import { SettingsStore } from "./src/settings.js";
@@ -360,10 +370,25 @@ interface RuntimeContext {
360
370
  seenMessageIds: Map<string, Set<string>>;
361
371
  /** sessionID → pending system-transform message, set at warn/escalate. */
362
372
  pendingInjections: Map<string, string>;
373
+ /** §Memory — sessionIDs that have already received memory context. */
374
+ injectedSessions: Set<string>;
363
375
  /** v0.7.0-alpha.1 — Dashboard publisher (or null if disabled/not started).
364
376
  * Used by the `event` hook to forward opencode session lifecycle
365
377
  * events to the v2 dashboard via the @polderlabs/bizar-sdk. */
366
378
  dashboardPublisher: DashboardPublisher | null;
379
+ /** §Memory — injects relevant memory context at session start. */
380
+ memoryInject: (sessionID: string, firstMessage: string) => Promise<void>;
381
+ /** §Memory — writes session summary to memory vault on session end. */
382
+ memoryWriteOnEnd: (sessionID: string, info: {
383
+ sessionID: string;
384
+ agent: string;
385
+ startedAt: number;
386
+ endedAt: number;
387
+ status: "idle" | "error" | "killed";
388
+ error?: string;
389
+ }, conversationPreview: string) => Promise<void>;
390
+ /** §Memory — session start timestamps for duration calculation on session end. */
391
+ sessionStartTimes: Map<string, number>;
367
392
  }
368
393
 
369
394
  /**
@@ -652,6 +677,22 @@ let bgAvailable = false;
652
677
 
653
678
  installSignalHandlers(logger, instanceManager, serve, stream, options.stateDir);
654
679
 
680
+ // §Memory — compute injectedSessions and memoryInject early so they can
681
+ // be embedded directly in the ctx object literal (avoids TS2741).
682
+ const injectedSessions = new Set<string>();
683
+ const sessionStartTimes = new Map<string, number>();
684
+ const { memoryInject } = createMemoryInject({
685
+ injectedSessions,
686
+ worktree: input.worktree,
687
+ logger,
688
+ enabled: true, // TODO: gate on settings.memory.injectOnSessionStart
689
+ });
690
+ const { memoryWriteOnEnd } = createMemoryWriteOnEnd({
691
+ worktree: input.worktree,
692
+ logger,
693
+ enabled: true, // TODO: gate on settings.memory.writeOnSessionEnd
694
+ });
695
+
655
696
  const ctx: RuntimeContext = {
656
697
  logger,
657
698
  options,
@@ -663,7 +704,11 @@ let bgAvailable = false;
663
704
  directory: input.directory,
664
705
  seenMessageIds: new Map(),
665
706
  pendingInjections: new Map(),
707
+ injectedSessions,
666
708
  dashboardPublisher,
709
+ memoryInject,
710
+ memoryWriteOnEnd,
711
+ sessionStartTimes,
667
712
  };
668
713
 
669
714
  return buildHooks(ctx, { instanceManager, bgAvailable });
@@ -983,6 +1028,25 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
983
1028
  worktree: ctx.worktree,
984
1029
  logger: ctx.logger,
985
1030
  }),
1031
+
1032
+ // §Memory — Bizar Memory tools. Available to all agents; no serve
1033
+ // child required. Worktree is passed for context (vault discovery).
1034
+ bizar_memory_search: createMemorySearchTool({
1035
+ worktree: ctx.worktree,
1036
+ logger: ctx.logger,
1037
+ }),
1038
+ bizar_memory_read: createMemoryReadTool({
1039
+ worktree: ctx.worktree,
1040
+ logger: ctx.logger,
1041
+ }),
1042
+ bizar_memory_write: createMemoryWriteTool({
1043
+ worktree: ctx.worktree,
1044
+ logger: ctx.logger,
1045
+ }),
1046
+ bizar_memory_list: createMemoryListTool({
1047
+ worktree: ctx.worktree,
1048
+ logger: ctx.logger,
1049
+ }),
986
1050
  };
987
1051
  const tools = bg.instanceManager
988
1052
  ? {
@@ -992,6 +1056,10 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
992
1056
  // src/opencode-runner.ts). The serve child is still
993
1057
  // available for the dashboard's v2 protocol and for any
994
1058
  // TUI/web client that wants to attach to it.
1059
+ //
1060
+ // v5.x — added pause/resume/steer/progress tools for
1061
+ // dashboard integration. See tools/bg-{pause,resume,
1062
+ // report-progress,send-message}.ts.
995
1063
  bizar_spawn_background: createBgSpawnTool({
996
1064
  instanceManager: bg.instanceManager,
997
1065
  worktree: ctx.worktree,
@@ -1009,6 +1077,23 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
1009
1077
  instanceManager: bg.instanceManager,
1010
1078
  logger: ctx.logger,
1011
1079
  }),
1080
+ bizar_pause: createBgPauseTool({
1081
+ instanceManager: bg.instanceManager,
1082
+ logger: ctx.logger,
1083
+ }),
1084
+ bizar_resume: createBgResumeTool({
1085
+ instanceManager: bg.instanceManager,
1086
+ logger: ctx.logger,
1087
+ }),
1088
+ bizar_send_message: createBgSendMessageTool({
1089
+ instanceManager: bg.instanceManager,
1090
+ logger: ctx.logger,
1091
+ }),
1092
+ // Available to ALL agents (the body of a bg agent calls it).
1093
+ bizar_report_progress: createBgReportProgressTool({
1094
+ instanceManager: bg.instanceManager,
1095
+ logger: ctx.logger,
1096
+ }),
1012
1097
  }
1013
1098
  : {
1014
1099
  ...basePlanTools,
@@ -1040,6 +1125,14 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
1040
1125
  output.system.push(pending);
1041
1126
  ctx.pendingInjections.delete(sessionID);
1042
1127
  }
1128
+
1129
+ // §Memory — inject relevant memory context at the start of the
1130
+ // session (first turn only). `popMemoryContext` is idempotent
1131
+ // (marks session as injected on first call).
1132
+ const memCtx = popMemoryContext(sessionID);
1133
+ if (memCtx) {
1134
+ output.system.push(memCtx);
1135
+ }
1043
1136
  },
1044
1137
 
1045
1138
  // Before the model is called, strip `` blocks from
@@ -1150,7 +1243,61 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
1150
1243
  });
1151
1244
  ctx.pendingInjections.delete(sessionID);
1152
1245
  ctx.seenMessageIds.delete(sessionID);
1246
+ ctx.injectedSessions.delete(sessionID);
1247
+ ctx.sessionStartTimes.delete(sessionID);
1248
+ }
1249
+
1250
+ // §Memory — on session start, fetch relevant memory context and
1251
+ // queue it for injection via `popMemoryContext` in the transform hook.
1252
+ if (type === "session.created") {
1253
+ const ev2 = ev as {
1254
+ properties?: {
1255
+ sessionID?: string;
1256
+ startedAt?: number;
1257
+ [k: string]: unknown;
1258
+ };
1259
+ messageText?: string;
1260
+ firstMessage?: string;
1261
+ };
1262
+ // Record start time for session-end duration calculation.
1263
+ const startedAt = typeof ev2.properties?.startedAt === "number"
1264
+ ? ev2.properties.startedAt
1265
+ : Date.now();
1266
+ ctx.sessionStartTimes.set(sessionID, startedAt);
1267
+ // Extract the first user message text — different opencode versions
1268
+ // surface this in different places; try several keys.
1269
+ const firstMessage =
1270
+ typeof ev2.messageText === "string"
1271
+ ? ev2.messageText
1272
+ : typeof ev2.firstMessage === "string"
1273
+ ? ev2.firstMessage
1274
+ : typeof ev.properties?.messageText === "string"
1275
+ ? ev.properties.messageText
1276
+ : "";
1277
+ void ctx.memoryInject(sessionID, firstMessage);
1153
1278
  }
1279
+
1280
+ // §Memory — on session end, write an automated session summary to the
1281
+ // memory vault. Fire-and-forget — failures are logged but never throw.
1282
+ if (type === "session.idle" || type === "session.error") {
1283
+ const startedAt = ctx.sessionStartTimes.get(sessionID) ?? Date.now();
1284
+ const endedAt = Date.now();
1285
+ const errorProp = (ev as { properties?: { error?: string } }).properties?.error;
1286
+ void ctx.memoryWriteOnEnd(
1287
+ sessionID,
1288
+ {
1289
+ sessionID,
1290
+ agent: (ev as { properties?: { agent?: string } }).properties?.agent ?? "unknown",
1291
+ startedAt,
1292
+ endedAt,
1293
+ status: type === "session.idle" ? "idle" : "error",
1294
+ error: type === "session.error" ? (errorProp ?? "unknown") : undefined,
1295
+ },
1296
+ "",
1297
+ );
1298
+ ctx.sessionStartTimes.delete(sessionID);
1299
+ }
1300
+
1154
1301
  // Other event types are no-ops on the hook side. The state file
1155
1302
  // is updated by `chat.message` and `tool.execute.before/after`.
1156
1303
  } catch (err) {