@polderlabs/bizar 5.5.0 → 5.5.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 (71) hide show
  1. package/bizar-dash/dist/assets/{EnvVarsSection-L413CQEI.js → EnvVarsSection-DoIzqrlb.js} +3 -3
  2. package/bizar-dash/dist/assets/{EnvVarsSection-L413CQEI.js.map → EnvVarsSection-DoIzqrlb.js.map} +1 -1
  3. package/bizar-dash/dist/assets/{MobileChat-0kGQb1S6.js → MobileChat-CoUCUsbr.js} +1 -1
  4. package/bizar-dash/dist/assets/{MobileChat-0kGQb1S6.js.map → MobileChat-CoUCUsbr.js.map} +1 -1
  5. package/bizar-dash/dist/assets/MobileSettings-BeO04g52.js +1 -0
  6. package/bizar-dash/dist/assets/MobileSettings-BeO04g52.js.map +1 -0
  7. package/bizar-dash/dist/assets/{Toast-C6f1zFJ2.js → Toast-D9nv5N6m.js} +1 -1
  8. package/bizar-dash/dist/assets/{Toast-C6f1zFJ2.js.map → Toast-D9nv5N6m.js.map} +1 -1
  9. package/bizar-dash/dist/assets/{icons-BsWXWkEF.js → icons-Btipv2pp.js} +100 -90
  10. package/bizar-dash/dist/assets/icons-Btipv2pp.js.map +1 -0
  11. package/bizar-dash/dist/assets/main-DHXPuddY.js +18 -0
  12. package/bizar-dash/dist/assets/main-DHXPuddY.js.map +1 -0
  13. package/bizar-dash/dist/assets/{main-BklYy02e.css → main-UG4jKK-F.css} +1 -1
  14. package/bizar-dash/dist/assets/mobile-H5KH9cTL.js +1 -0
  15. package/bizar-dash/dist/assets/{mobile-CsaN4CxH.js.map → mobile-H5KH9cTL.js.map} +1 -1
  16. package/bizar-dash/dist/assets/{mobile-layout-iFBdjEE4.js → mobile-layout-BqHxVb6_.js} +2 -2
  17. package/bizar-dash/dist/assets/{mobile-layout-iFBdjEE4.js.map → mobile-layout-BqHxVb6_.js.map} +1 -1
  18. package/bizar-dash/dist/assets/{useSlashCommands-DGJcC9iQ.js → useSlashCommands-C24-O1e7.js} +2 -2
  19. package/bizar-dash/dist/assets/{useSlashCommands-DGJcC9iQ.js.map → useSlashCommands-C24-O1e7.js.map} +1 -1
  20. package/bizar-dash/dist/index.html +7 -7
  21. package/bizar-dash/dist/mobile.html +3 -3
  22. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  23. package/bizar-dash/src/server/bg-spawner.mjs +522 -305
  24. package/bizar-dash/src/server/headroom.mjs +3 -1
  25. package/bizar-dash/src/server/memory-lightrag.mjs +26 -2
  26. package/bizar-dash/src/server/memory-store.mjs +74 -28
  27. package/bizar-dash/src/server/opencode-sdk.mjs +63 -3
  28. package/bizar-dash/src/server/plugins/registry.mjs +1 -1
  29. package/bizar-dash/src/server/routes/activity.mjs +85 -0
  30. package/bizar-dash/src/server/routes/background.mjs +31 -13
  31. package/bizar-dash/src/server/routes/memory.mjs +91 -38
  32. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +1 -1
  33. package/bizar-dash/src/server/server.mjs +4 -1
  34. package/bizar-dash/src/web/components/Sidebar.tsx +0 -1
  35. package/bizar-dash/src/web/styles/main.css +52 -13
  36. package/bizar-dash/src/web/styles/memory.css +17 -0
  37. package/bizar-dash/src/web/styles/settings.css +6 -56
  38. package/bizar-dash/src/web/views/Marketplace.tsx +192 -31
  39. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +157 -7
  40. package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +11 -0
  41. package/bizar-dash/src/web/views/settings/MemorySection.tsx +65 -11
  42. package/bizar-dash/tests/background-sdk-session.test.mjs +94 -0
  43. package/bizar-dash/tests/background-session-events.test.mjs +74 -0
  44. package/bizar-dash/tests/background-steer-sdk.test.mjs +71 -0
  45. package/bizar-dash/tests/background-steer.test.mjs +25 -7
  46. package/bizar-dash/tests/memory-namespace.test.mjs +2 -2
  47. package/bizar-dash/tests/memory-status-root.test.mjs +144 -0
  48. package/bizar-dash/tests/memory-store.test.mjs +5 -4
  49. package/bizar-dash/tests/memory-vault-config-endpoint.test.mjs +173 -0
  50. package/bizar-dash/tests/settings-mode-wiring.test.tsx +1 -1
  51. package/bizar-dash/tests/settings-nav.test.tsx +0 -1
  52. package/bizar-dash/tests/views/Marketplace.test.tsx +200 -0
  53. package/bizar-dash/tests/views/Memory.test.tsx +274 -0
  54. package/bizar-dash/tests/views/sidebar.test.tsx +116 -0
  55. package/bizar-dash/tests/views/topbar.test.tsx +87 -0
  56. package/cli/commands/marketplace.mjs +1 -1
  57. package/cli/memory.mjs +1 -1
  58. package/package.json +1 -1
  59. package/plugins/bizar/src/background-state.ts +11 -0
  60. package/plugins/bizar/src/background.ts +212 -34
  61. package/plugins/bizar/src/opencode-runner.ts +80 -353
  62. package/plugins/bizar/src/tools/bg-send-message.ts +160 -61
  63. package/plugins/bizar/src/tools/bg-spawn.ts +235 -156
  64. package/plugins/bizar/tests/tools/bg-send-message.test.ts +84 -11
  65. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +223 -0
  66. package/bizar-dash/dist/assets/MobileSettings-Cq1oFIdO.js +0 -1
  67. package/bizar-dash/dist/assets/MobileSettings-Cq1oFIdO.js.map +0 -1
  68. package/bizar-dash/dist/assets/icons-BsWXWkEF.js.map +0 -1
  69. package/bizar-dash/dist/assets/main-CBTgYSeG.js +0 -18
  70. package/bizar-dash/dist/assets/main-CBTgYSeG.js.map +0 -1
  71. package/bizar-dash/dist/assets/mobile-CsaN4CxH.js +0 -1
@@ -1,66 +1,139 @@
1
1
  /**
2
2
  * plugins/bizar/src/tools/bg-send-message.ts
3
3
  *
4
- * v5.x — `bizar_send_message` tool. Mid-flight redirect for a
5
- * background agent — "steer" the running instance with a new
6
- * instruction.
4
+ * v5.5.1 — `bizar_send_message` tool. TRUE mid-flight steer.
7
5
  *
8
- * ────────────────────────────────────────────────────────────────
9
- * v0.8.0 DESIGN NOTE why this tool is a structured no-op
10
- * ────────────────────────────────────────────────────────────────
11
- * In the opencode 1.17 `serve` HTTP API (used by bg-spawn up to v0.7)
12
- * you could `POST /session/{id}/prompt` to send a follow-up message
13
- * to a running session the loop would pick it up mid-run and start
14
- * a new turn. That's the "real" steer path.
6
+ * In v5.5.0 this tool returned `unavailable_in_subprocess_mode` because
7
+ * `opencode run` is a one-shot CLI. v5.5.1 switched bg agents to
8
+ * long-lived opencode serve SDK sessions, so this tool now delegates
9
+ * to `POST /api/background/<id>/steer` on the dashboard. The dashboard
10
+ * calls `sdk.sessions.prompt()` on the live session, which the
11
+ * opencode serve child picks up mid-loop as the next user turn.
15
12
  *
16
- * In v0.8.0 the plugin switched to `opencode run`, which is a
17
- * one-shot CLI: it accepts the prompt on the command line, runs the
18
- * agent loop to completion, and exits. There is no documented HTTP /
19
- * IPC channel for sending a follow-up user message mid-run. The
20
- * subprocess doesn't expose one and we don't own the opencode source.
13
+ * Wire contract (mirrors `bizar-dash/src/server/routes/background.mjs`):
14
+ * - Request: `POST /api/background/:id/steer` with body `{ message }`.
15
+ * - Response: `{ ok, mode: 'true_midflight', newInstanceId: null, instanceId, steerCount }`.
21
16
  *
22
- * Two paths to a workable steer:
23
- *
24
- * 1. **Switch back to HTTP server mode** — every bg agent would
25
- * start its own `opencode serve` + a server-side streaming
26
- * prompt. Heavyweight (extra processes, double the memory) and
27
- * a 1-2 sprint piece of work.
28
- *
29
- * 2. **Kill + restart with appended prompt** — graceful shutdown
30
- * of the current subprocess, spin up a new one whose prompt is
31
- * `(original) + "\n\n[STEERED " + ISO timestamp + "]\n" + msg`.
32
- * Cheaper; matches the user's mental model of "send new
33
- * instruction"; implemented in the dashboard at
34
- * `POST /api/background/:id/steer`.
35
- *
36
- * We document the constraint here. The tool itself returns the
37
- * concrete error and the recommended path so the agent can either
38
- * (a) ask Odin to use the dashboard steer endpoint or (b) call
39
- * `bizar_kill` + `bizar_spawn_background` with a combined prompt as
40
- * a backup.
41
- *
42
- * In a future v0.9.x this tool will be promoted to the real
43
- * implementation once opencode run supports mid-flight prompting.
17
+ * Errors surface as `{ ok: false, error }` so callers can react.
44
18
  */
45
19
  import { tool } from "@opencode-ai/plugin";
46
20
  import { z } from "zod";
47
21
 
48
22
  import type { InstanceManager } from "../background.js";
49
23
  import type { Logger } from "../logger.js";
24
+ import { readFileSync, existsSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import { homedir } from "node:os";
50
27
 
51
28
  export interface BgSendMessageDeps {
52
29
  instanceManager: InstanceManager;
53
30
  logger: Logger;
31
+ /**
32
+ * Optional injection point for tests. When provided, the dashboard
33
+ * HTTP call is short-circuited and the test function is called with
34
+ * the resolved request shape.
35
+ */
36
+ _dashboardPost?: (url: string, init: { headers: Record<string, string>; body: string }) => Promise<{ ok: boolean; status: number; json: () => Promise<unknown> }>;
37
+ }
38
+
39
+ // --- Dashboard URL + token (mirrors bg-spawn.ts) ------------------------
40
+
41
+ function resolveDashboardUrl(): string {
42
+ const fromEnv = process.env.BIZAR_DASHBOARD_URL;
43
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim().replace(/\/+$/, "");
44
+ const port = process.env.BIZAR_DASHBOARD_PORT;
45
+ if (port && /^\d+$/.test(port)) return `http://127.0.0.1:${port}`;
46
+ return "http://127.0.0.1:4098";
47
+ }
48
+
49
+ const DEFAULT_DASHBOARD_AUTH_PATHS = [
50
+ join(homedir(), ".config", "bizar", "dashboard-secret"),
51
+ join(homedir(), ".cache", "bizarharness", "dash-auth.json"),
52
+ join(homedir(), ".cache", "bizar", "dash-auth.json"),
53
+ ];
54
+
55
+ function readDashboardToken(): string {
56
+ for (const candidate of DEFAULT_DASHBOARD_AUTH_PATHS) {
57
+ try {
58
+ if (!existsSync(candidate)) continue;
59
+ const text = readFileSync(candidate, "utf-8").trim();
60
+ if (text && text.length >= 16) return text;
61
+ } catch {
62
+ /* ignore */
63
+ }
64
+ }
65
+ for (const candidate of DEFAULT_DASHBOARD_AUTH_PATHS) {
66
+ try {
67
+ if (!existsSync(candidate)) continue;
68
+ const parsed = JSON.parse(readFileSync(candidate, "utf-8")) as { password?: unknown };
69
+ if (typeof parsed.password === "string" && parsed.password.length >= 16) {
70
+ return parsed.password;
71
+ }
72
+ } catch {
73
+ /* ignore */
74
+ }
75
+ }
76
+ return "";
77
+ }
78
+
79
+ async function postJsonToDashboard(
80
+ url: string,
81
+ body: unknown,
82
+ override?: BgSendMessageDeps["_dashboardPost"],
83
+ ): Promise<unknown> {
84
+ if (override) {
85
+ const res = await override(url, {
86
+ headers: { "content-type": "application/json" },
87
+ body: JSON.stringify(body),
88
+ });
89
+ if (!res.ok) {
90
+ const text = await res.json().catch(() => ({}));
91
+ const err = new Error(
92
+ `dashboard HTTP ${res.status}: ${JSON.stringify(text).slice(0, 200)}`,
93
+ );
94
+ (err as Error & { httpStatus?: number }).httpStatus = res.status;
95
+ throw err;
96
+ }
97
+ return res.json();
98
+ }
99
+ const token = readDashboardToken();
100
+ const headers: Record<string, string> = {
101
+ "content-type": "application/json",
102
+ accept: "application/json",
103
+ };
104
+ if (token) headers.authorization = `Bearer ${token}`;
105
+
106
+ const ac = new AbortController();
107
+ const timer = setTimeout(() => ac.abort(), 8_000);
108
+ try {
109
+ const res = await fetch(url, {
110
+ method: "POST",
111
+ headers,
112
+ body: JSON.stringify(body),
113
+ signal: ac.signal,
114
+ });
115
+ if (!res.ok) {
116
+ const text = await res.text().catch(() => "");
117
+ const err = new Error(
118
+ `dashboard HTTP ${res.status}: ${text.slice(0, 200)}`,
119
+ );
120
+ (err as Error & { httpStatus?: number }).httpStatus = res.status;
121
+ throw err;
122
+ }
123
+ return res.json();
124
+ } finally {
125
+ clearTimeout(timer);
126
+ }
54
127
  }
55
128
 
56
129
  export function createBgSendMessageTool(deps: BgSendMessageDeps) {
57
130
  return tool({
58
131
  description:
59
132
  "Send a follow-up message to a running background agent. " +
60
- "v0.8.0 LIMITATION: opencode run is a one-shot CLI and does not " +
61
- "support mid-flight prompting. This tool returns an `unavailable_in_subprocess_mode` " +
62
- "error and points the caller to the dashboard's steer endpoint, " +
63
- "which performs kill+restart-with-appended-prompt.",
133
+ "v5.5.1: TRUE mid-flight steer via the opencode serve SDK. " +
134
+ "The same instance keeps running; the new message becomes the next user turn " +
135
+ "of the running opencode session. No kill+respawn, no [STEERED <ts>] marker, " +
136
+ "no loss of agent context.",
64
137
  args: {
65
138
  instanceId: z
66
139
  .string()
@@ -71,9 +144,9 @@ export function createBgSendMessageTool(deps: BgSendMessageDeps) {
71
144
  .min(1)
72
145
  .describe("The follow-up instruction to send."),
73
146
  },
74
- execute: async (rawArgs, ctx) => {
147
+ execute: async (rawArgs, _ctx) => {
75
148
  const args = rawArgs as { instanceId: string; message: string };
76
- // Verify the instance exists so we don't silently accept bogus ids.
149
+ // 1. Verify the instance exists so we don't silently accept bogus ids.
77
150
  const inst = await deps.instanceManager.get(args.instanceId);
78
151
  if (!inst) {
79
152
  return {
@@ -83,23 +156,49 @@ export function createBgSendMessageTool(deps: BgSendMessageDeps) {
83
156
  }),
84
157
  };
85
158
  }
86
- deps.logger.debug(
87
- `bizar: sendMessage(${args.instanceId})mid-flight steer unavailable in v0.8.0 subprocess mode`,
88
- );
89
- void ctx;
90
- return {
91
- output: JSON.stringify({
92
- error: "unavailable_in_subprocess_mode",
93
- message:
94
- "v0.8.0 bg agents run as `opencode run` subprocesses and cannot accept a follow-up prompt mid-flight. " +
95
- "Use the dashboard's `POST /api/background/<id>/steer` (Steer button on instance detail) which performs " +
96
- "kill+restart with the new instruction appended to the original prompt. " +
97
- "Or, if running headlessly, you can call `bizar_kill` followed by `bizar_spawn_background` " +
98
- "with a combined prompt yourself.",
99
- instanceId: args.instanceId,
100
- dashboardHint: `POST /api/background/${args.instanceId}/steer`,
101
- }),
102
- };
159
+
160
+ // 2. v5.5.1 delegate to the dashboard. The dashboard owns the
161
+ // opencode SDK and calls `sdk.sessions.prompt({...})` on the
162
+ // live session, which the opencode serve child picks up
163
+ // mid-loop as the next user turn.
164
+ const dashboardUrl = `${resolveDashboardUrl()}/api/background/${encodeURIComponent(args.instanceId)}/steer`;
165
+ try {
166
+ const data = (await postJsonToDashboard(
167
+ dashboardUrl,
168
+ { message: args.message },
169
+ deps._dashboardPost,
170
+ )) as { ok: boolean; mode?: string; steerCount?: number; error?: string };
171
+ if (!data || data.ok !== true) {
172
+ return {
173
+ output: JSON.stringify({
174
+ error: data?.error || "steer_failed",
175
+ instanceId: args.instanceId,
176
+ }),
177
+ };
178
+ }
179
+ deps.logger.info(
180
+ `bizar: sendMessage(${args.instanceId}) — TRUE mid-flight steer #${data.steerCount ?? "?"} accepted`,
181
+ );
182
+ return {
183
+ output: JSON.stringify({
184
+ ok: true,
185
+ mode: data.mode || "true_midflight",
186
+ instanceId: args.instanceId,
187
+ steerCount: data.steerCount ?? 0,
188
+ message:
189
+ "Steer delivered to the running opencode session. The agent will pick up your message as the next user turn — no kill+respawn was performed, no context was lost.",
190
+ }),
191
+ };
192
+ } catch (err: unknown) {
193
+ const msg = err instanceof Error ? err.message : String(err);
194
+ deps.logger.warn(`bizar: sendMessage(${args.instanceId}) failed: ${msg}`);
195
+ return {
196
+ output: JSON.stringify({
197
+ error: `steer_failed: ${msg}`,
198
+ instanceId: args.instanceId,
199
+ }),
200
+ };
201
+ }
103
202
  },
104
203
  });
105
- }
204
+ }