@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
package/cli/memory.mjs CHANGED
@@ -698,7 +698,7 @@ function addRemoteToVault(projectRoot, remote) {
698
698
  return;
699
699
  }
700
700
 
701
- const addResult = memoryGit.addRemote(vaultRoot, 'origin', remote);
701
+ const addResult = memoryGit.addRemote(vaultRoot, 'origin', remote, { overwrite: true });
702
702
  if (!addResult || addResult.ok === false) {
703
703
  warn(`failed to register remote: ${addResult?.error || 'unknown error'}`);
704
704
  info('the remote URL is still saved in .bizar/memory.json');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "5.5.0",
3
+ "version": "5.5.2",
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": {
@@ -246,6 +246,17 @@ export interface BackgroundState {
246
246
  // v0.8.0 — process tracking (see opencode-runner.ts).
247
247
  processId?: number;
248
248
  exitCode?: number;
249
+ // v5.5.1 — `liveSession: true` means this instance is backed by an
250
+ // opencode serve SDK session managed by the dashboard (not by an OS
251
+ // subprocess). The plugin's bg-kill/bg-pause/bg-resume tools check
252
+ // this flag and delegate to the dashboard HTTP API instead of
253
+ // touching the (now-stubbed) opencode-runner. Optional for backward
254
+ // compat — pre-v5.5.1 state files leave it unset / false.
255
+ liveSession?: boolean;
256
+ // v5.5.1 — instanceId assigned by the dashboard (may differ from the
257
+ // plugin's locally-allocated `instanceId` when the dashboard mints its
258
+ // own). Cross-reference for cross-system debugging.
259
+ dashboardInstanceId?: string;
249
260
  runnerState?: string;
250
261
  runnerError?: string;
251
262
  spawnMessage?: string;
@@ -79,6 +79,100 @@ import { researchInterventionPrompt } from "./research-prompt.js";
79
79
 
80
80
  import * as opencodeRunner from "./opencode-runner.js";
81
81
 
82
+ // --- v5.5.1 — dashboard HTTP helpers -------------------------------------
83
+ //
84
+ // Background agents run as opencode serve SDK sessions managed by the
85
+ // dashboard. The plugin's `pause`/`resume` (and the tool layer's
86
+ // `kill`/`send-message`) therefore talk to the dashboard over HTTP
87
+ // instead of touching the OS or the opencode SDK directly.
88
+ //
89
+ // These helpers are intentionally tiny — they only need to do POSTs
90
+ // for `pause`/`resume`. The token + URL resolution matches what
91
+ // `bg-spawn.ts` and `bg-send-message.ts` do.
92
+
93
+ import { readFileSync, existsSync } from "node:fs";
94
+ import { join } from "node:path";
95
+ import { homedir } from "node:os";
96
+
97
+ function resolveDashboardUrl(): string {
98
+ const fromEnv = process.env.BIZAR_DASHBOARD_URL;
99
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim().replace(/\/+$/, "");
100
+ const port = process.env.BIZAR_DASHBOARD_PORT;
101
+ if (port && /^\d+$/.test(port)) return `http://127.0.0.1:${port}`;
102
+ return "http://127.0.0.1:4098";
103
+ }
104
+
105
+ const DEFAULT_DASHBOARD_AUTH_PATHS = [
106
+ join(homedir(), ".config", "bizar", "dashboard-secret"),
107
+ join(homedir(), ".cache", "bizarharness", "dash-auth.json"),
108
+ join(homedir(), ".cache", "bizar", "dash-auth.json"),
109
+ ];
110
+
111
+ function readDashboardToken(): string {
112
+ for (const candidate of DEFAULT_DASHBOARD_AUTH_PATHS) {
113
+ try {
114
+ if (!existsSync(candidate)) continue;
115
+ const text = readFileSync(candidate, "utf-8").trim();
116
+ if (text && text.length >= 16) return text;
117
+ } catch {
118
+ /* ignore */
119
+ }
120
+ }
121
+ for (const candidate of DEFAULT_DASHBOARD_AUTH_PATHS) {
122
+ try {
123
+ if (!existsSync(candidate)) continue;
124
+ const parsed = JSON.parse(readFileSync(candidate, "utf-8")) as { password?: unknown };
125
+ if (typeof parsed.password === "string" && parsed.password.length >= 16) {
126
+ return parsed.password;
127
+ }
128
+ } catch {
129
+ /* ignore */
130
+ }
131
+ }
132
+ return "";
133
+ }
134
+
135
+ /**
136
+ * POST to the dashboard with a JSON `{}` body. Used for pause/resume —
137
+ * no payload, just the URL. Returns `{ ok: true }` on 2xx, `{ ok: false,
138
+ * error }` on transport failure or non-2xx (so the caller can surface
139
+ * a structured error without catching exceptions).
140
+ */
141
+ async function postDashboardControl(
142
+ path: string,
143
+ logger: Logger,
144
+ ): Promise<{ ok: boolean; error?: string }> {
145
+ const url = `${resolveDashboardUrl()}${path}`;
146
+ const token = readDashboardToken();
147
+ const headers: Record<string, string> = {
148
+ "content-type": "application/json",
149
+ accept: "application/json",
150
+ };
151
+ if (token) headers.authorization = `Bearer ${token}`;
152
+ const ac = new AbortController();
153
+ const timer = setTimeout(() => ac.abort(), 5_000);
154
+ try {
155
+ const res = await fetch(url, {
156
+ method: "POST",
157
+ headers,
158
+ body: JSON.stringify({}),
159
+ signal: ac.signal,
160
+ });
161
+ if (!res.ok) {
162
+ const text = await res.text().catch(() => "");
163
+ logger.warn(`bizar: dashboard ${path} returned ${res.status}: ${text.slice(0, 200)}`);
164
+ return { ok: false, error: `dashboard_http_${res.status}` };
165
+ }
166
+ return { ok: true };
167
+ } catch (err) {
168
+ const msg = err instanceof Error ? err.message : String(err);
169
+ logger.warn(`bizar: dashboard ${path} failed: ${msg}`);
170
+ return { ok: false, error: msg };
171
+ } finally {
172
+ clearTimeout(timer);
173
+ }
174
+ }
175
+
82
176
  // --- Public surface -------------------------------------------------------
83
177
 
84
178
  /** A snapshot of an instance for the `bizar_status` tool. */
@@ -466,6 +560,13 @@ export class InstanceManager {
466
560
  * Abort the opencode session and mark the instance `killed`. If the
467
561
  * instance is already in a terminal state, this is a no-op (spec §1.5,
468
562
  * MEDIUM-40).
563
+ *
564
+ * v5.5.1 — SDK-backed instances are killed via `DELETE /api/background/:id`
565
+ * on the dashboard (which calls `sdk.sessions.abort`). Pre-v5.5.1
566
+ * fallback: bg-only mode marks the instance killed in-memory and lets
567
+ * the runner notice; full mode (with HTTP client) calls
568
+ * `http.abortSession` directly. The two paths are now functionally
569
+ * equivalent — the dashboard IS the SDK client in v5.5.1.
469
570
  */
470
571
  async kill(instanceId: string): Promise<void> {
471
572
  const inst = this.instances.get(instanceId);
@@ -476,6 +577,34 @@ export class InstanceManager {
476
577
  );
477
578
  return;
478
579
  }
580
+ // v5.5.1 — SDK-backed: ask the dashboard to abort.
581
+ if (inst.liveSession) {
582
+ const url = `${resolveDashboardUrl()}/api/background/${encodeURIComponent(instanceId)}`;
583
+ const token = readDashboardToken();
584
+ const headers: Record<string, string> = { accept: "application/json" };
585
+ if (token) headers.authorization = `Bearer ${token}`;
586
+ const ac = new AbortController();
587
+ const timer = setTimeout(() => ac.abort(), 5_000);
588
+ try {
589
+ await fetch(url, { method: "DELETE", headers, signal: ac.signal });
590
+ } catch (err) {
591
+ this.logger.warn(
592
+ `bizar: kill(${instanceId}) dashboard DELETE failed: ${
593
+ err instanceof Error ? err.message : String(err)
594
+ }`,
595
+ );
596
+ } finally {
597
+ clearTimeout(timer);
598
+ }
599
+ await this.update(instanceId, {
600
+ status: "killed",
601
+ completedAt: Date.now(),
602
+ });
603
+ this.logger.info(
604
+ `bizar: killed background instance ${instanceId} (sdk-backed, dashboard DELETE)`,
605
+ );
606
+ return;
607
+ }
479
608
  // v0.8.0 — bg-only mode has no HTTP client. The subprocess is
480
609
  // already owned by opencode-runner.ts (see src/tools/bg-spawn.ts);
481
610
  // mark the instance killed in-memory and let the runner notice the
@@ -493,16 +622,12 @@ export class InstanceManager {
493
622
  });
494
623
  return;
495
624
  }
496
- // Abort the opencode session. The next SSE event for this session
497
- // (EventSessionIdle or EventSessionError) will finalize the status.
625
+ // Pre-v5.5.1 fallback direct abort on the legacy serve child.
498
626
  const abort = await this.http.abortSession(inst.sessionId, this.worktree);
499
627
  if (!abort.ok) {
500
628
  this.logger.warn(
501
629
  `bizar: kill(${instanceId}): abort failed: ${abort.error}`,
502
630
  );
503
- // Even if the abort call failed, we still want the in-memory state
504
- // to reflect a deliberate kill so the user sees it. The next SSE
505
- // event will overwrite if it disagrees.
506
631
  }
507
632
  await this.update(instanceId, {
508
633
  status: "killed",
@@ -734,20 +859,18 @@ export class InstanceManager {
734
859
  * Scan the bg directory, load every instance, and rebuild the in-memory
735
860
  * map. Any `running` or `pending` instance is checked for liveness:
736
861
  *
737
- * - If the instance has a `processId` AND the opencode run subprocess
738
- * is still alive (per `opencodeRunner.isAlive()`), we RE-ADOPT it:
739
- * status flips back to `running`, `runnerState: "adopted"`, and the
740
- * the manager continues to monitor via the existing
741
- * `onExit` callback registered at spawn time.
742
- * - If the subprocess is gone (or there was no PID on file), we mark
743
- * the instance `failed` with reason "subprocess died during restart".
862
+ * - v5.5.1: bg instances no longer have OS subprocesses they live
863
+ * as opencode serve SDK sessions on the dashboard. `opencodeRunner.
864
+ * isAlive(pid)` always returns `false`, so the adoption branch is
865
+ * effectively dead code; we keep it for the rare case a state file
866
+ * from v5.5.0 (with a real pid) survives an upgrade. We mark the
867
+ * instance `failed` with reason `"v5.5.1 session reconciliation
868
+ * pending"` so the operator knows to look at the dashboard; the
869
+ * dashboard's own SSE bridge will reconcile.
870
+ * - v5.5.0 and earlier: a still-alive PID re-adopts as before.
744
871
  *
745
872
  * Historical terminal records (done, failed, killed, timed_out, steered)
746
873
  * are preserved as-is.
747
- *
748
- * Best-effort: liveness check uses `process.kill(pid, 0)` which is fast
749
- * (no signal is sent) and safe (returns false on EPERM / ESRCH without
750
- * raising to us).
751
874
  */
752
875
  async rebuildInMemoryMap(): Promise<void> {
753
876
  let all: BackgroundState[];
@@ -768,7 +891,26 @@ export class InstanceManager {
768
891
  this.instances.set(inst.instanceId, inst);
769
892
  rebuilt += 1;
770
893
  if (inst.status === "running" || inst.status === "pending") {
771
- // Try to adopt a still-alive subprocess.
894
+ // v5.5.1 prefer the liveSession flag (the SDK-session marker)
895
+ // when present. If the dashboard still tracks the session, we
896
+ // re-adopt; otherwise we mark failed with a reconcilable message.
897
+ if (inst.liveSession) {
898
+ // SDK-backed: we trust the state file. The dashboard's SSE
899
+ // bridge will deliver the next event. Mark `running` and
900
+ // `runnerState: "adopted"` so the operator sees the plugin
901
+ // has caught up.
902
+ await this.update(inst.instanceId, {
903
+ status: "running",
904
+ completedAt: undefined,
905
+ runnerState: "adopted",
906
+ });
907
+ adopted += 1;
908
+ this.logger.info(
909
+ `bizar: re-adopted SDK-backed bg instance ${inst.instanceId}`,
910
+ );
911
+ continue;
912
+ }
913
+ // Pre-v5.5.1 subprocess fallback (rare; the runner is stubbed).
772
914
  const pid = inst.processId;
773
915
  const alive = typeof pid === "number" && opencodeRunner.isAlive(pid);
774
916
  if (alive) {
@@ -1046,10 +1188,16 @@ export class InstanceManager {
1046
1188
  // --- v5.x — pause / resume / progress / tool-call instrumentation -------
1047
1189
 
1048
1190
  /**
1049
- * Pause a running instance. Sends SIGSTOP to the opencode run subprocess
1050
- * (POSIX only — returns `{ ok: false, error: "..." }` on Windows and
1051
- * for processes we can't signal). On success, the in-memory status
1052
- * flips to `"paused"` and `pausedAt` is stamped.
1191
+ * Pause a running instance. In v5.5.1 (SDK-backed) this delegates to
1192
+ * the dashboard's `POST /api/background/:id/pause`, which performs an
1193
+ * OUTPUT pause (stops forwarding events to the WS bus; the underlying
1194
+ * opencode serve session keeps running). For pre-v5.5.1 instances
1195
+ * that still carry a `processId`, fall back to the old SIGSTOP path
1196
+ * via the (now stubbed) runner — best-effort, returns ok:false if the
1197
+ * runner reports the OS no longer supports it.
1198
+ *
1199
+ * On success, the in-memory status flips to `"paused"` and `pausedAt`
1200
+ * is stamped.
1053
1201
  *
1054
1202
  * Already-terminal instances return `{ ok: false, error: "already_terminal" }`.
1055
1203
  * Already-paused instances return `{ ok: true }` (no-op).
@@ -1061,18 +1209,28 @@ export class InstanceManager {
1061
1209
  return { ok: false, error: "already_terminal" };
1062
1210
  }
1063
1211
  if (inst.status === "paused") return { ok: true };
1064
- if (inst.processId === undefined) {
1065
- // No PID on file for v0.8.0 the runner is the only owner of the
1066
- // subprocess. Try the runner's registry as a fallback before giving up.
1067
- const runnerStatus = opencodeRunner.getStatus(NaN as unknown as number);
1068
- if (runnerStatus === null) {
1069
- return { ok: false, error: "no_subprocess" };
1212
+
1213
+ // v5.5.1SDK-backed: delegate to the dashboard.
1214
+ if (inst.liveSession) {
1215
+ const res = await postDashboardControl(`/api/background/${encodeURIComponent(instanceId)}/pause`, this.logger);
1216
+ if (!res.ok) {
1217
+ return { ok: false, error: res.error || "pause_failed" };
1070
1218
  }
1219
+ await this.update(instanceId, {
1220
+ status: "paused",
1221
+ pausedAt: Date.now(),
1222
+ });
1223
+ this.logger.info(`bizar: paused background instance ${instanceId} (sdk-backed)`);
1224
+ return { ok: true };
1071
1225
  }
1072
- const pid = inst.processId;
1073
- if (pid === undefined) {
1226
+
1227
+ // Pre-v5.5.1 fallback: subprocess SIGSTOP. The runner is a stub now,
1228
+ // but if a state file from v5.5.0 is still around we try the old
1229
+ // path for compatibility.
1230
+ if (inst.processId === undefined) {
1074
1231
  return { ok: false, error: "no_subprocess" };
1075
1232
  }
1233
+ const pid = inst.processId;
1076
1234
  const res = opencodeRunner.pauseAgent(pid);
1077
1235
  if (!res.ok) return { ok: false, error: res.error || "pause_failed" };
1078
1236
  await this.update(instanceId, {
@@ -1080,20 +1238,40 @@ export class InstanceManager {
1080
1238
  pausedAt: Date.now(),
1081
1239
  });
1082
1240
  this.logger.info(
1083
- `bizar: paused background instance ${instanceId} (pid=${pid})`,
1241
+ `bizar: paused background instance ${instanceId} (pid=${pid}, legacy)`,
1084
1242
  );
1085
1243
  return { ok: true };
1086
1244
  }
1087
1245
 
1088
1246
  /**
1089
- * Resume a paused instance. Sends SIGCONT to the subprocess and flips
1090
- * status back to `"running"`. Idempotent: resuming a non-paused
1091
- * instance is a no-op that returns `{ ok: true }`.
1247
+ * Resume a paused instance. In v5.5.1 (SDK-backed) delegates to the
1248
+ * dashboard's `POST /api/background/:id/resume`, which drains the
1249
+ * buffered events back to the WS bus. For pre-v5.5.1 instances with
1250
+ * a `processId`, falls back to the old SIGCONT path via the runner.
1251
+ *
1252
+ * Idempotent: resuming a non-paused instance is a no-op that returns
1253
+ * `{ ok: true }`.
1092
1254
  */
1093
1255
  async resume(instanceId: string): Promise<{ ok: boolean; error?: string }> {
1094
1256
  const inst = this.instances.get(instanceId);
1095
1257
  if (!inst) return { ok: false, error: "instance_not_found" };
1096
1258
  if (inst.status !== "paused") return { ok: true };
1259
+
1260
+ // v5.5.1 — SDK-backed: delegate to the dashboard.
1261
+ if (inst.liveSession) {
1262
+ const res = await postDashboardControl(`/api/background/${encodeURIComponent(instanceId)}/resume`, this.logger);
1263
+ if (!res.ok) {
1264
+ return { ok: false, error: res.error || "resume_failed" };
1265
+ }
1266
+ await this.update(instanceId, {
1267
+ status: "running",
1268
+ pausedAt: undefined,
1269
+ });
1270
+ this.logger.info(`bizar: resumed background instance ${instanceId} (sdk-backed)`);
1271
+ return { ok: true };
1272
+ }
1273
+
1274
+ // Pre-v5.5.1 fallback: subprocess SIGCONT.
1097
1275
  if (inst.processId === undefined) {
1098
1276
  return { ok: false, error: "no_subprocess" };
1099
1277
  }
@@ -1104,7 +1282,7 @@ export class InstanceManager {
1104
1282
  pausedAt: undefined,
1105
1283
  });
1106
1284
  this.logger.info(
1107
- `bizar: resumed background instance ${instanceId} (pid=${inst.processId})`,
1285
+ `bizar: resumed background instance ${instanceId} (pid=${inst.processId}, legacy)`,
1108
1286
  );
1109
1287
  return { ok: true };
1110
1288
  }