opencode-discipline 0.1.1 → 0.1.3
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/agents/explore.md +43 -0
- package/agents/plan.md +12 -2
- package/dist/index.js +202 -192
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Fast codebase search. Finds files, patterns, and structure quickly. Cheap and parallel-friendly.
|
|
3
|
+
model: openai/gpt-4.1-mini
|
|
4
|
+
temperature: 0
|
|
5
|
+
mode: subagent
|
|
6
|
+
color: "#78909C"
|
|
7
|
+
tools:
|
|
8
|
+
read: true
|
|
9
|
+
write: false
|
|
10
|
+
edit: false
|
|
11
|
+
bash: true
|
|
12
|
+
glob: true
|
|
13
|
+
grep: true
|
|
14
|
+
task: false
|
|
15
|
+
permission:
|
|
16
|
+
bash:
|
|
17
|
+
"*": deny
|
|
18
|
+
"rtk *": allow
|
|
19
|
+
"find *": allow
|
|
20
|
+
"ls *": allow
|
|
21
|
+
"cat *": allow
|
|
22
|
+
"head *": allow
|
|
23
|
+
"tail *": allow
|
|
24
|
+
"wc *": allow
|
|
25
|
+
"git log *": allow
|
|
26
|
+
"git show *": allow
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
You are a fast codebase explorer. Your job is to find files, patterns, and structure as quickly as possible. You never implement or modify anything.
|
|
30
|
+
|
|
31
|
+
## How you work
|
|
32
|
+
|
|
33
|
+
1. Start with project structure — `ls`, file tree, config files
|
|
34
|
+
2. Use Glob for file patterns, Grep for content search
|
|
35
|
+
3. Read only what's relevant — don't dump entire files
|
|
36
|
+
4. Return concise findings with exact file paths and line numbers
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
- NEVER write or edit files
|
|
41
|
+
- Be fast — breadth first, then drill into relevant areas
|
|
42
|
+
- Return structured results the caller can act on immediately
|
|
43
|
+
- If you can't find what's needed, say so
|
package/agents/plan.md
CHANGED
|
@@ -27,6 +27,12 @@ permission:
|
|
|
27
27
|
"cargo check *": allow
|
|
28
28
|
"cargo test --no-run *": allow
|
|
29
29
|
"npm run type-check *": allow
|
|
30
|
+
"bun run type-check *": allow
|
|
31
|
+
"bun run type-check": allow
|
|
32
|
+
"bun test *": allow
|
|
33
|
+
"bun test": allow
|
|
34
|
+
"bun run build *": allow
|
|
35
|
+
"bun run build": allow
|
|
30
36
|
"find *": allow
|
|
31
37
|
"wc *": allow
|
|
32
38
|
"mkdir *": allow
|
|
@@ -148,10 +154,13 @@ For high-stakes plans (production changes, data migrations, auth refactors):
|
|
|
148
154
|
|
|
149
155
|
### Presenting the plan
|
|
150
156
|
|
|
151
|
-
After writing:
|
|
157
|
+
After writing and completing Wave 4 review:
|
|
152
158
|
1. Tell the user: "Plan written to `tasks/plans/{filename}.md`"
|
|
153
159
|
2. Summarize in 3-5 sentences
|
|
154
|
-
3.
|
|
160
|
+
3. Present exactly three choices and ask the user to pick one:
|
|
161
|
+
- **Accept plan & start work** — Call `accept_plan(action="accept")` to create a new Build session with the plan registered. The Build agent will start implementing immediately.
|
|
162
|
+
- **Start work later** — Do nothing. The plan is saved and the user can start a Build session manually whenever they're ready.
|
|
163
|
+
- **I have modifications to make** — Call `accept_plan(action="revise")` and stay in Wave 4 to incorporate the user's feedback. After revisions, present these three choices again.
|
|
155
164
|
|
|
156
165
|
## Rules
|
|
157
166
|
|
|
@@ -162,3 +171,4 @@ After writing:
|
|
|
162
171
|
- Prefer small phases (2-4 items). If a phase has 5+ items, split it.
|
|
163
172
|
- Interleave tests with implementation — never "Phase N: write all tests."
|
|
164
173
|
- A good plan lets the Build agent one-shot the implementation. That's the bar.
|
|
174
|
+
- `advance_wave` and `accept_plan` are plugin tools you MUST call — they are not implementation actions. Calling them is part of your planning workflow, not a violation of read-only mode.
|
package/dist/index.js
CHANGED
|
@@ -12619,20 +12619,18 @@ var WAVE_NEXT_STEPS = {
|
|
|
12619
12619
|
3: "writing the plan file",
|
|
12620
12620
|
4: "plan review and refinement"
|
|
12621
12621
|
};
|
|
12622
|
+
function hasStringProp(obj, key) {
|
|
12623
|
+
return key in obj && typeof obj[key] === "string";
|
|
12624
|
+
}
|
|
12622
12625
|
function extractSessionID(result) {
|
|
12623
12626
|
if (!result || typeof result !== "object") {
|
|
12624
12627
|
return;
|
|
12625
12628
|
}
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
return value.id;
|
|
12629
|
+
if (hasStringProp(result, "id")) {
|
|
12630
|
+
return result.id;
|
|
12629
12631
|
}
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
const dataRecord = data;
|
|
12633
|
-
if (typeof dataRecord.id === "string") {
|
|
12634
|
-
return dataRecord.id;
|
|
12635
|
-
}
|
|
12632
|
+
if ("data" in result && result.data && typeof result.data === "object" && hasStringProp(result.data, "id")) {
|
|
12633
|
+
return result.data.id;
|
|
12636
12634
|
}
|
|
12637
12635
|
return;
|
|
12638
12636
|
}
|
|
@@ -12735,11 +12733,8 @@ function extractTodos(response) {
|
|
|
12735
12733
|
return [];
|
|
12736
12734
|
}
|
|
12737
12735
|
function hasPlanKickoffTodo(todos, planName) {
|
|
12738
|
-
const
|
|
12739
|
-
return todos.some((todo) =>
|
|
12740
|
-
const content = todo.content.toLowerCase();
|
|
12741
|
-
return content.includes(planNameLower) && content.includes("plan");
|
|
12742
|
-
});
|
|
12736
|
+
const expected = buildTodoSeedContent(planName).toLowerCase();
|
|
12737
|
+
return todos.some((todo) => todo.content.toLowerCase().includes(expected));
|
|
12743
12738
|
}
|
|
12744
12739
|
async function readSessionTodos(client, directory, sessionID) {
|
|
12745
12740
|
const sessionApi = client.session;
|
|
@@ -12752,197 +12747,212 @@ async function readSessionTodos(client, directory, sessionID) {
|
|
|
12752
12747
|
path: { id: sessionID }
|
|
12753
12748
|
});
|
|
12754
12749
|
return extractTodos(response);
|
|
12755
|
-
} catch {
|
|
12750
|
+
} catch (error45) {
|
|
12751
|
+
console.warn("[discipline] readSessionTodos failed:", error45);
|
|
12756
12752
|
return;
|
|
12757
12753
|
}
|
|
12758
12754
|
}
|
|
12759
|
-
|
|
12760
|
-
const
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12755
|
+
async function handleCompacting(ctx, input, output) {
|
|
12756
|
+
const state = ctx.manager.getState(input.sessionID);
|
|
12757
|
+
if (!state) {
|
|
12758
|
+
return;
|
|
12759
|
+
}
|
|
12760
|
+
output.context.push(buildCompactionContext(state));
|
|
12761
|
+
}
|
|
12762
|
+
async function handleSystemTransform(ctx, input, output) {
|
|
12763
|
+
const sessionID = input.sessionID;
|
|
12764
|
+
const state = sessionID ? ctx.manager.getState(sessionID) : undefined;
|
|
12765
|
+
if (!state) {
|
|
12766
|
+
output.system.push("## Discipline Plugin\nYou are operating under the opencode-discipline plugin. To start a structured plan, call `advance_wave` to begin Wave 1 (Interview).");
|
|
12767
|
+
return;
|
|
12768
|
+
}
|
|
12769
|
+
output.system.push(buildWaveStateSystemBlock(state.wave, state.planName));
|
|
12770
|
+
const currentAgent = input.agent;
|
|
12771
|
+
if (currentAgent === "plan" && state.wave < 3) {
|
|
12772
|
+
output.system.push(buildPlanReadOnlyReminder());
|
|
12773
|
+
}
|
|
12774
|
+
if (sessionID && currentAgent === "plan" && !state.accepted) {
|
|
12775
|
+
const nudgeState = ctx.todoNudges.get(sessionID) ?? {
|
|
12776
|
+
prompted: false,
|
|
12777
|
+
retried: false,
|
|
12778
|
+
seeded: false
|
|
12779
|
+
};
|
|
12780
|
+
const todos = await readSessionTodos(ctx.client, ctx.directory, sessionID);
|
|
12781
|
+
const hasSeededTodo = todos !== undefined && hasPlanKickoffTodo(todos, state.planName);
|
|
12782
|
+
if (hasSeededTodo) {
|
|
12783
|
+
nudgeState.seeded = true;
|
|
12784
|
+
}
|
|
12785
|
+
if (!nudgeState.seeded) {
|
|
12786
|
+
if (!nudgeState.prompted) {
|
|
12787
|
+
output.system.push(buildTodoSeedInstruction(state.planName, false));
|
|
12788
|
+
nudgeState.prompted = true;
|
|
12789
|
+
} else if (todos !== undefined && !hasSeededTodo && !nudgeState.retried) {
|
|
12790
|
+
output.system.push(buildTodoSeedInstruction(state.planName, true));
|
|
12791
|
+
nudgeState.retried = true;
|
|
12776
12792
|
}
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
12793
|
+
}
|
|
12794
|
+
ctx.todoNudges.set(sessionID, nudgeState);
|
|
12795
|
+
}
|
|
12796
|
+
}
|
|
12797
|
+
async function handleToolExecuteBefore(ctx, input, output) {
|
|
12798
|
+
const filePath = output.args?.filePath;
|
|
12799
|
+
if (typeof filePath === "string") {
|
|
12800
|
+
if (input.tool === "read" && isBlockedEnvRead(filePath)) {
|
|
12801
|
+
throw new Error("Reading .env files is blocked by opencode-discipline.");
|
|
12802
|
+
}
|
|
12803
|
+
if (input.tool === "edit" || input.tool === "write") {
|
|
12804
|
+
const state = ctx.manager.getState(input.sessionID);
|
|
12805
|
+
if (state?.accepted) {
|
|
12806
|
+
throw new Error('Plan session is read-only after accept_plan(action="accept"). Switch to Build for implementation.');
|
|
12781
12807
|
}
|
|
12782
|
-
if (
|
|
12783
|
-
|
|
12784
|
-
prompted: false,
|
|
12785
|
-
retried: false,
|
|
12786
|
-
seeded: false
|
|
12787
|
-
};
|
|
12788
|
-
const todos = await readSessionTodos(client, directory, sessionID);
|
|
12789
|
-
const hasSeededTodo = todos !== undefined && hasPlanKickoffTodo(todos, state.planName);
|
|
12790
|
-
if (hasSeededTodo) {
|
|
12791
|
-
nudgeState.seeded = true;
|
|
12792
|
-
}
|
|
12793
|
-
if (!nudgeState.seeded) {
|
|
12794
|
-
if (!nudgeState.prompted) {
|
|
12795
|
-
output.system.push(buildTodoSeedInstruction(state.planName, false));
|
|
12796
|
-
nudgeState.prompted = true;
|
|
12797
|
-
} else if (todos !== undefined && !hasSeededTodo && !nudgeState.retried) {
|
|
12798
|
-
output.system.push(buildTodoSeedInstruction(state.planName, true));
|
|
12799
|
-
nudgeState.retried = true;
|
|
12800
|
-
}
|
|
12801
|
-
}
|
|
12802
|
-
todoNudges.set(sessionID, nudgeState);
|
|
12808
|
+
if (state && state.wave < 3 && isPlanMarkdownFile(ctx.worktree, filePath)) {
|
|
12809
|
+
throw new Error(`Cannot write plan files until Wave 3 (Plan Generation). Current wave: ${state.wave}. Call advance_wave to progress.`);
|
|
12803
12810
|
}
|
|
12811
|
+
}
|
|
12812
|
+
}
|
|
12813
|
+
}
|
|
12814
|
+
function createAdvanceWaveTool(ctx) {
|
|
12815
|
+
return tool({
|
|
12816
|
+
description: "Advance the planning wave. Call this to move from one wave to the next. Wave 1: Interview, Wave 2: Gap Analysis, Wave 3: Plan Generation, Wave 4: Review. You must call this before starting each wave. The first call starts a new plan and generates the plan filename.",
|
|
12817
|
+
args: {
|
|
12818
|
+
sessionID: tool.schema.string().describe("The current session ID")
|
|
12804
12819
|
},
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
}
|
|
12820
|
+
async execute(args, context) {
|
|
12821
|
+
if (context.agent !== "plan") {
|
|
12822
|
+
return "Error: advance_wave is only available to the Plan agent.";
|
|
12823
|
+
}
|
|
12824
|
+
try {
|
|
12825
|
+
const current = ctx.manager.getState(args.sessionID);
|
|
12826
|
+
const state = current ? ctx.manager.advanceWave(args.sessionID) : ctx.manager.startPlan(args.sessionID);
|
|
12827
|
+
if (!current) {
|
|
12828
|
+
ctx.todoNudges.set(args.sessionID, {
|
|
12829
|
+
prompted: false,
|
|
12830
|
+
retried: false,
|
|
12831
|
+
seeded: false
|
|
12832
|
+
});
|
|
12819
12833
|
}
|
|
12834
|
+
const waveName = WAVE_NAMES[state.wave];
|
|
12835
|
+
const nextStep = WAVE_NEXT_STEPS[state.wave];
|
|
12836
|
+
return `Wave ${state.wave} (${waveName}) started for plan '${state.planName}'. You may now proceed with ${nextStep}.`;
|
|
12837
|
+
} catch (error45) {
|
|
12838
|
+
const message = error45 instanceof Error ? error45.message : "Unknown error";
|
|
12839
|
+
return `Error: ${message}`;
|
|
12820
12840
|
}
|
|
12841
|
+
}
|
|
12842
|
+
});
|
|
12843
|
+
}
|
|
12844
|
+
function createAcceptPlanTool(ctx) {
|
|
12845
|
+
return tool({
|
|
12846
|
+
description: "Accept or revise the generated plan. Use action='revise' to keep planning in Wave 4, or action='accept' to hand off to Build with only the plan path.",
|
|
12847
|
+
args: {
|
|
12848
|
+
sessionID: tool.schema.string().describe("The current plan session ID"),
|
|
12849
|
+
action: tool.schema.enum(["accept", "revise"]),
|
|
12850
|
+
planPath: tool.schema.string().optional().describe("Optional relative or absolute path to the plan file")
|
|
12821
12851
|
},
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
].join(" ");
|
|
12881
|
-
}
|
|
12882
|
-
if (state.wave !== 4) {
|
|
12883
|
-
return `Error: accept_plan(action="accept") is only available in Wave 4 (Review). Current wave: ${state.wave}.`;
|
|
12884
|
-
}
|
|
12885
|
-
try {
|
|
12886
|
-
accessSync(absolutePlanPath, constants.R_OK);
|
|
12887
|
-
} catch {
|
|
12888
|
-
return `Error: Plan file '${relativePlanPath}' is missing or unreadable.`;
|
|
12889
|
-
}
|
|
12890
|
-
let buildSessionID = "manual-build-session";
|
|
12891
|
-
let fallback = false;
|
|
12892
|
-
const sessionApi = client.session;
|
|
12893
|
-
const canCreate = sessionApi && typeof sessionApi.create === "function";
|
|
12894
|
-
const canPrompt = sessionApi && typeof sessionApi.prompt === "function";
|
|
12895
|
-
if (canCreate && canPrompt) {
|
|
12896
|
-
try {
|
|
12897
|
-
const createResult = await sessionApi.create({
|
|
12898
|
-
query: { directory },
|
|
12899
|
-
body: { title: `Build handoff: ${state.planName}` }
|
|
12900
|
-
});
|
|
12901
|
-
const createdSessionID = extractSessionID(createResult);
|
|
12902
|
-
if (!createdSessionID) {
|
|
12903
|
-
fallback = true;
|
|
12904
|
-
} else {
|
|
12905
|
-
buildSessionID = createdSessionID;
|
|
12906
|
-
await sessionApi.prompt({
|
|
12907
|
-
query: { directory },
|
|
12908
|
-
path: { id: createdSessionID },
|
|
12909
|
-
body: {
|
|
12910
|
-
agent: "build",
|
|
12911
|
-
system: "Use only this file as source of truth; do not rely on Plan context.",
|
|
12912
|
-
parts: [
|
|
12913
|
-
{
|
|
12914
|
-
type: "text",
|
|
12915
|
-
text: `Read ${relativePlanPath} and implement it phase-by-phase. Use only this plan file as handoff context.`
|
|
12916
|
-
}
|
|
12917
|
-
]
|
|
12852
|
+
async execute(args, context) {
|
|
12853
|
+
if (context.agent !== "plan") {
|
|
12854
|
+
return "Error: accept_plan is only available to the Plan agent.";
|
|
12855
|
+
}
|
|
12856
|
+
const state = ctx.manager.getState(args.sessionID);
|
|
12857
|
+
if (!state) {
|
|
12858
|
+
return `Error: No active plan found for session '${args.sessionID}'.`;
|
|
12859
|
+
}
|
|
12860
|
+
const defaultPlanPath = `tasks/plans/${state.planName}.md`;
|
|
12861
|
+
const resolvedPlanPath = args.planPath ?? defaultPlanPath;
|
|
12862
|
+
const absolutePlanPath = resolve(ctx.worktree, resolvedPlanPath);
|
|
12863
|
+
const relativePlanPath = toWorktreeRelativePath(ctx.worktree, absolutePlanPath);
|
|
12864
|
+
if (!isPlanMarkdownFile(ctx.worktree, absolutePlanPath)) {
|
|
12865
|
+
return "Error: planPath must point to tasks/plans/{planName}.md within the current worktree.";
|
|
12866
|
+
}
|
|
12867
|
+
if (args.action === "revise") {
|
|
12868
|
+
const nextStep = state.wave < 4 ? "Next step: call advance_wave until Wave 4, then continue review/revision." : "Next step: continue in Wave 4 review/revision and update the plan as needed.";
|
|
12869
|
+
return [
|
|
12870
|
+
"Plan revision requested.",
|
|
12871
|
+
`Current wave: ${state.wave} (${getWaveLabel(state.wave)}).`,
|
|
12872
|
+
`Plan file: ${relativePlanPath}`,
|
|
12873
|
+
nextStep
|
|
12874
|
+
].join(" ");
|
|
12875
|
+
}
|
|
12876
|
+
if (state.wave !== 4) {
|
|
12877
|
+
return `Error: accept_plan(action="accept") is only available in Wave 4 (Review). Current wave: ${state.wave}.`;
|
|
12878
|
+
}
|
|
12879
|
+
try {
|
|
12880
|
+
accessSync(absolutePlanPath, constants.R_OK);
|
|
12881
|
+
} catch {
|
|
12882
|
+
return `Error: Plan file '${relativePlanPath}' is missing or unreadable.`;
|
|
12883
|
+
}
|
|
12884
|
+
let buildSessionID = "manual-build-session";
|
|
12885
|
+
let fallback = false;
|
|
12886
|
+
const sessionApi = ctx.client.session;
|
|
12887
|
+
const canCreate = sessionApi && typeof sessionApi.create === "function";
|
|
12888
|
+
const canPrompt = sessionApi && typeof sessionApi.prompt === "function";
|
|
12889
|
+
if (canCreate && canPrompt) {
|
|
12890
|
+
try {
|
|
12891
|
+
const createResult = await sessionApi.create({
|
|
12892
|
+
query: { directory: ctx.directory },
|
|
12893
|
+
body: { title: `Build handoff: ${state.planName}` }
|
|
12894
|
+
});
|
|
12895
|
+
const createdSessionID = extractSessionID(createResult);
|
|
12896
|
+
if (!createdSessionID) {
|
|
12897
|
+
fallback = true;
|
|
12898
|
+
} else {
|
|
12899
|
+
buildSessionID = createdSessionID;
|
|
12900
|
+
await sessionApi.prompt({
|
|
12901
|
+
query: { directory: ctx.directory },
|
|
12902
|
+
path: { id: createdSessionID },
|
|
12903
|
+
body: {
|
|
12904
|
+
agent: "build",
|
|
12905
|
+
system: "Use only this file as source of truth; do not rely on Plan context.",
|
|
12906
|
+
parts: [
|
|
12907
|
+
{
|
|
12908
|
+
type: "text",
|
|
12909
|
+
text: `Read ${relativePlanPath} and implement it phase-by-phase. Use only this plan file as handoff context.`
|
|
12918
12910
|
}
|
|
12919
|
-
|
|
12911
|
+
]
|
|
12920
12912
|
}
|
|
12921
|
-
}
|
|
12922
|
-
fallback = true;
|
|
12923
|
-
}
|
|
12924
|
-
} else {
|
|
12925
|
-
fallback = true;
|
|
12926
|
-
}
|
|
12927
|
-
const acceptedState = manager.markAccepted(args.sessionID, buildSessionID);
|
|
12928
|
-
if (fallback) {
|
|
12929
|
-
return [
|
|
12930
|
-
"Plan accepted.",
|
|
12931
|
-
`Plan file: ${relativePlanPath}`,
|
|
12932
|
-
"Direct session handoff is unavailable in this environment.",
|
|
12933
|
-
"Fallback: switch to Build agent and read the plan file first.",
|
|
12934
|
-
`State saved with accepted timestamp ${acceptedState.acceptedAt}.`
|
|
12935
|
-
].join(" ");
|
|
12913
|
+
});
|
|
12936
12914
|
}
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12915
|
+
} catch {
|
|
12916
|
+
fallback = true;
|
|
12917
|
+
}
|
|
12918
|
+
} else {
|
|
12919
|
+
fallback = true;
|
|
12920
|
+
}
|
|
12921
|
+
const acceptedState = ctx.manager.markAccepted(args.sessionID, buildSessionID);
|
|
12922
|
+
if (fallback) {
|
|
12923
|
+
return [
|
|
12924
|
+
"Plan accepted.",
|
|
12925
|
+
`Plan file: ${relativePlanPath}`,
|
|
12926
|
+
"Direct session handoff is unavailable in this environment.",
|
|
12927
|
+
"Fallback: switch to Build agent and read the plan file first.",
|
|
12928
|
+
`State saved with accepted timestamp ${acceptedState.acceptedAt}.`
|
|
12929
|
+
].join(" ");
|
|
12930
|
+
}
|
|
12931
|
+
return [
|
|
12932
|
+
"Plan accepted and handed off to Build.",
|
|
12933
|
+
`Plan file: ${relativePlanPath}`,
|
|
12934
|
+
`Build session: ${buildSessionID}`,
|
|
12935
|
+
"First build action seeded: read the plan file with clean handoff context.",
|
|
12936
|
+
`State saved with accepted timestamp ${acceptedState.acceptedAt}.`
|
|
12937
|
+
].join(" ");
|
|
12938
|
+
}
|
|
12939
|
+
});
|
|
12940
|
+
}
|
|
12941
|
+
var DisciplinePlugin = async ({ worktree, directory, client }) => {
|
|
12942
|
+
const ctx = {
|
|
12943
|
+
manager: new WaveStateManager(worktree),
|
|
12944
|
+
todoNudges: new Map,
|
|
12945
|
+
worktree,
|
|
12946
|
+
directory,
|
|
12947
|
+
client
|
|
12948
|
+
};
|
|
12949
|
+
return {
|
|
12950
|
+
"experimental.session.compacting": (input, output) => handleCompacting(ctx, input, output),
|
|
12951
|
+
"experimental.chat.system.transform": (input, output) => handleSystemTransform(ctx, input, output),
|
|
12952
|
+
"tool.execute.before": (input, output) => handleToolExecuteBefore(ctx, input, output),
|
|
12953
|
+
tool: {
|
|
12954
|
+
advance_wave: createAdvanceWaveTool(ctx),
|
|
12955
|
+
accept_plan: createAcceptPlanTool(ctx)
|
|
12946
12956
|
}
|
|
12947
12957
|
};
|
|
12948
12958
|
};
|