@polderlabs/bizar-plugin 0.6.1 → 0.8.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.
- package/index.ts +174 -48
- package/package.json +2 -1
- package/src/background-state.ts +38 -2
- package/src/background.ts +13 -0
- package/src/commands.ts +28 -11
- package/src/dashboard-client.ts +235 -0
- package/src/event-stream.ts +32 -0
- package/src/opencode-runner.ts +362 -0
- package/src/tools/bg-spawn.ts +161 -124
- package/tests/config.test.ts +2 -2
- package/tests/dashboard-client.test.ts +159 -0
package/index.ts
CHANGED
|
@@ -98,6 +98,7 @@ import type { Plugin, Hooks, PluginInput, PluginOptions } from "@opencode-ai/plu
|
|
|
98
98
|
import { createLogger, type Logger } from "./src/logger.js";
|
|
99
99
|
import { decide, isLogOnlyWarn } from "./src/loop.js";
|
|
100
100
|
import { fingerprint } from "./src/fingerprint.js";
|
|
101
|
+
import { createDashboardPublisher, type DashboardPublisher } from "./src/dashboard-client.js";
|
|
101
102
|
import { StateStore, type SessionState } from "./src/state.js";
|
|
102
103
|
import { LogWriter } from "./src/report.js";
|
|
103
104
|
import {
|
|
@@ -125,7 +126,7 @@ import { SettingsStore } from "./src/settings.js";
|
|
|
125
126
|
import { parseSlashCommand } from "./src/commands.js";
|
|
126
127
|
import { createPlanActionTool } from "./src/tools/plan-action.js";
|
|
127
128
|
import { createWaitForFeedbackTool } from "./src/tools/wait-for-feedback.js";
|
|
128
|
-
import {
|
|
129
|
+
import { stripInlineThinkBlocks } from "./src/reasoning-clean.js";
|
|
129
130
|
|
|
130
131
|
// v0.5.0 — visual plan wiring: side-effect executor + plan-fs
|
|
131
132
|
import { executeSideEffect, type ExecuteOptions } from "./src/commands-impl.js";
|
|
@@ -244,6 +245,10 @@ interface RuntimeContext {
|
|
|
244
245
|
seenMessageIds: Map<string, Set<string>>;
|
|
245
246
|
/** sessionID → pending system-transform message, set at warn/escalate. */
|
|
246
247
|
pendingInjections: Map<string, string>;
|
|
248
|
+
/** v0.7.0-alpha.1 — Dashboard publisher (or null if disabled/not started).
|
|
249
|
+
* Used by the `event` hook to forward opencode session lifecycle
|
|
250
|
+
* events to the v2 dashboard via the @polderlabs/bizar-sdk. */
|
|
251
|
+
dashboardPublisher: DashboardPublisher | null;
|
|
247
252
|
}
|
|
248
253
|
|
|
249
254
|
/**
|
|
@@ -333,10 +338,11 @@ async function init(
|
|
|
333
338
|
|
|
334
339
|
// --- Background agents (v0.4.2) -----------------------------------------
|
|
335
340
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
341
|
+
let instanceManager: InstanceManager | null = null;
|
|
342
|
+
let serve: ServeLifecycle | null = null;
|
|
343
|
+
let stream: EventStream | null = null;
|
|
344
|
+
let dashboardPublisher: DashboardPublisher | null = null;
|
|
345
|
+
let bgAvailable = false;
|
|
340
346
|
|
|
341
347
|
if (readServeDisabled()) {
|
|
342
348
|
logger.info("bizar: background agents disabled via BIZAR_SERVE_DISABLE=1");
|
|
@@ -394,6 +400,44 @@ async function init(
|
|
|
394
400
|
});
|
|
395
401
|
streamHandle = stream;
|
|
396
402
|
|
|
403
|
+
// v0.7.0-alpha.1 — Wire dashboard publisher to the EventStream so
|
|
404
|
+
// every opencode SSE event is also published to the v2 dashboard.
|
|
405
|
+
// The publisher gracefully degrades if the dashboard is unreachable
|
|
406
|
+
// (queues, retries, warns; never throws into the plugin).
|
|
407
|
+
try {
|
|
408
|
+
const pub = createDashboardPublisher({
|
|
409
|
+
logger,
|
|
410
|
+
});
|
|
411
|
+
await pub.start();
|
|
412
|
+
dashboardPublisher = pub;
|
|
413
|
+
stream.onEvent((event) => {
|
|
414
|
+
// Translate opencode StreamEvent → DashboardEvent shape.
|
|
415
|
+
// The dashboard only cares about the wire-level (type, properties)
|
|
416
|
+
// so we forward { type, properties } directly. Cast through
|
|
417
|
+
// `unknown` because the opencode event shape doesn't exactly
|
|
418
|
+
// match the SDK's discriminated DashboardEvent — it's a
|
|
419
|
+
// forward-compatible passthrough.
|
|
420
|
+
const dashEvent = {
|
|
421
|
+
type: event.type,
|
|
422
|
+
properties: { ...event },
|
|
423
|
+
} as unknown as Parameters<DashboardPublisher["publish"]>[0];
|
|
424
|
+
void pub.publish(dashEvent);
|
|
425
|
+
});
|
|
426
|
+
// Stop the publisher when the stream is disposed.
|
|
427
|
+
const origDisconnect = stream.disconnect.bind(stream);
|
|
428
|
+
stream.disconnect = async () => {
|
|
429
|
+
await origDisconnect();
|
|
430
|
+
pub.stop();
|
|
431
|
+
};
|
|
432
|
+
logger.info("bizar: dashboard publisher wired to EventStream (v2 protocol)");
|
|
433
|
+
} catch (err) {
|
|
434
|
+
logger.warn(
|
|
435
|
+
`bizar: dashboard publisher failed to start: ${
|
|
436
|
+
err instanceof Error ? err.message : String(err)
|
|
437
|
+
}`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
397
441
|
instanceManager = new InstanceManager({
|
|
398
442
|
stateStore: bgStateStore,
|
|
399
443
|
maxConcurrent,
|
|
@@ -469,6 +513,7 @@ async function init(
|
|
|
469
513
|
directory: input.directory,
|
|
470
514
|
seenMessageIds: new Map(),
|
|
471
515
|
pendingInjections: new Map(),
|
|
516
|
+
dashboardPublisher,
|
|
472
517
|
};
|
|
473
518
|
|
|
474
519
|
return buildHooks(ctx, { instanceManager, bgAvailable });
|
|
@@ -700,6 +745,47 @@ async function listPlanSlugs(worktree: string, logger: Logger): Promise<string[]
|
|
|
700
745
|
* delegates to the runtime context and the supporting modules.
|
|
701
746
|
*/
|
|
702
747
|
function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
748
|
+
// ────────────────────────────────────────────────────────────────────
|
|
749
|
+
// v0.6.2 — Reasoning directive
|
|
750
|
+
// ────────────────────────────────────────────────────────────────────
|
|
751
|
+
// Some reasoning models (notably MiniMax M3 via OpenRouter) emit
|
|
752
|
+
// their chain-of-thought in BOTH the structured `reasoning` /
|
|
753
|
+
// `reasoning_details` field AND inline as `` blocks inside
|
|
754
|
+
// `message.content`. opencode's openrouter SDK extracts the structured
|
|
755
|
+
// reasoning correctly and renders it as a separate "Thought" panel,
|
|
756
|
+
// but it does NOT strip the inline blocks from `content`, so the user
|
|
757
|
+
// sees the same thinking text twice — once in the proper panel and
|
|
758
|
+
// again as visible message text below it.
|
|
759
|
+
//
|
|
760
|
+
// The opencode plugin API in this version does NOT trigger a
|
|
761
|
+
// `config` hook (the `wrap-fetch` workaround from v0.6.1 is dead
|
|
762
|
+
// code in current builds), so we cannot post-process the response
|
|
763
|
+
// stream. The only working hooks that can help are:
|
|
764
|
+
//
|
|
765
|
+
// 1. `experimental.chat.system.transform` — runs every turn; we
|
|
766
|
+
// push a directive telling the model to put thinking in the
|
|
767
|
+
// structured field only.
|
|
768
|
+
// 2. `experimental.chat.messages.transform` — runs before each
|
|
769
|
+
// request; we strip `` blocks from previous assistant
|
|
770
|
+
// messages so the model sees clean history and is less likely
|
|
771
|
+
// to keep emitting inline ``.
|
|
772
|
+
//
|
|
773
|
+
// Neither fixes the CURRENT response (the model has already
|
|
774
|
+
// returned), but together they strongly reduce — and in many cases
|
|
775
|
+
// eliminate — the duplication on subsequent turns.
|
|
776
|
+
const REASONING_DIRECTIVE_MARKER = "BIZAR_REASONING_DIRECTIVE_v0.6.2";
|
|
777
|
+
const REASONING_DIRECTIVE = [
|
|
778
|
+
REASONING_DIRECTIVE_MARKER,
|
|
779
|
+
"",
|
|
780
|
+
"When reasoning is enabled for this conversation, output your thinking",
|
|
781
|
+
"ONLY in the model's structured reasoning field. Do NOT emit `` blocks",
|
|
782
|
+
"inline inside your message content — the opencode host extracts the",
|
|
783
|
+
"reasoning field and renders it as a separate, collapsable \"Thought\"",
|
|
784
|
+
"panel. If you also emit the same text inline, the user will see your",
|
|
785
|
+
"thinking twice (once in the panel and once as visible message body).",
|
|
786
|
+
"Keep the actual response text in the normal content stream.",
|
|
787
|
+
].join(" ");
|
|
788
|
+
|
|
703
789
|
// Build the 7 tools. We always register them; if the serve child is
|
|
704
790
|
// not available, the background tools return a clear error. The
|
|
705
791
|
// bizar_get_plan_comments, bizar_plan_action, and
|
|
@@ -707,7 +793,7 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
707
793
|
// work regardless of the serve child's state.
|
|
708
794
|
//
|
|
709
795
|
// v0.4.0 — added `bizar_plan_action` (CRUD on the v2 canvas) and
|
|
710
|
-
//
|
|
796
|
+
// bizar_wait_for_feedback (poll until feedback). Both are pure
|
|
711
797
|
// file I/O — no serve child required.
|
|
712
798
|
//
|
|
713
799
|
// v0.5.0 — renamed `bizarre_*` → `bizar_*` (single `r`) to match
|
|
@@ -731,9 +817,13 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
731
817
|
const tools = bg.instanceManager
|
|
732
818
|
? {
|
|
733
819
|
...basePlanTools,
|
|
820
|
+
// v0.8.0 — bg-spawn no longer needs the HTTP client. It
|
|
821
|
+
// spawns an `opencode run` subprocess per agent (see
|
|
822
|
+
// src/opencode-runner.ts). The serve child is still
|
|
823
|
+
// available for the dashboard's v2 protocol and for any
|
|
824
|
+
// TUI/web client that wants to attach to it.
|
|
734
825
|
bizar_spawn_background: createBgSpawnTool({
|
|
735
826
|
instanceManager: bg.instanceManager,
|
|
736
|
-
http: (bg.instanceManager as unknown as { http: HttpClient }).http,
|
|
737
827
|
worktree: ctx.worktree,
|
|
738
828
|
logger: ctx.logger,
|
|
739
829
|
}),
|
|
@@ -756,36 +846,57 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
756
846
|
};
|
|
757
847
|
|
|
758
848
|
return {
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
|
|
849
|
+
// Push a persistent system-prompt directive that tells reasoning
|
|
850
|
+
// models to put their thinking in the structured reasoning field
|
|
851
|
+
// (rendered as a separate "Thought" panel by opencode) rather than
|
|
852
|
+
// also emitting it inline as `` blocks in the content. The openrouter
|
|
853
|
+
// SDK does not strip the inline `` blocks, so without this
|
|
854
|
+
// directive the user sees the reasoning twice — once in the proper
|
|
855
|
+
// panel and once as visible message text.
|
|
856
|
+
//
|
|
857
|
+
// We push the directive only when the marker is absent so we don't
|
|
858
|
+
// append it on every turn.
|
|
859
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
860
|
+
const sessionID = input.sessionID;
|
|
861
|
+
if (!sessionID) return;
|
|
862
|
+
if (!output.system.some((s) => s.includes(REASONING_DIRECTIVE_MARKER))) {
|
|
863
|
+
output.system.push(REASONING_DIRECTIVE);
|
|
864
|
+
}
|
|
865
|
+
// §3.1, §5.4 — handoff injection point. We push a single string
|
|
866
|
+
// onto `output.system` if a pending injection is queued for this
|
|
867
|
+
// session.
|
|
868
|
+
const pending = ctx.pendingInjections.get(sessionID);
|
|
869
|
+
if (pending) {
|
|
870
|
+
output.system.push(pending);
|
|
871
|
+
ctx.pendingInjections.delete(sessionID);
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
// Before the model is called, strip `` blocks from
|
|
876
|
+
// any text content in previous assistant messages. This keeps the
|
|
877
|
+
// model's view of its own history clean of the duplicated thinking
|
|
878
|
+
// it emitted earlier, reducing the chance it will keep emitting
|
|
879
|
+
// inline `` on subsequent turns.
|
|
880
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
765
881
|
try {
|
|
766
|
-
const
|
|
767
|
-
if (!
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (!
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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}`);
|
|
882
|
+
const messages = (output as { messages?: unknown }).messages;
|
|
883
|
+
if (!Array.isArray(messages)) return;
|
|
884
|
+
for (const msg of messages) {
|
|
885
|
+
if (!msg || typeof msg !== "object") continue;
|
|
886
|
+
const m = msg as { role?: unknown; parts?: unknown };
|
|
887
|
+
if (m.role !== "assistant") continue;
|
|
888
|
+
if (!Array.isArray(m.parts)) continue;
|
|
889
|
+
for (const part of m.parts) {
|
|
890
|
+
if (!part || typeof part !== "object") continue;
|
|
891
|
+
const p = part as { type?: unknown; text?: unknown };
|
|
892
|
+
if (p.type === "text" && typeof p.text === "string" && p.text.includes("<think>")) {
|
|
893
|
+
p.text = stripInlineThinkBlocks(p.text);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
785
896
|
}
|
|
786
897
|
} catch (err) {
|
|
787
898
|
ctx.logger.warn(
|
|
788
|
-
`bizar:
|
|
899
|
+
`bizar: messages.transform failed (passing through): ${
|
|
789
900
|
err instanceof Error ? err.message : String(err)
|
|
790
901
|
}`,
|
|
791
902
|
);
|
|
@@ -797,10 +908,37 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
797
908
|
// `chat.message` seed, per spec §4.5.1).
|
|
798
909
|
event: async ({ event }) => {
|
|
799
910
|
try {
|
|
800
|
-
|
|
911
|
+
// v0.7.0-alpha.1 — opencode's event object has { type: string,
|
|
912
|
+
// properties: { sessionID: string, ... } }. The legacy plugin
|
|
913
|
+
// assumed `event.sessionID` was top-level (which is wrong), so
|
|
914
|
+
// the hook returned early for every event. We extract from
|
|
915
|
+
// BOTH locations to be robust across opencode versions, and
|
|
916
|
+
// publish to the dashboard regardless.
|
|
917
|
+
const ev = event as {
|
|
918
|
+
type?: string;
|
|
919
|
+
sessionID?: string;
|
|
920
|
+
properties?: { sessionID?: string; [k: string]: unknown };
|
|
921
|
+
};
|
|
801
922
|
const type = ev.type;
|
|
802
|
-
const sessionID = ev.sessionID;
|
|
803
|
-
if (!type
|
|
923
|
+
const sessionID = ev.sessionID ?? ev.properties?.sessionID;
|
|
924
|
+
if (!type) return;
|
|
925
|
+
|
|
926
|
+
// v0.7.0-alpha.1 — Forward every opencode event to the dashboard.
|
|
927
|
+
// The plugin SDK does NOT translate opencode events to the SDK's
|
|
928
|
+
// discriminated DashboardEvent shape (it's a forward-compatible
|
|
929
|
+
// passthrough — the dashboard is happy with any {type, properties}
|
|
930
|
+
// event object). The publish is fire-and-forget; failures are
|
|
931
|
+
// logged and swallowed inside the publisher.
|
|
932
|
+
if (ctx.dashboardPublisher !== null) {
|
|
933
|
+
const dashEvent = {
|
|
934
|
+
type,
|
|
935
|
+
properties: { ...ev },
|
|
936
|
+
} as unknown as Parameters<DashboardPublisher["publish"]>[0];
|
|
937
|
+
void ctx.dashboardPublisher.publish(dashEvent);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Legacy logic below: only runs when we have a sessionID.
|
|
941
|
+
if (!sessionID) return;
|
|
804
942
|
|
|
805
943
|
if (type === "session.deleted") {
|
|
806
944
|
await ctx.stateStore.withLock(sessionID, async () => {
|
|
@@ -1102,18 +1240,6 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
|
|
|
1102
1240
|
}
|
|
1103
1241
|
},
|
|
1104
1242
|
|
|
1105
|
-
// §3.1, §5.4 — handoff injection point. We push a single string onto
|
|
1106
|
-
// `output.system` if a pending injection is queued for this session.
|
|
1107
|
-
"experimental.chat.system.transform": async (input, output) => {
|
|
1108
|
-
const sessionID = input.sessionID;
|
|
1109
|
-
if (!sessionID) return;
|
|
1110
|
-
const pending = ctx.pendingInjections.get(sessionID);
|
|
1111
|
-
if (pending) {
|
|
1112
|
-
output.system.push(pending);
|
|
1113
|
-
ctx.pendingInjections.delete(sessionID);
|
|
1114
|
-
}
|
|
1115
|
-
},
|
|
1116
|
-
|
|
1117
1243
|
// v0.4.2 — register the 4 background tools.
|
|
1118
1244
|
tool: tools,
|
|
1119
1245
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@polderlabs/bizar-sdk": "*",
|
|
29
30
|
"zod": "4.1.8"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
package/src/background-state.ts
CHANGED
|
@@ -107,8 +107,32 @@ export type BackgroundStatus =
|
|
|
107
107
|
* - `restartCount` — number of times this instance has been
|
|
108
108
|
* auto-restarted (not including the original spawn). Default 0.
|
|
109
109
|
* - `maxRestarts` — cap; default 3.
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
* - `lastRestartAt` — epoch ms of the most recent auto-restart.
|
|
111
|
+
* - `processId` (v0.8.0) — PID of the opencode run subprocess. Set
|
|
112
|
+
* by the opencode-runner after `Bun.spawn` returns. Optional
|
|
113
|
+
* for backward compat.
|
|
114
|
+
* - `exitCode` (v0.8.0) — exit code of the opencode run
|
|
115
|
+
* subprocess. Populated when the process exits.
|
|
116
|
+
* - `runnerState` (v0.8.0) — free-form status from the runner
|
|
117
|
+
* ("starting" | "running" | "done" | "failed" | "killed").
|
|
118
|
+
* Allows the dashboard to show runner-level state separately
|
|
119
|
+
* from the higher-level `status`.
|
|
120
|
+
* - `runnerError` (v0.8.0) — opencode run subprocess error (e.g.
|
|
121
|
+
* "opencode run exited with code 1").
|
|
122
|
+
* - `spawnMessage` (v0.8.0) — "you can continue" message returned
|
|
123
|
+
* to the LLM by the spawn tool. Surfaces in the dashboard.
|
|
124
|
+
* - `spawnNextSteps` (v0.8.0) — list of next-step hints returned
|
|
125
|
+
* to the LLM by the spawn tool.
|
|
126
|
+
* - `sessionIdAt` (v0.8.0) — when the opencode sessionId was
|
|
127
|
+
* first observed in the subprocess stderr.
|
|
128
|
+
* - `runnerStartedAt` / `runnerEndedAt` — when the subprocess
|
|
129
|
+
* started / ended (subset of `startedAt`/`completedAt`).
|
|
130
|
+
* - `spawnedAt` — when the bg-spawn tool recorded the instance.
|
|
131
|
+
* Distinct from `startedAt` because the runner starts a
|
|
132
|
+
* tick or two later.
|
|
133
|
+
* - `exitSignal` — exit signal name (e.g. "SIGTERM",
|
|
134
|
+
* "SIGKILL") if the subprocess was killed.
|
|
135
|
+
*/
|
|
112
136
|
export interface BackgroundState {
|
|
113
137
|
instanceId: string;
|
|
114
138
|
sessionId: string;
|
|
@@ -153,6 +177,18 @@ export interface BackgroundState {
|
|
|
153
177
|
* `{ ok: false }`. Cleared on a successful restart.
|
|
154
178
|
*/
|
|
155
179
|
restartError?: string;
|
|
180
|
+
// v0.8.0 — process tracking (see opencode-runner.ts).
|
|
181
|
+
processId?: number;
|
|
182
|
+
exitCode?: number;
|
|
183
|
+
runnerState?: string;
|
|
184
|
+
runnerError?: string;
|
|
185
|
+
spawnMessage?: string;
|
|
186
|
+
spawnNextSteps?: string[];
|
|
187
|
+
sessionIdAt?: number;
|
|
188
|
+
runnerStartedAt?: number;
|
|
189
|
+
runnerEndedAt?: number;
|
|
190
|
+
spawnedAt?: number;
|
|
191
|
+
exitSignal?: string;
|
|
156
192
|
}
|
|
157
193
|
|
|
158
194
|
/**
|
package/src/background.ts
CHANGED
|
@@ -866,6 +866,19 @@ export class InstanceManager {
|
|
|
866
866
|
}
|
|
867
867
|
}
|
|
868
868
|
|
|
869
|
+
/**
|
|
870
|
+
* v0.8.0 — Public version of `_maybeAutoRestart`. The opencode-runner
|
|
871
|
+
* (see src/opencode-runner.ts) calls this from its onExit callback
|
|
872
|
+
* when a `bizar_spawn_background` subprocess exits. Without this,
|
|
873
|
+
* persistent instances would never auto-restart under the new
|
|
874
|
+
* subprocess-based path (the SSE event handler that previously
|
|
875
|
+
* triggered auto-restart is no longer wired up because we don't
|
|
876
|
+
* subscribe to per-session events anymore).
|
|
877
|
+
*/
|
|
878
|
+
async maybeAutoRestart(instanceId: string): Promise<void> {
|
|
879
|
+
await this._maybeAutoRestart(instanceId);
|
|
880
|
+
}
|
|
881
|
+
|
|
869
882
|
/** v0.5.5 — Attempt an auto-restart for a persistent failed instance. */
|
|
870
883
|
private async _maybeAutoRestart(instanceId: string): Promise<void> {
|
|
871
884
|
const inst = this.instances.get(instanceId);
|
package/src/commands.ts
CHANGED
|
@@ -314,7 +314,7 @@ function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
314
314
|
// No argument — return current state as a dialog
|
|
315
315
|
return {
|
|
316
316
|
handled: true,
|
|
317
|
-
response: ""
|
|
317
|
+
response: `Visual plan mode is currently ${currentEnabled ? "on" : "off"}.`,
|
|
318
318
|
dialog: {
|
|
319
319
|
id: generateId(),
|
|
320
320
|
title: "Visual Plan",
|
|
@@ -334,7 +334,7 @@ function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
334
334
|
if (lc === "on" || lc === "true" || lc === "1" || lc === "enable") {
|
|
335
335
|
return {
|
|
336
336
|
handled: true,
|
|
337
|
-
response:
|
|
337
|
+
response: `Visual plan mode is now on.`,
|
|
338
338
|
settingsPatch: { visualPlanEnabled: true },
|
|
339
339
|
dialog: {
|
|
340
340
|
id: generateId(),
|
|
@@ -353,7 +353,7 @@ function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
353
353
|
if (lc === "off" || lc === "false" || lc === "0" || lc === "disable") {
|
|
354
354
|
return {
|
|
355
355
|
handled: true,
|
|
356
|
-
response:
|
|
356
|
+
response: `Visual plan mode is now off.`,
|
|
357
357
|
settingsPatch: { visualPlanEnabled: false },
|
|
358
358
|
dialog: {
|
|
359
359
|
id: generateId(),
|
|
@@ -372,7 +372,7 @@ function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
372
372
|
if (lc === "status" || lc === "state" || lc === "?") {
|
|
373
373
|
return {
|
|
374
374
|
handled: true,
|
|
375
|
-
response: ""
|
|
375
|
+
response: `Visual plan mode is currently ${currentEnabled ? "on" : "off"}.`,
|
|
376
376
|
dialog: {
|
|
377
377
|
id: generateId(),
|
|
378
378
|
title: "Visual Plan",
|
|
@@ -444,7 +444,7 @@ function handlePlan(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
444
444
|
function helpPlan(): SlashCommandResult {
|
|
445
445
|
return {
|
|
446
446
|
handled: true,
|
|
447
|
-
response: "",
|
|
447
|
+
response: "Plan commands: /plan new <slug> [template] | /plan list | /plan open <slug> | /plan get <slug> | /plan add <slug> | /plan update <slug> <id> | /plan delete <slug> <id> | /plan comment <slug> [id] \"text\" | /plan comments <slug> [id] | /plan status <slug> <status> | /plan wait <slug> [--timeout N]. Run /plan <subcommand> for details.",
|
|
448
448
|
dialog: {
|
|
449
449
|
id: generateId(),
|
|
450
450
|
title: "Plan Commands",
|
|
@@ -477,7 +477,7 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
|
|
|
477
477
|
if (args.length === 0 || args[0] === "") {
|
|
478
478
|
return {
|
|
479
479
|
handled: true,
|
|
480
|
-
response: "",
|
|
480
|
+
response: "Usage: /plan new <slug> [template]. Available templates: " + KNOWN_TEMPLATES.join(", ") + ".",
|
|
481
481
|
dialog: {
|
|
482
482
|
id: generateId(),
|
|
483
483
|
title: "Create New Plan",
|
|
@@ -520,7 +520,7 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
|
|
|
520
520
|
|
|
521
521
|
return {
|
|
522
522
|
handled: true,
|
|
523
|
-
response: ""
|
|
523
|
+
response: `Created plan "${titleCase(slug)}" with the "${resolvedTemplate}" template. Use /plan open ${slug} to open it.`,
|
|
524
524
|
sideEffect: {
|
|
525
525
|
kind: "create_plan",
|
|
526
526
|
slug,
|
|
@@ -544,9 +544,26 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
|
|
|
544
544
|
|
|
545
545
|
function handlePlanList(ctx: ParseContext): SlashCommandResult {
|
|
546
546
|
const slugs = ctx.availablePlanSlugs ?? [];
|
|
547
|
+
if (slugs.length === 0) {
|
|
548
|
+
return {
|
|
549
|
+
handled: true,
|
|
550
|
+
response: "No plans found in the current worktree. Use /plan new <slug> to create one.",
|
|
551
|
+
sideEffect: { kind: "list_plans" },
|
|
552
|
+
dialog: {
|
|
553
|
+
id: generateId(),
|
|
554
|
+
title: "Plans",
|
|
555
|
+
command: "/plan list",
|
|
556
|
+
component: "plan-list",
|
|
557
|
+
data: {
|
|
558
|
+
plans: slugs,
|
|
559
|
+
count: slugs.length,
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
}
|
|
547
564
|
return {
|
|
548
565
|
handled: true,
|
|
549
|
-
response: ""
|
|
566
|
+
response: `Found ${slugs.length} plan(s) (${slugs.length}): ${slugs.join(", ")}.`,
|
|
550
567
|
sideEffect: { kind: "list_plans" },
|
|
551
568
|
dialog: {
|
|
552
569
|
id: generateId(),
|
|
@@ -584,7 +601,7 @@ function handlePlanOpen(args: string[], ctx: ParseContext): SlashCommandResult {
|
|
|
584
601
|
|
|
585
602
|
return {
|
|
586
603
|
handled: true,
|
|
587
|
-
response: ""
|
|
604
|
+
response: `Opening plan "${slug}" at ${url}.`,
|
|
588
605
|
settingsPatch: { lastUsedSlug: slug },
|
|
589
606
|
sideEffect: {
|
|
590
607
|
kind: "open_plan_url",
|
|
@@ -618,7 +635,7 @@ function handlePlanGet(args: string[]): SlashCommandResult {
|
|
|
618
635
|
}
|
|
619
636
|
return {
|
|
620
637
|
handled: true,
|
|
621
|
-
response: ""
|
|
638
|
+
response: `Fetching canvas for plan "${slug}"…`,
|
|
622
639
|
sideEffect: {
|
|
623
640
|
kind: "tool_invocation",
|
|
624
641
|
toolName: "bizar_plan_action",
|
|
@@ -1078,7 +1095,7 @@ function handleBizar(arg: string, ctx: ParseContext): SlashCommandResult {
|
|
|
1078
1095
|
function helpResult(): SlashCommandResult {
|
|
1079
1096
|
return {
|
|
1080
1097
|
handled: true,
|
|
1081
|
-
response: "",
|
|
1098
|
+
response: "Available commands: /visual-plan [on|off|status], /plan new <slug> [template], /plan list, /plan open <slug>, /plan get <slug>, /plan add <slug>, /plan update <slug> <id>, /plan delete <slug> <id>, /plan comment <slug> [id] \"text\", /plan comments <slug> [id], /plan status <slug> <status>, /plan wait <slug> [--timeout N], /bizar, /bizar <args>, /help. See the dialog for full descriptions.",
|
|
1082
1099
|
dialog: {
|
|
1083
1100
|
id: generateId(),
|
|
1084
1101
|
title: "Bizar Commands",
|