opencode-discipline 0.2.1 → 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/agents/README.md +7 -3
- package/agents/analyzer.md +0 -1
- package/agents/build.md +17 -13
- package/agents/explore.md +47 -12
- package/agents/librarian.md +50 -40
- package/agents/plan.md +6 -4
- package/dist/index.js +311 -10
- 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/agents/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This package ships eleven agent definitions for disciplined plan-first execution
|
|
|
8
8
|
| `build` | `anthropic/claude-opus-4-6` | primary | Executes approved plans phase-by-phase with verification gates |
|
|
9
9
|
| `analyzer` | `openai/gpt-5.4` | agent | Traces control flow, data flow, and system interactions from file lists into step-by-step explanations |
|
|
10
10
|
| `oracle` | `openai/gpt-5.4` | subagent | Read-only architecture and tradeoff advisor |
|
|
11
|
-
| `librarian` | `openai/gpt-5.2` | subagent |
|
|
11
|
+
| `librarian` | `openai/gpt-5.2` | subagent | External knowledge specialist — docs, best practices, implementation guidance |
|
|
12
12
|
| `reviewer` | `openai/gpt-5.4` | subagent | Read-only quality critic for plans and code |
|
|
13
13
|
| `designer` | `anthropic/claude-sonnet-4-6` | subagent | Read-only UI/UX and accessibility advisor |
|
|
14
14
|
| `deep` | `openai/gpt-5.3-codex` | subagent | Advanced implementation subagent for complex coding work |
|
|
@@ -43,7 +43,11 @@ User overrides take precedence per field; non-overridden fields use the plugin d
|
|
|
43
43
|
## Delegation flow
|
|
44
44
|
|
|
45
45
|
```text
|
|
46
|
-
|
|
46
|
+
Explorer = codebase search ("what exists?")
|
|
47
|
+
Librarian = external knowledge ("what should we do?")
|
|
48
|
+
Golden rule: Explorer first, Librarian second.
|
|
49
|
+
|
|
50
|
+
plan -> explore/explore-deep -> analyzer -> librarian (if needed) -> oracle -> accept_plan
|
|
47
51
|
accept_plan -> build
|
|
48
|
-
build -> explore/explore-deep
|
|
52
|
+
build -> explore/explore-deep -> analyzer -> librarian (if needed) -> oracle -> reviewer -> done
|
|
49
53
|
```
|
package/agents/analyzer.md
CHANGED
|
@@ -31,7 +31,6 @@ permission:
|
|
|
31
31
|
"*": deny
|
|
32
32
|
"explore": allow
|
|
33
33
|
"explore-deep": allow
|
|
34
|
-
"librarian": allow
|
|
35
34
|
---
|
|
36
35
|
|
|
37
36
|
You are the Analyzer — a systems-thinking agent that turns raw file lists into clear, actionable understanding. Where explorers find *what* exists, you explain *how* it works.
|
package/agents/build.md
CHANGED
|
@@ -37,9 +37,10 @@ If the user references a plan file, or if `tasks/plans/` contains a recent plan:
|
|
|
37
37
|
|
|
38
38
|
1. **Read the plan first.** Understand all phases, dependencies, and verification criteria before writing any code.
|
|
39
39
|
2. **Work phase by phase.** Complete Phase 1 entirely, run its verification step, confirm it passes, then move to Phase 2. Never jump ahead.
|
|
40
|
-
3. **
|
|
41
|
-
4. **
|
|
42
|
-
5. **
|
|
40
|
+
3. **Read files on-demand, not in bulk.** The plan already has specific file paths and line numbers. Read each file as you work on it — do NOT spawn subagents to pre-gather all files upfront. That wastes your context window on code you won't touch for several phases.
|
|
41
|
+
4. **Run verification after each phase.** Execute the exact command from the plan's "Verify" field. If it fails, fix it before moving on.
|
|
42
|
+
5. **Check off items as you go.** Update the plan file: change `- [ ]` to `- [x]` for completed items.
|
|
43
|
+
6. **If a phase fails verification twice**, stop. Tell the user what's failing and suggest switching to Plan agent to re-plan the remaining phases.
|
|
43
44
|
|
|
44
45
|
## When no plan exists
|
|
45
46
|
|
|
@@ -49,17 +50,20 @@ Work directly on the task. For non-trivial work (3+ files or architectural chang
|
|
|
49
50
|
|
|
50
51
|
Keep your context window clean. You are an implementer, not a researcher.
|
|
51
52
|
|
|
53
|
+
**Golden rule: Explorer first, Librarian second. Never the opposite.**
|
|
54
|
+
- Explorer answers: "what exists in the codebase?"
|
|
55
|
+
- Librarian answers: "what should we do?" (external docs, best practices)
|
|
56
|
+
|
|
52
57
|
**Delegate to @explore when:**
|
|
53
|
-
- You need
|
|
58
|
+
- You need to understand what exists in the codebase — file structure, function signatures, types, patterns
|
|
54
59
|
- You need to check what imports or depends on a file before changing it
|
|
55
|
-
-
|
|
60
|
+
- **This is your default for any codebase research.** Explorer returns structured summaries, not raw file dumps.
|
|
56
61
|
|
|
57
62
|
**Delegate to @librarian when:**
|
|
58
|
-
- You need
|
|
59
|
-
- You need
|
|
60
|
-
- You need
|
|
61
|
-
-
|
|
62
|
-
- **This is your default for any research involving 3+ files.** One spawn, one response.
|
|
63
|
+
- You need external documentation — library APIs, framework guides, migration docs
|
|
64
|
+
- You need best practices or recommended patterns from outside the codebase
|
|
65
|
+
- You need to validate an approach against official docs
|
|
66
|
+
- **Librarian does NOT search the codebase.** It only fetches external knowledge. If it needs codebase context, it delegates to Explorer internally.
|
|
63
67
|
|
|
64
68
|
**Delegate to @analyzer when:**
|
|
65
69
|
- You need to understand *how* a system works, not just *what files* exist
|
|
@@ -76,9 +80,9 @@ Keep your context window clean. You are an implementer, not a researcher.
|
|
|
76
80
|
- You've made changes touching 3+ files and want a sanity check
|
|
77
81
|
|
|
78
82
|
**Delegation pattern for understanding a subsystem:**
|
|
79
|
-
1. @
|
|
80
|
-
2. @analyzer → "explain how X works
|
|
81
|
-
|
|
83
|
+
1. @explore → "find all code related to X" → returns file paths + structured summaries
|
|
84
|
+
2. @analyzer → "explain how X works" → returns step-by-step flow analysis
|
|
85
|
+
3. @librarian (only if needed) → "what do the docs say about Y?" → returns external guidance
|
|
82
86
|
|
|
83
87
|
**DO NOT delegate when:**
|
|
84
88
|
- The task is straightforward and you already have the context
|
package/agents/explore.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Fast codebase search. Finds files, patterns, and structure quickly. Cheap and parallel-friendly.
|
|
2
|
+
description: Fast codebase search. Finds files, patterns, and structure quickly. Returns structured snippets — enough context to act on. Cheap and parallel-friendly.
|
|
3
3
|
model: anthropic/claude-haiku-4-5
|
|
4
4
|
temperature: 0
|
|
5
5
|
mode: subagent
|
|
@@ -27,28 +27,63 @@ permission:
|
|
|
27
27
|
"echo *": allow
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
You are
|
|
30
|
+
You are the codebase explorer — the **internal search engine** for the agent suite. You answer one question: **"what exists in this codebase?"**
|
|
31
|
+
|
|
32
|
+
You find files, read key sections, and return structured context. Your output gives callers enough to understand the code without reading every file themselves.
|
|
33
|
+
|
|
34
|
+
## Your role in the agent suite
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Explorer → "what exists in your code" (you)
|
|
38
|
+
Librarian → "what should you do about it" (external docs)
|
|
39
|
+
Analyzer → "how does it work step-by-step" (flow tracing)
|
|
40
|
+
```
|
|
31
41
|
|
|
32
42
|
## How you work
|
|
33
43
|
|
|
34
|
-
1.
|
|
35
|
-
2.
|
|
36
|
-
3.
|
|
37
|
-
4. Return concise findings with exact file paths and line numbers
|
|
44
|
+
1. **Find files** — use Glob for patterns, Grep for content search, `ls` for structure
|
|
45
|
+
2. **Read key sections** — once you find relevant files, read enough to extract signatures, types, and key logic
|
|
46
|
+
3. **Return structured context** — file paths, exports, function signatures, relevant code snippets
|
|
38
47
|
|
|
39
|
-
##
|
|
48
|
+
## Output format
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
## Codebase: {what was searched for}
|
|
52
|
+
|
|
53
|
+
### Files found
|
|
54
|
+
- `path/to/file.ts` (N lines) — {role}
|
|
55
|
+
- Exports: `functionA(arg: Type): ReturnType`, `ComponentB`, `TypeC`
|
|
56
|
+
- Key logic: {what the file does, 1-2 sentences}
|
|
57
|
+
- Relevant lines: L42-L58 ({what's there})
|
|
58
|
+
|
|
59
|
+
### Structure
|
|
60
|
+
- {how these files relate to each other}
|
|
61
|
+
- {directory organization pattern}
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
### Patterns observed
|
|
64
|
+
- {naming conventions, error handling, state management approach}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### What to return
|
|
68
|
+
|
|
69
|
+
**Always include:** file paths, line numbers, function/component signatures, type definitions, export lists, key conditional logic (2-5 lines max per snippet).
|
|
70
|
+
|
|
71
|
+
**Never include:** full file contents, entire function bodies, JSX markup, import blocks, boilerplate. The caller doesn't need 200 lines — they need to know the shape.
|
|
72
|
+
|
|
73
|
+
**Exception:** Files under 30 lines (configs, migrations, small utilities) — include verbatim since summarizing would be longer.
|
|
74
|
+
|
|
75
|
+
## Scope guard
|
|
42
76
|
|
|
43
|
-
|
|
77
|
+
You are a **codebase locator and summarizer**. You search the repo and return structured context.
|
|
44
78
|
|
|
45
|
-
|
|
79
|
+
- If the caller asks for external docs or best practices → tell them to use @librarian
|
|
80
|
+
- If the caller asks for step-by-step flow analysis → return the file paths and tell them to use @analyzer
|
|
81
|
+
- If the search is too complex for you (cross-cutting dependencies, multi-step reasoning) → say so and recommend @explore-deep
|
|
46
82
|
|
|
47
83
|
## Rules
|
|
48
84
|
|
|
49
85
|
- NEVER write or edit files
|
|
50
|
-
- NEVER
|
|
86
|
+
- NEVER return full file contents — return structured summaries with key snippets
|
|
51
87
|
- Be fast — breadth first, then drill into relevant areas
|
|
52
88
|
- Return structured results the caller can act on immediately
|
|
53
89
|
- If you can't find what's needed, say so
|
|
54
|
-
- If the request is too heavy for you, say so and recommend the right agent
|
package/agents/librarian.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: External knowledge specialist. Fetches documentation, best practices, and implementation guidance from outside the codebase. Pairs with Explorer — Explorer finds what exists in your code, Librarian finds what you should do.
|
|
3
3
|
model: openai/gpt-5.2
|
|
4
4
|
temperature: 0
|
|
5
5
|
mode: subagent
|
|
6
6
|
color: "#1D9E75"
|
|
7
7
|
tools:
|
|
8
|
-
read:
|
|
8
|
+
read: false
|
|
9
9
|
write: false
|
|
10
10
|
edit: false
|
|
11
11
|
bash: true
|
|
12
|
-
glob:
|
|
13
|
-
grep:
|
|
12
|
+
glob: false
|
|
13
|
+
grep: false
|
|
14
14
|
fetch: true
|
|
15
15
|
task: true
|
|
16
16
|
permission:
|
|
@@ -18,61 +18,71 @@ permission:
|
|
|
18
18
|
"*": ask
|
|
19
19
|
"rtk *": allow
|
|
20
20
|
"echo *": allow
|
|
21
|
+
task:
|
|
22
|
+
"*": deny
|
|
23
|
+
"explore": allow
|
|
24
|
+
"explore-deep": allow
|
|
21
25
|
---
|
|
22
26
|
|
|
23
|
-
You are the
|
|
27
|
+
You are the external knowledge specialist. You find documentation, best practices, patterns, and implementation guidance from **outside the codebase**. You never search the codebase directly — that's Explorer's job.
|
|
24
28
|
|
|
25
|
-
##
|
|
29
|
+
## Your role in the agent suite
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
```
|
|
32
|
+
Explorer → "what exists in your code"
|
|
33
|
+
Librarian → "what should you do about it"
|
|
34
|
+
```
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
When a caller needs context before planning or building, the flow is:
|
|
37
|
+
1. **Explorer** scans the codebase → returns file paths, snippets, structure
|
|
38
|
+
2. **You** take that context and find external knowledge → docs, patterns, best practices
|
|
39
|
+
3. **Caller** gets both reality (Explorer) and direction (Librarian)
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
## How you work
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
1. **Receive context** — the caller (or Explorer output) tells you what the codebase looks like. If you need codebase context and don't have it, delegate to @explore first.
|
|
44
|
+
2. **Fetch external knowledge** — library docs, framework guides, API references, best practice articles, migration guides.
|
|
45
|
+
3. **Validate patterns** — cross-reference what the codebase does with what the docs recommend.
|
|
46
|
+
4. **Return actionable guidance** — not just "here's the docs" but "here's what you should do, based on the docs and your codebase."
|
|
37
47
|
|
|
38
48
|
## Output format
|
|
39
49
|
|
|
40
50
|
```
|
|
41
|
-
##
|
|
51
|
+
## Guidance: {topic}
|
|
42
52
|
|
|
43
|
-
###
|
|
44
|
-
- {
|
|
45
|
-
- {finding 2}
|
|
46
|
-
- {finding 3}
|
|
53
|
+
### Context received
|
|
54
|
+
- {brief summary of what Explorer/caller told you about the codebase}
|
|
47
55
|
|
|
48
|
-
###
|
|
49
|
-
-
|
|
50
|
-
|
|
56
|
+
### Recommended approach
|
|
57
|
+
- {concrete recommendation with reasoning}
|
|
58
|
+
- {alternative if applicable}
|
|
51
59
|
|
|
52
|
-
###
|
|
53
|
-
- {
|
|
54
|
-
- {
|
|
60
|
+
### Documentation references
|
|
61
|
+
- {URL or source} — {key takeaway relevant to this task}
|
|
62
|
+
- {URL or source} — {key takeaway}
|
|
55
63
|
|
|
56
|
-
###
|
|
57
|
-
- {
|
|
64
|
+
### Implementation patterns
|
|
65
|
+
- {pattern name}: {how to apply it, with code snippets if helpful}
|
|
66
|
+
- {anti-pattern to avoid}: {why, what to do instead}
|
|
58
67
|
|
|
59
|
-
###
|
|
60
|
-
- {
|
|
61
|
-
- {
|
|
68
|
+
### Caveats
|
|
69
|
+
- {version-specific gotchas, breaking changes, deprecations}
|
|
70
|
+
- {edge cases the docs mention}
|
|
62
71
|
```
|
|
63
72
|
|
|
64
|
-
## When to delegate
|
|
65
|
-
|
|
66
|
-
- **@explore** — "find files matching X pattern" — fast, cheap, returns paths
|
|
67
|
-
- **@explore-deep** — complex cross-cutting searches, dependency tracing across many directories
|
|
73
|
+
## When to delegate to Explorer
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
If the caller asks you a question that requires codebase knowledge you don't have:
|
|
76
|
+
- Delegate to @explore — "find files related to X" or "what pattern does the codebase use for Y"
|
|
77
|
+
- Wait for Explorer's response, then combine it with your external knowledge
|
|
78
|
+
- Never guess about what's in the codebase — either you were told, or you ask Explorer
|
|
70
79
|
|
|
71
80
|
## Rules
|
|
72
81
|
|
|
73
|
-
- NEVER write or modify
|
|
74
|
-
- NEVER
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
- If
|
|
82
|
+
- NEVER read, write, or modify codebase files directly — you have no file tools
|
|
83
|
+
- NEVER guess about codebase structure — if you need to know, delegate to @explore
|
|
84
|
+
- ALWAYS cite your sources — URLs, doc versions, specific sections
|
|
85
|
+
- Be specific and actionable — "use `middleware()` from v4.2+" not "check the docs"
|
|
86
|
+
- Keep output concise. Your output lands in an expensive model's context window.
|
|
87
|
+
- If you can't find relevant external docs, say so. Don't fabricate references.
|
|
88
|
+
- If the caller asks for codebase analysis (how does X work?), tell them to use @explore or @analyzer instead — that's not your job.
|
package/agents/plan.md
CHANGED
|
@@ -61,16 +61,18 @@ When a user describes work, enter interview mode. Ask focused questions to elimi
|
|
|
61
61
|
- Are there existing patterns in the codebase to follow or break from?
|
|
62
62
|
|
|
63
63
|
While interviewing:
|
|
64
|
-
- Delegate to @explore for
|
|
65
|
-
- Delegate to @librarian for
|
|
64
|
+
- Delegate to @explore for codebase research — file structure, function signatures, types, patterns. **This is your default for understanding what exists.**
|
|
65
|
+
- Delegate to @librarian for external knowledge — library docs, best practices, migration guides. **Librarian does NOT search the codebase.**
|
|
66
66
|
- Delegate to @analyzer to understand how existing systems work (control flow, data flow, interactions)
|
|
67
67
|
- Do NOT proceed to planning until requirements are clear
|
|
68
68
|
- If the user says "just plan it" or "skip questions," ask the 2 most critical questions only, then proceed
|
|
69
69
|
|
|
70
|
+
**Golden rule: Explorer first, Librarian second. Never the opposite.**
|
|
71
|
+
|
|
70
72
|
**Delegation pattern for feature planning:**
|
|
71
|
-
1. @
|
|
73
|
+
1. @explore → "find all code related to X" → returns file paths + structured summaries
|
|
72
74
|
2. @analyzer → "explain how X works" → returns step-by-step flow analysis
|
|
73
|
-
|
|
75
|
+
3. @librarian (only if needed) → "what do the docs say about Y?" → returns external guidance
|
|
74
76
|
|
|
75
77
|
### Wave 2: Gap analysis (mandatory)
|
|
76
78
|
|
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
|
|
@@ -19513,6 +19514,9 @@ class WaveStateManager {
|
|
|
19513
19514
|
if (!state) {
|
|
19514
19515
|
throw new Error(`No active plan found for session '${sessionID}'.`);
|
|
19515
19516
|
}
|
|
19517
|
+
if (state.accepted) {
|
|
19518
|
+
throw new Error("Plan has already been accepted. No further wave advances are needed. The planning session is complete.");
|
|
19519
|
+
}
|
|
19516
19520
|
if (state.wave >= 4) {
|
|
19517
19521
|
throw new Error("Wave is already at 4 and cannot be advanced further.");
|
|
19518
19522
|
}
|
|
@@ -19582,6 +19586,29 @@ class WaveStateManager {
|
|
|
19582
19586
|
this.persist(nextState);
|
|
19583
19587
|
return nextState;
|
|
19584
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
|
+
}
|
|
19585
19612
|
persist(state) {
|
|
19586
19613
|
this.ensureDir();
|
|
19587
19614
|
const filePath = join(this.stateDir, `${state.planName}.json`);
|
|
@@ -19624,6 +19651,155 @@ var WAVE_NEXT_STEPS = {
|
|
|
19624
19651
|
3: "writing the plan file",
|
|
19625
19652
|
4: "plan review and refinement"
|
|
19626
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
|
+
}
|
|
19627
19803
|
function hasStringProp(obj, key) {
|
|
19628
19804
|
return key in obj && typeof obj[key] === "string";
|
|
19629
19805
|
}
|
|
@@ -19639,6 +19815,41 @@ function extractSessionID(result) {
|
|
|
19639
19815
|
}
|
|
19640
19816
|
return;
|
|
19641
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
|
+
}
|
|
19642
19853
|
function getWaveLabel(wave) {
|
|
19643
19854
|
return WAVE_NAMES[wave];
|
|
19644
19855
|
}
|
|
@@ -19661,6 +19872,7 @@ function isBlockedEnvRead(filePath) {
|
|
|
19661
19872
|
return fileName.startsWith(".env.");
|
|
19662
19873
|
}
|
|
19663
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.";
|
|
19664
19876
|
return [
|
|
19665
19877
|
"## \uD83D\uDD12 Discipline Plugin \u2014 Wave State",
|
|
19666
19878
|
"",
|
|
@@ -19674,7 +19886,7 @@ function buildWaveStateSystemBlock(wave, planName) {
|
|
|
19674
19886
|
"- Wave 3 (Plan Generation): NOW write the plan to tasks/plans/{planName}.md using the structured template.",
|
|
19675
19887
|
"- Wave 4 (Review): Self-review the plan. Delegate to @oracle for high-stakes decisions. Edit the plan if needed.",
|
|
19676
19888
|
"",
|
|
19677
|
-
`**You are in Wave ${wave}
|
|
19889
|
+
`**You are in Wave ${wave}.** ${advanceGuidance}`,
|
|
19678
19890
|
"**Writing to tasks/plans/*.md is BLOCKED until Wave 3.**"
|
|
19679
19891
|
].join(`
|
|
19680
19892
|
`);
|
|
@@ -19684,7 +19896,8 @@ function buildWave2OraclePrompt() {
|
|
|
19684
19896
|
"## MANDATORY: Wave 2 Oracle Check",
|
|
19685
19897
|
"Before you can advance to Wave 3, delegate to `@oracle` once for a gap-analysis sanity check.",
|
|
19686
19898
|
'Use the Task tool with `subagent_type: "oracle"` and summarize the result in your analysis.',
|
|
19687
|
-
"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."
|
|
19688
19901
|
].join(`
|
|
19689
19902
|
`);
|
|
19690
19903
|
}
|
|
@@ -19698,6 +19911,21 @@ function buildPlanReadOnlyReminder() {
|
|
|
19698
19911
|
].join(`
|
|
19699
19912
|
`);
|
|
19700
19913
|
}
|
|
19914
|
+
function buildPostAcceptPrompt(planName) {
|
|
19915
|
+
const planPath = `tasks/plans/${planName}.md`;
|
|
19916
|
+
return [
|
|
19917
|
+
"## Discipline Plugin \u2014 Plan Accepted",
|
|
19918
|
+
"",
|
|
19919
|
+
`The plan \`${planPath}\` has been accepted and the planning session is complete.`,
|
|
19920
|
+
"",
|
|
19921
|
+
"**You are DONE. Do not call any more tools. Do not call advance_wave. Do not take further action.**",
|
|
19922
|
+
"",
|
|
19923
|
+
"Tell the user the plan is accepted and ready for a Build agent to execute.",
|
|
19924
|
+
"If the automatic Build session handoff succeeded, confirm that.",
|
|
19925
|
+
"If it fell back to manual, tell the user to switch to the Build agent."
|
|
19926
|
+
].join(`
|
|
19927
|
+
`);
|
|
19928
|
+
}
|
|
19701
19929
|
function buildPlanHandoffPrompt(planName) {
|
|
19702
19930
|
const planPath = `tasks/plans/${planName}.md`;
|
|
19703
19931
|
return [
|
|
@@ -19890,6 +20118,25 @@ async function handleCompacting(ctx, input, output) {
|
|
|
19890
20118
|
}
|
|
19891
20119
|
output.context.push(buildCompactionContext(state));
|
|
19892
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
|
+
}
|
|
19893
20140
|
async function handleSystemTransform(ctx, input, output) {
|
|
19894
20141
|
const sessionID = input.sessionID;
|
|
19895
20142
|
const state = sessionID ? ctx.manager.getState(sessionID) : undefined;
|
|
@@ -19898,19 +20145,32 @@ async function handleSystemTransform(ctx, input, output) {
|
|
|
19898
20145
|
return;
|
|
19899
20146
|
}
|
|
19900
20147
|
output.system.push(buildWaveStateSystemBlock(state.wave, state.planName));
|
|
20148
|
+
if (state.accepted) {
|
|
20149
|
+
output.system.push(buildPostAcceptPrompt(state.planName));
|
|
20150
|
+
return;
|
|
20151
|
+
}
|
|
19901
20152
|
if (isPlanContext(input.agent) && state.wave < 3) {
|
|
19902
20153
|
output.system.push(buildPlanReadOnlyReminder());
|
|
19903
20154
|
}
|
|
19904
|
-
if (isPlanContext(input.agent) && state.wave === 2 && !state.
|
|
20155
|
+
if (isPlanContext(input.agent) && state.wave === 2 && !state.oracleReviewedAt) {
|
|
19905
20156
|
output.system.push(buildWave2OraclePrompt());
|
|
19906
20157
|
}
|
|
19907
|
-
if (state.wave === 4
|
|
20158
|
+
if (state.wave === 4) {
|
|
19908
20159
|
const planFilePath = resolve2(ctx.worktree, `tasks/plans/${state.planName}.md`);
|
|
19909
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
|
+
}
|
|
19910
20170
|
output.system.push(buildPlanHandoffPrompt(state.planName));
|
|
19911
20171
|
}
|
|
19912
20172
|
}
|
|
19913
|
-
if (sessionID && isPlanContext(input.agent)
|
|
20173
|
+
if (sessionID && isPlanContext(input.agent)) {
|
|
19914
20174
|
const nudgeState = getOrCreateTodoNudgeState(ctx, sessionID);
|
|
19915
20175
|
const todos = await readSessionTodos(ctx.client, ctx.directory, sessionID);
|
|
19916
20176
|
const hasSeededTodo = todos !== undefined && hasPlanningTodoChecklist(todos, state.planName);
|
|
@@ -19947,6 +20207,19 @@ async function handleToolExecuteBefore(ctx, input, output) {
|
|
|
19947
20207
|
}
|
|
19948
20208
|
}
|
|
19949
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
|
+
}
|
|
19950
20223
|
const state = ctx.manager.getState(input.sessionID);
|
|
19951
20224
|
if (!state || state.accepted) {
|
|
19952
20225
|
return;
|
|
@@ -20006,9 +20279,31 @@ function createAdvanceWaveTool(ctx) {
|
|
|
20006
20279
|
}
|
|
20007
20280
|
const waveName = WAVE_NAMES[state.wave];
|
|
20008
20281
|
const nextStep = WAVE_NEXT_STEPS[state.wave];
|
|
20009
|
-
|
|
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}`;
|
|
20010
20297
|
} catch (error45) {
|
|
20011
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
|
+
}
|
|
20012
20307
|
return `Error: ${message}`;
|
|
20013
20308
|
}
|
|
20014
20309
|
}
|
|
@@ -20097,8 +20392,9 @@ function createAcceptPlanTool(ctx) {
|
|
|
20097
20392
|
"Plan accepted.",
|
|
20098
20393
|
`Plan file: ${relativePlanPath}`,
|
|
20099
20394
|
"Direct session handoff is unavailable in this environment.",
|
|
20100
|
-
"Fallback: switch to Build agent and read the plan file
|
|
20101
|
-
`State saved with accepted timestamp ${acceptedState.acceptedAt}
|
|
20395
|
+
"Fallback: tell the user to switch to the Build agent and read the plan file.",
|
|
20396
|
+
`State saved with accepted timestamp ${acceptedState.acceptedAt}.`,
|
|
20397
|
+
"IMPORTANT: The planning session is now COMPLETE. Do NOT call advance_wave or any other tools. Just confirm to the user."
|
|
20102
20398
|
].join(" ");
|
|
20103
20399
|
}
|
|
20104
20400
|
return [
|
|
@@ -20106,21 +20402,25 @@ function createAcceptPlanTool(ctx) {
|
|
|
20106
20402
|
`Plan file: ${relativePlanPath}`,
|
|
20107
20403
|
`Build session: ${buildSessionID}`,
|
|
20108
20404
|
"First build action seeded: read the plan file with clean handoff context.",
|
|
20109
|
-
`State saved with accepted timestamp ${acceptedState.acceptedAt}
|
|
20405
|
+
`State saved with accepted timestamp ${acceptedState.acceptedAt}.`,
|
|
20406
|
+
"IMPORTANT: The planning session is now COMPLETE. Do NOT call advance_wave or any other tools. Just confirm to the user."
|
|
20110
20407
|
].join(" ");
|
|
20111
20408
|
}
|
|
20112
20409
|
});
|
|
20113
20410
|
}
|
|
20114
20411
|
var DisciplinePlugin = async ({ worktree, directory, client }) => {
|
|
20115
20412
|
const pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
20413
|
+
const pluginVersion = readPluginVersion(pluginDir);
|
|
20116
20414
|
const agentConfigs = loadAgentConfigs(resolve2(pluginDir, "../agents"));
|
|
20117
20415
|
const ctx = {
|
|
20118
20416
|
manager: new WaveStateManager(worktree),
|
|
20119
20417
|
todoNudges: new Map,
|
|
20418
|
+
notifications: new NotificationManager,
|
|
20120
20419
|
worktree,
|
|
20121
20420
|
directory,
|
|
20122
20421
|
client
|
|
20123
20422
|
};
|
|
20423
|
+
await showStartupToast(client, directory, pluginVersion);
|
|
20124
20424
|
return {
|
|
20125
20425
|
config: async (input) => {
|
|
20126
20426
|
const agents = input.agent;
|
|
@@ -20134,6 +20434,7 @@ var DisciplinePlugin = async ({ worktree, directory, client }) => {
|
|
|
20134
20434
|
"experimental.chat.system.transform": (input, output) => handleSystemTransform(ctx, input, output),
|
|
20135
20435
|
"tool.execute.before": (input, output) => handleToolExecuteBefore(ctx, input, output),
|
|
20136
20436
|
"tool.execute.after": (input, output) => handleToolExecuteAfter(ctx, input, output),
|
|
20437
|
+
"session.idle": (input, output) => handleSessionIdle(ctx, input, output),
|
|
20137
20438
|
tool: {
|
|
20138
20439
|
advance_wave: createAdvanceWaveTool(ctx),
|
|
20139
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;
|