@polderlabs/bizar-plugin 0.6.0 → 0.6.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.
- package/README.md +1 -1
- package/index.ts +60 -3
- package/package.json +1 -1
- package/src/background-state.ts +41 -0
- package/src/background.ts +147 -11
- package/src/commands-impl.ts +4 -4
- package/src/commands.ts +278 -101
- package/src/reasoning-clean.ts +360 -0
- package/src/serve.ts +12 -3
- package/src/tools/bg-spawn.ts +21 -1
- package/tests/attach-handler-bug.test.ts +5 -3
- package/tests/background-state.test.ts +1 -1
- package/tests/background.test.ts +1 -1
- package/tests/block.test.ts +3 -1
- package/tests/canonical-key-order.test.ts +11 -7
- package/tests/event.test.ts +1 -1
- package/tests/fingerprint.test.ts +22 -21
- package/tests/http-client.test.ts +5 -3
- package/tests/options.test.ts +10 -8
- package/tests/settings.test.ts +2 -2
- package/tests/stall-think.test.ts +13 -12
- package/tests/state.test.ts +2 -1
- package/tests/tools/bg-spawn.test.ts +12 -12
- package/tests/update-deadlock.test.ts +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ The plugin starts one `opencode serve` process on init (single-serve, multi-sess
|
|
|
155
155
|
const result = await bizarre_spawn_background({
|
|
156
156
|
agent: "mimir", // which agent to run
|
|
157
157
|
prompt: "Research X and return findings", // what to do
|
|
158
|
-
model: "minimax
|
|
158
|
+
model: "openrouter/minimax-m3", // optional: override model
|
|
159
159
|
timeoutMs: 300_000, // optional: default 5 min, max 30 min
|
|
160
160
|
}, ctx);
|
|
161
161
|
console.log(result.instanceId); // "bgr_01ARSH3J5V..."
|
package/index.ts
CHANGED
|
@@ -125,9 +125,12 @@ import { SettingsStore } from "./src/settings.js";
|
|
|
125
125
|
import { parseSlashCommand } from "./src/commands.js";
|
|
126
126
|
import { createPlanActionTool } from "./src/tools/plan-action.js";
|
|
127
127
|
import { createWaitForFeedbackTool } from "./src/tools/wait-for-feedback.js";
|
|
128
|
+
import { wrapFetchForReasoningCleanup } from "./src/reasoning-clean.js";
|
|
128
129
|
|
|
129
130
|
// v0.5.0 — visual plan wiring: side-effect executor + plan-fs
|
|
130
131
|
import { executeSideEffect, type ExecuteOptions } from "./src/commands-impl.js";
|
|
132
|
+
import { join as pathJoin } from "node:path";
|
|
133
|
+
import { homedir } from "node:os";
|
|
131
134
|
|
|
132
135
|
// --- Env-var constants (per spec §8) -------------------------------------
|
|
133
136
|
|
|
@@ -753,9 +756,40 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
753
756
|
};
|
|
754
757
|
|
|
755
758
|
return {
|
|
756
|
-
// §3.1 — config:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
+
// §3.1 — config: wrap provider fetches to strip duplicated inline
|
|
760
|
+
// think blocks from responses of reasoning models that emit BOTH a
|
|
761
|
+
// structured reasoning field (rendered as a thought) AND an inline
|
|
762
|
+
// `` block (which would otherwise leak into the visible message).
|
|
763
|
+
// See plugins/bizar/src/reasoning-clean.ts for the full rationale.
|
|
764
|
+
config: async (cfg) => {
|
|
765
|
+
try {
|
|
766
|
+
const providers = (cfg as { provider?: Record<string, unknown> } | undefined)?.provider;
|
|
767
|
+
if (!providers || typeof providers !== "object") return;
|
|
768
|
+
const debug = (msg: string) => ctx.logger.debug(`bizar: ${msg}`);
|
|
769
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
770
|
+
if (!provider || typeof provider !== "object") continue;
|
|
771
|
+
const prov = provider as { options?: Record<string, unknown> };
|
|
772
|
+
if (!prov.options || typeof prov.options !== "object") continue;
|
|
773
|
+
const original = prov.options.fetch;
|
|
774
|
+
if (typeof original !== "function") continue;
|
|
775
|
+
// Only wrap once — detect by stamping a sentinel.
|
|
776
|
+
const wrapped = (original as { __bizarReasoningClean?: boolean })
|
|
777
|
+
.__bizarReasoningClean;
|
|
778
|
+
if (wrapped) continue;
|
|
779
|
+
prov.options.fetch = wrapFetchForReasoningCleanup(
|
|
780
|
+
original as Parameters<typeof wrapFetchForReasoningCleanup>[0],
|
|
781
|
+
{ debug, providers: [name] },
|
|
782
|
+
);
|
|
783
|
+
(prov.options.fetch as { __bizarReasoningClean?: boolean }).__bizarReasoningClean = true;
|
|
784
|
+
debug(`wrapped provider.fetch for ${name}`);
|
|
785
|
+
}
|
|
786
|
+
} catch (err) {
|
|
787
|
+
ctx.logger.warn(
|
|
788
|
+
`bizar: config hook failed (passing through): ${
|
|
789
|
+
err instanceof Error ? err.message : String(err)
|
|
790
|
+
}`,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
759
793
|
},
|
|
760
794
|
|
|
761
795
|
// §3.1, §4.5.1 — event: track session boundaries. We do NOT create
|
|
@@ -862,6 +896,29 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
862
896
|
finalResponse = `Command failed: ${msg}`;
|
|
863
897
|
}
|
|
864
898
|
}
|
|
899
|
+
// --- v0.5.1: dialog support.
|
|
900
|
+
// If the command emitted a dialog descriptor, persist it to disk
|
|
901
|
+
// so Tyr's dialog-poller can broadcast it to the dashboard.
|
|
902
|
+
// We return without throwing so no chat bubble is shown.
|
|
903
|
+
if (result.dialog) {
|
|
904
|
+
// S4 — defense-in-depth: validate ID shape before touching disk.
|
|
905
|
+
if (!/^dlg_[a-zA-Z0-9_-]{1,64}$/.test(result.dialog.id)) {
|
|
906
|
+
return; // silently drop malformed dialog IDs
|
|
907
|
+
}
|
|
908
|
+
try {
|
|
909
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
910
|
+
const dialogDir = pathJoin(homedir(), ".cache", "bizar", "dialogs");
|
|
911
|
+
await mkdir(dialogDir, { recursive: true });
|
|
912
|
+
await writeFile(
|
|
913
|
+
pathJoin(dialogDir, `${result.dialog.id}.json`),
|
|
914
|
+
JSON.stringify({ ...result.dialog, createdAt: new Date().toISOString() }, null, 2),
|
|
915
|
+
);
|
|
916
|
+
} catch (dialogErr: unknown) {
|
|
917
|
+
const msg = dialogErr instanceof Error ? dialogErr.message : String(dialogErr);
|
|
918
|
+
ctx.logger.warn(`bizar: failed to write dialog file: ${msg}`);
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
865
922
|
// Surface the response to the user/host. We throw so the
|
|
866
923
|
// message is treated as handled; the LLM does not process
|
|
867
924
|
// it further. The host renders the throw message.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Bizar opencode plugin — loop detection, status reporting, handoff signal, background agents, and slash commands + visual plan flow for subagent activity",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
package/src/background-state.ts
CHANGED
|
@@ -99,6 +99,15 @@ export type BackgroundStatus =
|
|
|
99
99
|
* - `interventionAt` — epoch ms of the most recent intervention.
|
|
100
100
|
* - `interventionReason` — short human-readable description of the
|
|
101
101
|
* intervention, e.g. `"thinking loop (5m 12s without tool/text)"`.
|
|
102
|
+
*
|
|
103
|
+
* v0.5.5 — persistent auto-restart. These are typed as optional so
|
|
104
|
+
* existing state files on disk remain valid after upgrade.
|
|
105
|
+
* - `persistent` — when true, the manager auto-restarts on terminal
|
|
106
|
+
* failure (up to maxRestarts). Default false.
|
|
107
|
+
* - `restartCount` — number of times this instance has been
|
|
108
|
+
* auto-restarted (not including the original spawn). Default 0.
|
|
109
|
+
* - `maxRestarts` — cap; default 3.
|
|
110
|
+
* - `lastRestartAt` — epoch ms of the most recent auto-restart.
|
|
102
111
|
*/
|
|
103
112
|
export interface BackgroundState {
|
|
104
113
|
instanceId: string;
|
|
@@ -127,6 +136,23 @@ export interface BackgroundState {
|
|
|
127
136
|
interventionCount?: number;
|
|
128
137
|
interventionAt?: number;
|
|
129
138
|
interventionReason?: string;
|
|
139
|
+
// v0.5.5 — persistent auto-restart
|
|
140
|
+
persistent?: boolean;
|
|
141
|
+
restartCount?: number;
|
|
142
|
+
maxRestarts?: number;
|
|
143
|
+
lastRestartAt?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Full original prompt text, stored so the instance can be restarted
|
|
146
|
+
* with the same input. Optional for backward compat; always set on
|
|
147
|
+
* new spawns from v0.5.5+.
|
|
148
|
+
*/
|
|
149
|
+
prompt?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Human-readable error from the last failed restart attempt.
|
|
152
|
+
* Set only when `_maybeAutoRestart` calls `restart()` and it returns
|
|
153
|
+
* `{ ok: false }`. Cleared on a successful restart.
|
|
154
|
+
*/
|
|
155
|
+
restartError?: string;
|
|
130
156
|
}
|
|
131
157
|
|
|
132
158
|
/**
|
|
@@ -159,6 +185,10 @@ export const EMPTY_BACKGROUND_STATE: Omit<
|
|
|
159
185
|
lastEventAt: 0,
|
|
160
186
|
lastToolOrTextAt: 0,
|
|
161
187
|
interventionCount: 0,
|
|
188
|
+
// v0.5.5 — persistent auto-restart defaults
|
|
189
|
+
persistent: false,
|
|
190
|
+
restartCount: 0,
|
|
191
|
+
maxRestarts: 3,
|
|
162
192
|
};
|
|
163
193
|
|
|
164
194
|
/**
|
|
@@ -314,6 +344,17 @@ function readState(
|
|
|
314
344
|
if (typeof parsed.interventionCount !== "number") {
|
|
315
345
|
parsed.interventionCount = 0;
|
|
316
346
|
}
|
|
347
|
+
// v0.5.5 — backfill persistent/restart fields for files written by
|
|
348
|
+
// older versions. These are optional but the restarter needs them.
|
|
349
|
+
if (typeof parsed.persistent !== "boolean") {
|
|
350
|
+
parsed.persistent = false;
|
|
351
|
+
}
|
|
352
|
+
if (typeof parsed.restartCount !== "number") {
|
|
353
|
+
parsed.restartCount = 0;
|
|
354
|
+
}
|
|
355
|
+
if (typeof parsed.maxRestarts !== "number") {
|
|
356
|
+
parsed.maxRestarts = 3;
|
|
357
|
+
}
|
|
317
358
|
return parsed;
|
|
318
359
|
} catch (err: unknown) {
|
|
319
360
|
logger.log({
|
package/src/background.ts
CHANGED
|
@@ -278,6 +278,30 @@ export class InstanceManager {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
// --- Internal dispatch (shared by add() and restart()) ------------------
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Insert a fully-constructed BackgroundState into the in-memory map
|
|
285
|
+
* and persist to disk asynchronously. Does NOT check the concurrency
|
|
286
|
+
* cap — the caller handles that.
|
|
287
|
+
*
|
|
288
|
+
* This is extracted from `add()` so `restart()` can reuse the same
|
|
289
|
+
* insert-and-persist path without duplicating the logic.
|
|
290
|
+
*/
|
|
291
|
+
private dispatchInternal(full: BackgroundState): BackgroundState {
|
|
292
|
+
this.instances.set(full.instanceId, full);
|
|
293
|
+
// Persist asynchronously; failure is logged but does not roll back
|
|
294
|
+
// the in-memory insert (the instance is "tracked" either way).
|
|
295
|
+
this.stateStore.save(full).catch((err: unknown) => {
|
|
296
|
+
this.logger.warn(
|
|
297
|
+
`bizar: failed to persist new instance ${full.instanceId}: ${
|
|
298
|
+
err instanceof Error ? err.message : String(err)
|
|
299
|
+
}`,
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
return full;
|
|
303
|
+
}
|
|
304
|
+
|
|
281
305
|
// --- Atomic add (spec §2.2) ---------------------------------------------
|
|
282
306
|
|
|
283
307
|
/**
|
|
@@ -315,16 +339,7 @@ export class InstanceManager {
|
|
|
315
339
|
lastToolOrTextAt: now,
|
|
316
340
|
interventionCount: 0,
|
|
317
341
|
};
|
|
318
|
-
this.
|
|
319
|
-
// Persist asynchronously; failure is logged but does not roll back
|
|
320
|
-
// the in-memory insert (the instance is "tracked" either way).
|
|
321
|
-
this.stateStore.save(full).catch((err: unknown) => {
|
|
322
|
-
this.logger.warn(
|
|
323
|
-
`bizar: failed to persist new instance ${draft.instanceId}: ${
|
|
324
|
-
err instanceof Error ? err.message : String(err)
|
|
325
|
-
}`,
|
|
326
|
-
);
|
|
327
|
-
});
|
|
342
|
+
this.dispatchInternal(full);
|
|
328
343
|
// BUGFIX (v0.5.1): Do NOT call attachEventHandler() here. The
|
|
329
344
|
// instance was just added with sessionId="" (filled in later by
|
|
330
345
|
// POST /session). EventStream.onSessionEvent rejects empty strings,
|
|
@@ -426,6 +441,86 @@ export class InstanceManager {
|
|
|
426
441
|
});
|
|
427
442
|
this.logger.info(`bizar: killed background instance ${instanceId}`);
|
|
428
443
|
}
|
|
444
|
+
|
|
445
|
+
// --- Restart (v0.5.5 — persistent auto-restart) ------------------------
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Re-spawn a failed persistent instance with the same prompt, agent,
|
|
449
|
+
* and model config. The new instance gets a fresh `instanceId` and is
|
|
450
|
+
* linked to the original via `parentInstanceId`.
|
|
451
|
+
*
|
|
452
|
+
* Returns `{ ok: true, newInstanceId }` on success, or
|
|
453
|
+
* `{ ok: false, error: "..." }` on failure. Does NOT check the
|
|
454
|
+
* concurrency cap — the original instance is terminal, so its slot
|
|
455
|
+
* is already freed.
|
|
456
|
+
*/
|
|
457
|
+
async restart(instanceId: string): Promise<{
|
|
458
|
+
ok: boolean;
|
|
459
|
+
newInstanceId?: string;
|
|
460
|
+
error?: string;
|
|
461
|
+
}> {
|
|
462
|
+
const existing = this.instances.get(instanceId);
|
|
463
|
+
if (!existing) return { ok: false, error: "instance_not_found" };
|
|
464
|
+
const totalRestarts = this.getTotalRestartCount(instanceId);
|
|
465
|
+
if (totalRestarts >= (existing.maxRestarts ?? 3)) {
|
|
466
|
+
return { ok: false, error: "max_restarts_reached" };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const now = Date.now();
|
|
470
|
+
const newInstanceId = generateInstanceId();
|
|
471
|
+
const full: BackgroundState = {
|
|
472
|
+
instanceId: newInstanceId,
|
|
473
|
+
sessionId: "",
|
|
474
|
+
agent: existing.agent,
|
|
475
|
+
model: existing.model,
|
|
476
|
+
promptPreview: (existing.prompt ?? existing.promptPreview ?? "").slice(0, PROMPT_PREVIEW_MAX),
|
|
477
|
+
prompt: existing.prompt,
|
|
478
|
+
parentAgent: existing.parentAgent,
|
|
479
|
+
parentInstanceId: instanceId,
|
|
480
|
+
logPath: `${this.worktree}/.opencode/log/${newInstanceId}.log`,
|
|
481
|
+
timeoutMs: existing.timeoutMs,
|
|
482
|
+
toolCallCount: 0,
|
|
483
|
+
// v0.5.5 — persist the auto-restart fields
|
|
484
|
+
persistent: existing.persistent,
|
|
485
|
+
maxRestarts: existing.maxRestarts,
|
|
486
|
+
restartCount: (existing.restartCount ?? 0) + 1,
|
|
487
|
+
status: "pending",
|
|
488
|
+
startedAt: now,
|
|
489
|
+
lastEventAt: now,
|
|
490
|
+
lastToolOrTextAt: now,
|
|
491
|
+
interventionCount: 0,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
this.dispatchInternal(full);
|
|
495
|
+
this.logger.info(
|
|
496
|
+
`bizar: restarted instance ${instanceId} as ${newInstanceId} (restart #${full.restartCount})`,
|
|
497
|
+
);
|
|
498
|
+
return { ok: true, newInstanceId };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Walk the parent chain to compute the true restart count for this
|
|
503
|
+
* instance (Forseti C4). `restartCount` alone only reflects the child's
|
|
504
|
+
* own counter, so a chain of restarts can exceed `maxRestarts`. This
|
|
505
|
+
* helper sums the counters across the entire chain (parent + all
|
|
506
|
+
* descendants) and returns the total.
|
|
507
|
+
*/
|
|
508
|
+
private getTotalRestartCount(instanceId: string): number {
|
|
509
|
+
let current = this.instances.get(instanceId);
|
|
510
|
+
if (!current) return 0;
|
|
511
|
+
let count = current.restartCount ?? 0;
|
|
512
|
+
let parentId = current.parentInstanceId;
|
|
513
|
+
const visited = new Set<string>([instanceId]);
|
|
514
|
+
while (parentId && !visited.has(parentId)) {
|
|
515
|
+
visited.add(parentId);
|
|
516
|
+
const parent = this.instances.get(parentId);
|
|
517
|
+
if (!parent) break;
|
|
518
|
+
count += parent.restartCount ?? 0;
|
|
519
|
+
parentId = parent.parentInstanceId;
|
|
520
|
+
}
|
|
521
|
+
return count;
|
|
522
|
+
}
|
|
523
|
+
|
|
429
524
|
// --- Collect ------------------------------------------------------------
|
|
430
525
|
|
|
431
526
|
/**
|
|
@@ -634,6 +729,8 @@ export class InstanceManager {
|
|
|
634
729
|
error: `No activity for ${this.stallTimeoutMs}ms — LLM appears stalled`,
|
|
635
730
|
completedAt: Date.now(),
|
|
636
731
|
});
|
|
732
|
+
// v0.5.5 — persistent auto-restart on stall
|
|
733
|
+
await this._maybeAutoRestart(inst.instanceId);
|
|
637
734
|
}
|
|
638
735
|
|
|
639
736
|
/**
|
|
@@ -704,6 +801,8 @@ export class InstanceManager {
|
|
|
704
801
|
error: `Thinking loop detected: ${formatDuration(sinceMs)} of thinking without tool calls or output. Spawn a Mimir agent for research.`,
|
|
705
802
|
completedAt: Date.now(),
|
|
706
803
|
});
|
|
804
|
+
// v0.5.5 — persistent auto-restart on thinking-loop exhaustion
|
|
805
|
+
await this._maybeAutoRestart(inst.instanceId);
|
|
707
806
|
}
|
|
708
807
|
|
|
709
808
|
// --- Internal: per-session event handler -------------------------------
|
|
@@ -759,6 +858,37 @@ export class InstanceManager {
|
|
|
759
858
|
error: errMsg,
|
|
760
859
|
completedAt: Date.now(),
|
|
761
860
|
});
|
|
861
|
+
// v0.5.5 — persistent auto-restart. If the instance is persistent
|
|
862
|
+
// and was not explicitly killed, try to restart.
|
|
863
|
+
if (inst.persistent && inst.status !== "killed") {
|
|
864
|
+
await this._maybeAutoRestart(instanceId);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** v0.5.5 — Attempt an auto-restart for a persistent failed instance. */
|
|
870
|
+
private async _maybeAutoRestart(instanceId: string): Promise<void> {
|
|
871
|
+
const inst = this.instances.get(instanceId);
|
|
872
|
+
if (!inst || !inst.persistent) return;
|
|
873
|
+
if (inst.status === "killed") return; // user killed it — do not restart
|
|
874
|
+
this.logger.info(
|
|
875
|
+
`bizar: persistent instance ${instanceId} failed; auto-restarting`,
|
|
876
|
+
);
|
|
877
|
+
const result = await this.restart(instanceId);
|
|
878
|
+
if (result.ok) {
|
|
879
|
+
// Clear any previous restartError on the parent so the operator
|
|
880
|
+
// sees a clean state.
|
|
881
|
+
await this.update(instanceId, { restartError: undefined });
|
|
882
|
+
this.logger.info(
|
|
883
|
+
`bizar: auto-restart complete: ${instanceId} -> ${result.newInstanceId}`,
|
|
884
|
+
);
|
|
885
|
+
} else {
|
|
886
|
+
// Persist a human-readable error so the operator can see why the
|
|
887
|
+
// restart was rejected (e.g. max_restarts_reached).
|
|
888
|
+
await this.update(instanceId, { restartError: result.error || "unknown" });
|
|
889
|
+
this.logger.warn(
|
|
890
|
+
`bizar: auto-restart failed for ${instanceId}: ${result.error}`,
|
|
891
|
+
);
|
|
762
892
|
}
|
|
763
893
|
}
|
|
764
894
|
|
|
@@ -800,7 +930,11 @@ export class InstanceManager {
|
|
|
800
930
|
patch.completedAt = Date.now();
|
|
801
931
|
}
|
|
802
932
|
await this.update(instanceId, patch);
|
|
803
|
-
|
|
933
|
+
// v0.5.5 — persistent auto-restart on tool-call cap
|
|
934
|
+
if (patch.status === "failed") {
|
|
935
|
+
await this._maybeAutoRestart(instanceId);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
804
938
|
}
|
|
805
939
|
|
|
806
940
|
// --- Loop-guard threshold-12 detection (spec §4.1) ---
|
|
@@ -816,6 +950,8 @@ export class InstanceManager {
|
|
|
816
950
|
loopGuardTool: tool,
|
|
817
951
|
completedAt: Date.now(),
|
|
818
952
|
});
|
|
953
|
+
// v0.5.5 — persistent auto-restart on loop-guard
|
|
954
|
+
await this._maybeAutoRestart(instanceId);
|
|
819
955
|
return;
|
|
820
956
|
}
|
|
821
957
|
}
|
package/src/commands-impl.ts
CHANGED
|
@@ -112,7 +112,7 @@ export interface ExecuteResult {
|
|
|
112
112
|
* list so we can include status + lastEdited).
|
|
113
113
|
* `open_plan_url` — just returns the parser's response (the parser
|
|
114
114
|
* already built the URL). No I/O.
|
|
115
|
-
* `launch_dashboard` — spawns `bizar
|
|
115
|
+
* `launch_dashboard` — spawns `bizar dash start` as a detached
|
|
116
116
|
* child process. Reads the port file back and
|
|
117
117
|
* appends the URL to the parser's response.
|
|
118
118
|
* `tool_invocation` — delegates to `executeToolInvocation`.
|
|
@@ -196,7 +196,7 @@ async function executeListPlans(
|
|
|
196
196
|
/**
|
|
197
197
|
* Launch the Bizar dashboard as a detached child process.
|
|
198
198
|
*
|
|
199
|
-
|
|
199
|
+
* We spawn `bizar dash start` with `detached: true` and `unref()`
|
|
200
200
|
* so the child's lifetime is independent of the plugin host. We then
|
|
201
201
|
* poll the port file (written by the child) for up to ~3s and append
|
|
202
202
|
* the URL to the parser's response. If anything goes wrong we surface
|
|
@@ -234,7 +234,7 @@ async function executeLaunchDashboard(
|
|
|
234
234
|
// `bizar` is on $PATH for global installs; for npx / local installs
|
|
235
235
|
// we'd want to resolve to the package's bin. Spawn `bizar` directly
|
|
236
236
|
// for now — the user's $PATH is the source of truth.
|
|
237
|
-
const child = spawn("bizar", ["
|
|
237
|
+
const child = spawn("bizar", ["dash", "start"], {
|
|
238
238
|
detached: true,
|
|
239
239
|
stdio: "ignore",
|
|
240
240
|
cwd: ctx.worktree,
|
|
@@ -248,7 +248,7 @@ async function executeLaunchDashboard(
|
|
|
248
248
|
return {
|
|
249
249
|
responseOverride:
|
|
250
250
|
`Could not launch the Bizar dashboard: ${msg}\n` +
|
|
251
|
-
`Try running \`bizar
|
|
251
|
+
`Try running \`bizar dash start\` in your terminal.`,
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
|