opencode-discipline 0.2.2 → 0.3.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/README.md +33 -0
- package/dist/index.js +281 -4
- package/dist/wave-state.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,39 @@ It provides:
|
|
|
28
28
|
bun run build
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Notifications (optional)
|
|
32
|
+
|
|
33
|
+
On startup, the plugin shows a small in-app toast: `opencode-discipline vX is running`.
|
|
34
|
+
|
|
35
|
+
You can enable milestone notifications for key planning events:
|
|
36
|
+
|
|
37
|
+
- `Need your answers` (agent question prompts)
|
|
38
|
+
- `Plan is ready` (Wave 4 handoff stage)
|
|
39
|
+
- `OpenCode has finished` (Build agent completion)
|
|
40
|
+
|
|
41
|
+
Set one environment variable before running OpenCode:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export OPENCODE_DISCIPLINE_NOTIFY=metadata
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Supported modes:
|
|
48
|
+
|
|
49
|
+
- `off`
|
|
50
|
+
- `metadata` (emit structured notification metadata)
|
|
51
|
+
- `os` (default; desktop notification via `osascript`/`notify-send`/PowerShell)
|
|
52
|
+
- `both` (metadata + OS)
|
|
53
|
+
|
|
54
|
+
Optional custom command hook:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export OPENCODE_DISCIPLINE_NOTIFY_COMMAND='terminal-notifier -title "{title}" -message "{message}"'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Template variables: `{event}`, `{title}`, `{message}`, `{sessionID}`.
|
|
61
|
+
|
|
62
|
+
Build completion notifications are emitted automatically when OpenCode reports the Build session as idle (`session.idle`).
|
|
63
|
+
|
|
31
64
|
## Test
|
|
32
65
|
|
|
33
66
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -6915,8 +6915,9 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6915
6915
|
});
|
|
6916
6916
|
|
|
6917
6917
|
// src/index.ts
|
|
6918
|
-
import { accessSync, constants, existsSync as existsSync2 } from "fs";
|
|
6918
|
+
import { accessSync, constants, existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
6919
6919
|
import { basename as basename2, dirname, relative, resolve as resolve2 } from "path";
|
|
6920
|
+
import { spawn } from "child_process";
|
|
6920
6921
|
import { fileURLToPath } from "url";
|
|
6921
6922
|
|
|
6922
6923
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
@@ -19585,6 +19586,29 @@ class WaveStateManager {
|
|
|
19585
19586
|
this.persist(nextState);
|
|
19586
19587
|
return nextState;
|
|
19587
19588
|
}
|
|
19589
|
+
isAcceptedBuildSession(sessionID) {
|
|
19590
|
+
for (const state of this.states.values()) {
|
|
19591
|
+
if (state.acceptedBySessionID === sessionID) {
|
|
19592
|
+
return true;
|
|
19593
|
+
}
|
|
19594
|
+
}
|
|
19595
|
+
if (!existsSync(this.stateDir)) {
|
|
19596
|
+
return false;
|
|
19597
|
+
}
|
|
19598
|
+
const entries = readdirSync2(this.stateDir).filter((entry) => entry.endsWith(".json"));
|
|
19599
|
+
for (const entry of entries) {
|
|
19600
|
+
try {
|
|
19601
|
+
const path = join(this.stateDir, entry);
|
|
19602
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
19603
|
+
if (parsed.acceptedBySessionID === sessionID) {
|
|
19604
|
+
return true;
|
|
19605
|
+
}
|
|
19606
|
+
} catch {
|
|
19607
|
+
continue;
|
|
19608
|
+
}
|
|
19609
|
+
}
|
|
19610
|
+
return false;
|
|
19611
|
+
}
|
|
19588
19612
|
persist(state) {
|
|
19589
19613
|
this.ensureDir();
|
|
19590
19614
|
const filePath = join(this.stateDir, `${state.planName}.json`);
|
|
@@ -19627,6 +19651,155 @@ var WAVE_NEXT_STEPS = {
|
|
|
19627
19651
|
3: "writing the plan file",
|
|
19628
19652
|
4: "plan review and refinement"
|
|
19629
19653
|
};
|
|
19654
|
+
var PLUGIN_DISPLAY_NAME = "opencode-discipline";
|
|
19655
|
+
var startupToastShown = false;
|
|
19656
|
+
function parseNotificationMode(raw) {
|
|
19657
|
+
const value = raw?.trim().toLowerCase();
|
|
19658
|
+
if (!value) {
|
|
19659
|
+
return "os";
|
|
19660
|
+
}
|
|
19661
|
+
if (value === "off" || value === "0" || value === "false" || value === "disabled") {
|
|
19662
|
+
return "off";
|
|
19663
|
+
}
|
|
19664
|
+
if (value === "metadata") {
|
|
19665
|
+
return "metadata";
|
|
19666
|
+
}
|
|
19667
|
+
if (value === "both" || value === "all") {
|
|
19668
|
+
return "both";
|
|
19669
|
+
}
|
|
19670
|
+
return "os";
|
|
19671
|
+
}
|
|
19672
|
+
function readPluginVersion(pluginDir) {
|
|
19673
|
+
try {
|
|
19674
|
+
const packageJsonPath = resolve2(pluginDir, "../package.json");
|
|
19675
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
19676
|
+
return typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
19677
|
+
} catch {
|
|
19678
|
+
return "unknown";
|
|
19679
|
+
}
|
|
19680
|
+
}
|
|
19681
|
+
async function showStartupToast(client, directory, version2) {
|
|
19682
|
+
if (startupToastShown) {
|
|
19683
|
+
return;
|
|
19684
|
+
}
|
|
19685
|
+
const tuiApi = client.tui;
|
|
19686
|
+
if (!tuiApi || typeof tuiApi.showToast !== "function") {
|
|
19687
|
+
return;
|
|
19688
|
+
}
|
|
19689
|
+
try {
|
|
19690
|
+
await tuiApi.showToast({
|
|
19691
|
+
query: { directory },
|
|
19692
|
+
body: {
|
|
19693
|
+
title: "Discipline Plugin",
|
|
19694
|
+
message: `${PLUGIN_DISPLAY_NAME} v${version2} is running`,
|
|
19695
|
+
variant: "info",
|
|
19696
|
+
duration: 3000
|
|
19697
|
+
}
|
|
19698
|
+
});
|
|
19699
|
+
startupToastShown = true;
|
|
19700
|
+
} catch {}
|
|
19701
|
+
}
|
|
19702
|
+
function escapeAppleScriptString(value) {
|
|
19703
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
|
|
19704
|
+
}
|
|
19705
|
+
function escapePowerShellString(value) {
|
|
19706
|
+
return value.replaceAll("'", "''");
|
|
19707
|
+
}
|
|
19708
|
+
|
|
19709
|
+
class NotificationManager {
|
|
19710
|
+
mode;
|
|
19711
|
+
commandTemplate;
|
|
19712
|
+
sentKeys = new Map;
|
|
19713
|
+
constructor() {
|
|
19714
|
+
this.mode = parseNotificationMode(process.env.OPENCODE_DISCIPLINE_NOTIFY);
|
|
19715
|
+
this.commandTemplate = process.env.OPENCODE_DISCIPLINE_NOTIFY_COMMAND?.trim();
|
|
19716
|
+
}
|
|
19717
|
+
notify(input) {
|
|
19718
|
+
if (this.mode === "off") {
|
|
19719
|
+
return;
|
|
19720
|
+
}
|
|
19721
|
+
if (this.isSent(input.sessionID, input.dedupeKey)) {
|
|
19722
|
+
return;
|
|
19723
|
+
}
|
|
19724
|
+
this.markSent(input.sessionID, input.dedupeKey);
|
|
19725
|
+
if (this.mode === "metadata" || this.mode === "both") {
|
|
19726
|
+
this.emitMetadata(input);
|
|
19727
|
+
}
|
|
19728
|
+
if (this.mode === "os" || this.mode === "both") {
|
|
19729
|
+
this.emitOsNotification(input);
|
|
19730
|
+
}
|
|
19731
|
+
}
|
|
19732
|
+
isSent(sessionID, key) {
|
|
19733
|
+
const keys = this.sentKeys.get(sessionID);
|
|
19734
|
+
return keys?.has(key) ?? false;
|
|
19735
|
+
}
|
|
19736
|
+
markSent(sessionID, key) {
|
|
19737
|
+
const keys = this.sentKeys.get(sessionID) ?? new Set;
|
|
19738
|
+
keys.add(key);
|
|
19739
|
+
this.sentKeys.set(sessionID, keys);
|
|
19740
|
+
}
|
|
19741
|
+
emitMetadata(input) {
|
|
19742
|
+
if (!input.toolContext || typeof input.toolContext !== "object") {
|
|
19743
|
+
return;
|
|
19744
|
+
}
|
|
19745
|
+
const maybeMetadata = input.toolContext.metadata;
|
|
19746
|
+
if (typeof maybeMetadata !== "function") {
|
|
19747
|
+
return;
|
|
19748
|
+
}
|
|
19749
|
+
try {
|
|
19750
|
+
maybeMetadata({
|
|
19751
|
+
type: "discipline.notification",
|
|
19752
|
+
event: input.event,
|
|
19753
|
+
title: input.title,
|
|
19754
|
+
message: input.message,
|
|
19755
|
+
sessionID: input.sessionID,
|
|
19756
|
+
createdAt: new Date().toISOString()
|
|
19757
|
+
});
|
|
19758
|
+
} catch {}
|
|
19759
|
+
}
|
|
19760
|
+
emitOsNotification(input) {
|
|
19761
|
+
if (this.commandTemplate) {
|
|
19762
|
+
const command = this.commandTemplate.replaceAll("{event}", input.event).replaceAll("{title}", input.title).replaceAll("{message}", input.message).replaceAll("{sessionID}", input.sessionID);
|
|
19763
|
+
this.spawnDetached(process.platform === "win32" ? "cmd.exe" : "sh", process.platform === "win32" ? ["/d", "/s", "/c", command] : ["-lc", command]);
|
|
19764
|
+
return;
|
|
19765
|
+
}
|
|
19766
|
+
if (process.platform === "darwin") {
|
|
19767
|
+
const script = `display notification "${escapeAppleScriptString(input.message)}" with title "${escapeAppleScriptString(input.title)}"`;
|
|
19768
|
+
this.spawnDetached("osascript", ["-e", script]);
|
|
19769
|
+
return;
|
|
19770
|
+
}
|
|
19771
|
+
if (process.platform === "linux") {
|
|
19772
|
+
this.spawnDetached("notify-send", [input.title, input.message]);
|
|
19773
|
+
return;
|
|
19774
|
+
}
|
|
19775
|
+
if (process.platform === "win32") {
|
|
19776
|
+
const script = [
|
|
19777
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
19778
|
+
"$notify = New-Object System.Windows.Forms.NotifyIcon",
|
|
19779
|
+
"$notify.Icon = [System.Drawing.SystemIcons]::Information",
|
|
19780
|
+
"$notify.BalloonTipTitle = '",
|
|
19781
|
+
escapePowerShellString(input.title),
|
|
19782
|
+
"'",
|
|
19783
|
+
"$notify.BalloonTipText = '",
|
|
19784
|
+
escapePowerShellString(input.message),
|
|
19785
|
+
"'",
|
|
19786
|
+
"$notify.Visible = $true",
|
|
19787
|
+
"$notify.ShowBalloonTip(4000)"
|
|
19788
|
+
].join(" ");
|
|
19789
|
+
this.spawnDetached("powershell", ["-NoProfile", "-Command", script]);
|
|
19790
|
+
}
|
|
19791
|
+
}
|
|
19792
|
+
spawnDetached(command, args) {
|
|
19793
|
+
try {
|
|
19794
|
+
const child = spawn(command, args, {
|
|
19795
|
+
stdio: "ignore",
|
|
19796
|
+
detached: true
|
|
19797
|
+
});
|
|
19798
|
+
child.on("error", () => {});
|
|
19799
|
+
child.unref();
|
|
19800
|
+
} catch {}
|
|
19801
|
+
}
|
|
19802
|
+
}
|
|
19630
19803
|
function hasStringProp(obj, key) {
|
|
19631
19804
|
return key in obj && typeof obj[key] === "string";
|
|
19632
19805
|
}
|
|
@@ -19642,6 +19815,41 @@ function extractSessionID(result) {
|
|
|
19642
19815
|
}
|
|
19643
19816
|
return;
|
|
19644
19817
|
}
|
|
19818
|
+
function extractSessionIDFromHookInput(input) {
|
|
19819
|
+
if (!input || typeof input !== "object") {
|
|
19820
|
+
return;
|
|
19821
|
+
}
|
|
19822
|
+
if (hasStringProp(input, "sessionID")) {
|
|
19823
|
+
return input.sessionID;
|
|
19824
|
+
}
|
|
19825
|
+
if (hasStringProp(input, "id")) {
|
|
19826
|
+
return input.id;
|
|
19827
|
+
}
|
|
19828
|
+
const value = input;
|
|
19829
|
+
const path = value.path;
|
|
19830
|
+
if (path && typeof path === "object" && hasStringProp(path, "id")) {
|
|
19831
|
+
return path.id;
|
|
19832
|
+
}
|
|
19833
|
+
const session = value.session;
|
|
19834
|
+
if (session && typeof session === "object" && hasStringProp(session, "id")) {
|
|
19835
|
+
return session.id;
|
|
19836
|
+
}
|
|
19837
|
+
return;
|
|
19838
|
+
}
|
|
19839
|
+
function extractAgentFromHookInput(input) {
|
|
19840
|
+
if (!input || typeof input !== "object") {
|
|
19841
|
+
return;
|
|
19842
|
+
}
|
|
19843
|
+
if (hasStringProp(input, "agent")) {
|
|
19844
|
+
return input.agent.toLowerCase();
|
|
19845
|
+
}
|
|
19846
|
+
const value = input;
|
|
19847
|
+
const session = value.session;
|
|
19848
|
+
if (session && typeof session === "object" && hasStringProp(session, "agent")) {
|
|
19849
|
+
return session.agent.toLowerCase();
|
|
19850
|
+
}
|
|
19851
|
+
return;
|
|
19852
|
+
}
|
|
19645
19853
|
function getWaveLabel(wave) {
|
|
19646
19854
|
return WAVE_NAMES[wave];
|
|
19647
19855
|
}
|
|
@@ -19664,6 +19872,7 @@ function isBlockedEnvRead(filePath) {
|
|
|
19664
19872
|
return fileName.startsWith(".env.");
|
|
19665
19873
|
}
|
|
19666
19874
|
function buildWaveStateSystemBlock(wave, planName) {
|
|
19875
|
+
const advanceGuidance = wave === 1 ? "Call `advance_wave` only after Wave 1 interview + checklist work is complete." : wave === 2 ? "Call `advance_wave` only after the Wave 2 Oracle check has completed and gap analysis is done." : wave === 3 ? "Call `advance_wave` only after the plan file is written and Wave 3 is complete." : "Do not call `advance_wave` again unless you are truly done with Wave 4 and ready for handoff decisions.";
|
|
19667
19876
|
return [
|
|
19668
19877
|
"## \uD83D\uDD12 Discipline Plugin \u2014 Wave State",
|
|
19669
19878
|
"",
|
|
@@ -19677,7 +19886,7 @@ function buildWaveStateSystemBlock(wave, planName) {
|
|
|
19677
19886
|
"- Wave 3 (Plan Generation): NOW write the plan to tasks/plans/{planName}.md using the structured template.",
|
|
19678
19887
|
"- Wave 4 (Review): Self-review the plan. Delegate to @oracle for high-stakes decisions. Edit the plan if needed.",
|
|
19679
19888
|
"",
|
|
19680
|
-
`**You are in Wave ${wave}
|
|
19889
|
+
`**You are in Wave ${wave}.** ${advanceGuidance}`,
|
|
19681
19890
|
"**Writing to tasks/plans/*.md is BLOCKED until Wave 3.**"
|
|
19682
19891
|
].join(`
|
|
19683
19892
|
`);
|
|
@@ -19687,7 +19896,8 @@ function buildWave2OraclePrompt() {
|
|
|
19687
19896
|
"## MANDATORY: Wave 2 Oracle Check",
|
|
19688
19897
|
"Before you can advance to Wave 3, delegate to `@oracle` once for a gap-analysis sanity check.",
|
|
19689
19898
|
'Use the Task tool with `subagent_type: "oracle"` and summarize the result in your analysis.',
|
|
19690
|
-
"Wave 2 -> 3 is enforced: `advance_wave` will fail until this Oracle check is completed."
|
|
19899
|
+
"Wave 2 -> 3 is enforced: `advance_wave` will fail until this Oracle check is completed.",
|
|
19900
|
+
"Do NOT retry `advance_wave` until the Oracle task returns successfully."
|
|
19691
19901
|
].join(`
|
|
19692
19902
|
`);
|
|
19693
19903
|
}
|
|
@@ -19908,6 +20118,25 @@ async function handleCompacting(ctx, input, output) {
|
|
|
19908
20118
|
}
|
|
19909
20119
|
output.context.push(buildCompactionContext(state));
|
|
19910
20120
|
}
|
|
20121
|
+
async function handleSessionIdle(ctx, input, _output) {
|
|
20122
|
+
const sessionID = extractSessionIDFromHookInput(input);
|
|
20123
|
+
if (!sessionID) {
|
|
20124
|
+
return;
|
|
20125
|
+
}
|
|
20126
|
+
const agent = extractAgentFromHookInput(input);
|
|
20127
|
+
const isBuildIdle = agent === "build" || ctx.manager.isAcceptedBuildSession(sessionID);
|
|
20128
|
+
if (!isBuildIdle) {
|
|
20129
|
+
return;
|
|
20130
|
+
}
|
|
20131
|
+
ctx.notifications.notify({
|
|
20132
|
+
sessionID,
|
|
20133
|
+
event: "coding_complete",
|
|
20134
|
+
title: "OpenCode has finished",
|
|
20135
|
+
message: "The Build agent is done and OpenCode is ready for input.",
|
|
20136
|
+
dedupeKey: "session-idle-coding-complete",
|
|
20137
|
+
toolContext: input
|
|
20138
|
+
});
|
|
20139
|
+
}
|
|
19911
20140
|
async function handleSystemTransform(ctx, input, output) {
|
|
19912
20141
|
const sessionID = input.sessionID;
|
|
19913
20142
|
const state = sessionID ? ctx.manager.getState(sessionID) : undefined;
|
|
@@ -19929,6 +20158,15 @@ async function handleSystemTransform(ctx, input, output) {
|
|
|
19929
20158
|
if (state.wave === 4) {
|
|
19930
20159
|
const planFilePath = resolve2(ctx.worktree, `tasks/plans/${state.planName}.md`);
|
|
19931
20160
|
if (existsSync2(planFilePath)) {
|
|
20161
|
+
if (sessionID) {
|
|
20162
|
+
ctx.notifications.notify({
|
|
20163
|
+
sessionID,
|
|
20164
|
+
event: "plan_ready",
|
|
20165
|
+
title: "Plan is ready",
|
|
20166
|
+
message: `tasks/plans/${state.planName}.md is ready for handoff review.`,
|
|
20167
|
+
dedupeKey: "wave-4-plan-ready"
|
|
20168
|
+
});
|
|
20169
|
+
}
|
|
19932
20170
|
output.system.push(buildPlanHandoffPrompt(state.planName));
|
|
19933
20171
|
}
|
|
19934
20172
|
}
|
|
@@ -19969,6 +20207,19 @@ async function handleToolExecuteBefore(ctx, input, output) {
|
|
|
19969
20207
|
}
|
|
19970
20208
|
}
|
|
19971
20209
|
async function handleToolExecuteAfter(ctx, input, output) {
|
|
20210
|
+
if (input.tool === "question") {
|
|
20211
|
+
const condensed = output.output.replace(/\s+/g, " ").trim();
|
|
20212
|
+
const questionText = condensed.length > 180 ? `${condensed.slice(0, 177)}...` : condensed;
|
|
20213
|
+
ctx.notifications.notify({
|
|
20214
|
+
sessionID: input.sessionID,
|
|
20215
|
+
event: "need_answers",
|
|
20216
|
+
title: "Need your answers",
|
|
20217
|
+
message: questionText || "The agent asked a question and is waiting for your answer.",
|
|
20218
|
+
dedupeKey: `question-${input.callID}`,
|
|
20219
|
+
toolContext: output
|
|
20220
|
+
});
|
|
20221
|
+
return;
|
|
20222
|
+
}
|
|
19972
20223
|
const state = ctx.manager.getState(input.sessionID);
|
|
19973
20224
|
if (!state || state.accepted) {
|
|
19974
20225
|
return;
|
|
@@ -20028,9 +20279,31 @@ function createAdvanceWaveTool(ctx) {
|
|
|
20028
20279
|
}
|
|
20029
20280
|
const waveName = WAVE_NAMES[state.wave];
|
|
20030
20281
|
const nextStep = WAVE_NEXT_STEPS[state.wave];
|
|
20031
|
-
|
|
20282
|
+
const advanceWhen = state.wave === 1 ? "Call `advance_wave` only after interview + checklist work is complete." : state.wave === 2 ? "Call `advance_wave` only after Oracle review is complete and gap analysis is done." : state.wave === 3 ? "Call `advance_wave` only after the plan file is written." : "Stay in Wave 4 for plan review/handoff; call `advance_wave` only if explicitly needed (normally you should use `accept_plan`).";
|
|
20283
|
+
if (state.wave === 4) {
|
|
20284
|
+
const planFilePath = resolve2(ctx.worktree, `tasks/plans/${state.planName}.md`);
|
|
20285
|
+
if (existsSync2(planFilePath)) {
|
|
20286
|
+
ctx.notifications.notify({
|
|
20287
|
+
sessionID: args.sessionID,
|
|
20288
|
+
event: "plan_ready",
|
|
20289
|
+
title: "Plan is ready",
|
|
20290
|
+
message: `tasks/plans/${state.planName}.md is ready for handoff review.`,
|
|
20291
|
+
dedupeKey: "wave-4-plan-ready",
|
|
20292
|
+
toolContext: context
|
|
20293
|
+
});
|
|
20294
|
+
}
|
|
20295
|
+
}
|
|
20296
|
+
return `Wave ${state.wave} (${waveName}) started for plan '${state.planName}'. You may now proceed with ${nextStep}. ${advanceWhen}`;
|
|
20032
20297
|
} catch (error45) {
|
|
20033
20298
|
const message = error45 instanceof Error ? error45.message : "Unknown error";
|
|
20299
|
+
if (message.includes("Cannot advance to Wave 3 before Oracle gap review is completed")) {
|
|
20300
|
+
return [
|
|
20301
|
+
`Error: ${message}`,
|
|
20302
|
+
"You are still in Wave 2 (Gap Analysis).",
|
|
20303
|
+
'Next action: run Task with subagent_type="oracle" and wait for successful completion.',
|
|
20304
|
+
"Do NOT call advance_wave again until that Oracle task succeeds."
|
|
20305
|
+
].join(" ");
|
|
20306
|
+
}
|
|
20034
20307
|
return `Error: ${message}`;
|
|
20035
20308
|
}
|
|
20036
20309
|
}
|
|
@@ -20137,14 +20410,17 @@ function createAcceptPlanTool(ctx) {
|
|
|
20137
20410
|
}
|
|
20138
20411
|
var DisciplinePlugin = async ({ worktree, directory, client }) => {
|
|
20139
20412
|
const pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
20413
|
+
const pluginVersion = readPluginVersion(pluginDir);
|
|
20140
20414
|
const agentConfigs = loadAgentConfigs(resolve2(pluginDir, "../agents"));
|
|
20141
20415
|
const ctx = {
|
|
20142
20416
|
manager: new WaveStateManager(worktree),
|
|
20143
20417
|
todoNudges: new Map,
|
|
20418
|
+
notifications: new NotificationManager,
|
|
20144
20419
|
worktree,
|
|
20145
20420
|
directory,
|
|
20146
20421
|
client
|
|
20147
20422
|
};
|
|
20423
|
+
await showStartupToast(client, directory, pluginVersion);
|
|
20148
20424
|
return {
|
|
20149
20425
|
config: async (input) => {
|
|
20150
20426
|
const agents = input.agent;
|
|
@@ -20158,6 +20434,7 @@ var DisciplinePlugin = async ({ worktree, directory, client }) => {
|
|
|
20158
20434
|
"experimental.chat.system.transform": (input, output) => handleSystemTransform(ctx, input, output),
|
|
20159
20435
|
"tool.execute.before": (input, output) => handleToolExecuteBefore(ctx, input, output),
|
|
20160
20436
|
"tool.execute.after": (input, output) => handleToolExecuteAfter(ctx, input, output),
|
|
20437
|
+
"session.idle": (input, output) => handleSessionIdle(ctx, input, output),
|
|
20161
20438
|
tool: {
|
|
20162
20439
|
advance_wave: createAdvanceWaveTool(ctx),
|
|
20163
20440
|
accept_plan: createAcceptPlanTool(ctx)
|
package/dist/wave-state.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare class WaveStateManager {
|
|
|
23
23
|
getPlanName(sessionID: string): string | undefined;
|
|
24
24
|
isWaveAtLeast(sessionID: string, minWave: Wave): boolean;
|
|
25
25
|
markAccepted(sessionID: string, buildSessionID: string): WaveState;
|
|
26
|
+
isAcceptedBuildSession(sessionID: string): boolean;
|
|
26
27
|
private persist;
|
|
27
28
|
private ensureDir;
|
|
28
29
|
private loadStateFromDisk;
|