lsd-pi 1.1.9 → 1.2.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/dist/resources/extensions/slash-commands/context.js +15 -8
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/init.js +47 -0
- package/dist/resources/extensions/slash-commands/plan.js +241 -54
- package/dist/resources/extensions/slash-commands/tools.js +47 -21
- package/dist/resources/extensions/subagent/index.js +5 -10
- package/dist/startup-model-validation.d.ts +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +2 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/types.ts +2 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +10 -3
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -2
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +2 -2
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +2 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +2 -0
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js +2 -0
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +2 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +2 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +6 -2
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +11 -4
- package/packages/pi-ai/src/providers/anthropic-shared.ts +5 -2
- package/packages/pi-ai/src/providers/anthropic.ts +2 -2
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +2 -1
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +3 -1
- package/packages/pi-ai/src/providers/google-vertex.ts +3 -1
- package/packages/pi-ai/src/providers/google.ts +3 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +2 -1
- package/packages/pi-ai/src/providers/openai-completions.ts +2 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +2 -1
- package/packages/pi-ai/src/providers/simple-options.ts +5 -3
- package/packages/pi-ai/src/types.ts +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +57 -20
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/classifier-service.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/classifier-service.js +34 -61
- package/packages/pi-coding-agent/dist/core/classifier-service.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +3 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js +59 -7
- package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -4
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +19 -20
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js +80 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +15 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +31 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +28 -68
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +44 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +41 -5
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +62 -19
- package/packages/pi-coding-agent/src/core/classifier-service.ts +35 -63
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +3 -1
- package/packages/pi-coding-agent/src/core/resource-loader-lsd-md.test.ts +67 -7
- package/packages/pi-coding-agent/src/core/resource-loader.ts +4 -4
- package/packages/pi-coding-agent/src/core/sdk.test.ts +100 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +24 -21
- package/packages/pi-coding-agent/src/core/settings-manager.ts +42 -8
- package/packages/pi-coding-agent/src/core/system-prompt.ts +39 -82
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +6 -0
- package/packages/pi-coding-agent/src/core/tools/bash-interceptor.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +26 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +53 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +41 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -7
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +3 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +2 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/slash-commands/context.ts +15 -8
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/init.ts +55 -0
- package/src/resources/extensions/slash-commands/plan.ts +277 -55
- package/src/resources/extensions/slash-commands/tools.ts +47 -21
- package/src/resources/extensions/subagent/index.ts +5 -10
|
@@ -60,21 +60,25 @@ export default function contextCommand(pi: ExtensionAPI) {
|
|
|
60
60
|
const descLen = t.description?.length ?? 0;
|
|
61
61
|
const schemaLen = JSON.stringify(t.parameters ?? {}).length;
|
|
62
62
|
const totalChars = nameLen + descLen + schemaLen;
|
|
63
|
+
const isActive = activeToolNames.has(t.name ?? "");
|
|
63
64
|
const tokens = Math.ceil(totalChars / 4);
|
|
64
65
|
return {
|
|
65
66
|
name: t.name ?? "(unnamed)",
|
|
67
|
+
totalChars,
|
|
66
68
|
tokens,
|
|
67
69
|
descTokens: Math.ceil(descLen / 4),
|
|
68
70
|
schemaTokens: Math.ceil((nameLen + schemaLen) / 4),
|
|
69
|
-
isActive
|
|
71
|
+
isActive,
|
|
70
72
|
};
|
|
71
73
|
});
|
|
72
74
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const activeSchemaBytes = toolSizes
|
|
76
|
+
.filter((t) => t.isActive)
|
|
77
|
+
.reduce((sum, t) => sum + t.totalChars, 0);
|
|
78
|
+
const registeredSchemaBytes = toolSizes.reduce((sum, t) => sum + t.totalChars, 0);
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const activeToolsTokens = Math.ceil(activeSchemaBytes / 4);
|
|
81
|
+
const registeredToolsTokens = Math.ceil(registeredSchemaBytes / 4);
|
|
78
82
|
|
|
79
83
|
const largestActiveTools = toolSizes
|
|
80
84
|
.filter((t) => t.isActive)
|
|
@@ -109,7 +113,7 @@ export default function contextCommand(pi: ExtensionAPI) {
|
|
|
109
113
|
const usedTokens = contextUsage?.tokens ?? null;
|
|
110
114
|
const percentUsed = contextUsage?.percent ?? null;
|
|
111
115
|
|
|
112
|
-
const fallbackUsedTokens = systemPromptTokens +
|
|
116
|
+
const fallbackUsedTokens = systemPromptTokens + activeToolsTokens + historyTokens;
|
|
113
117
|
const effectiveUsedTokens = usedTokens ?? fallbackUsedTokens;
|
|
114
118
|
const estimatedLabel = usedTokens === null ? " (estimated)" : "";
|
|
115
119
|
|
|
@@ -161,8 +165,11 @@ export default function contextCommand(pi: ExtensionAPI) {
|
|
|
161
165
|
lines.push("");
|
|
162
166
|
|
|
163
167
|
lines.push(`Tools ${activeToolsCount} active / ${totalToolsCount} registered`);
|
|
164
|
-
lines.push(`
|
|
165
|
-
lines.push(` Est. tokens: ~${
|
|
168
|
+
lines.push(` Active schema bytes: ${activeSchemaBytes.toLocaleString()}`);
|
|
169
|
+
lines.push(` Est. tokens: ~${activeToolsTokens.toLocaleString()}`);
|
|
170
|
+
if (activeToolsCount !== totalToolsCount) {
|
|
171
|
+
lines.push(` Registered total: ${registeredSchemaBytes.toLocaleString()} bytes · ~${registeredToolsTokens.toLocaleString()} tok`);
|
|
172
|
+
}
|
|
166
173
|
|
|
167
174
|
if (largestActiveTools.length > 0) {
|
|
168
175
|
lines.push(" Largest active tools:");
|
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
|
2
2
|
import auditCommand from "./audit.js";
|
|
3
3
|
import clearCommand from "./clear.js";
|
|
4
4
|
import contextCommand from "./context.js";
|
|
5
|
+
import initCommand from "./init.js";
|
|
5
6
|
import planCommand from "./plan.js";
|
|
6
7
|
import toolSearchExtension from "./tools.js";
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ export default function slashCommands(pi: ExtensionAPI) {
|
|
|
9
10
|
auditCommand(pi);
|
|
10
11
|
clearCommand(pi);
|
|
11
12
|
contextCommand(pi);
|
|
13
|
+
initCommand(pi);
|
|
12
14
|
planCommand(pi);
|
|
13
15
|
toolSearchExtension(pi);
|
|
14
16
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getAgentDir, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const STARTER_CONTENT = `# Project Context
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
- Add a short description of this project and its purpose.
|
|
9
|
+
|
|
10
|
+
## Commands
|
|
11
|
+
- Build:
|
|
12
|
+
- Test:
|
|
13
|
+
- Lint:
|
|
14
|
+
|
|
15
|
+
## Conventions
|
|
16
|
+
- Add coding conventions, architecture notes, and review expectations.
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
function ensureFile(filePath: string): "created" | "exists" {
|
|
20
|
+
if (existsSync(filePath)) {
|
|
21
|
+
return "exists";
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(filePath, STARTER_CONTENT, "utf-8");
|
|
24
|
+
return "created";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function initCommand(pi: ExtensionAPI) {
|
|
28
|
+
pi.registerCommand("init", {
|
|
29
|
+
description: "Initialize global and project LSD.md files if they do not exist",
|
|
30
|
+
async handler(_args: string, ctx: ExtensionCommandContext) {
|
|
31
|
+
const globalPath = resolve(getAgentDir(), "..", "LSD.md");
|
|
32
|
+
const projectPath = join(ctx.cwd, "LSD.md");
|
|
33
|
+
|
|
34
|
+
const globalStatus = ensureFile(globalPath);
|
|
35
|
+
const projectStatus = ensureFile(projectPath);
|
|
36
|
+
|
|
37
|
+
await ctx.reload();
|
|
38
|
+
|
|
39
|
+
const lines = ["Initialized LSD.md files", ""];
|
|
40
|
+
lines.push(`Global: ${globalStatus === "created" ? "created" : "exists"} ${globalPath}`);
|
|
41
|
+
lines.push(`Project: ${projectStatus === "created" ? "created" : "exists"} ${projectPath}`);
|
|
42
|
+
|
|
43
|
+
if (globalStatus === "exists" && projectStatus === "exists") {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push("Nothing changed.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pi.sendMessage({
|
|
49
|
+
customType: "init:report",
|
|
50
|
+
content: lines.join("\n"),
|
|
51
|
+
display: true,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getPermissionMode,
|
|
5
5
|
isToolCallEventType,
|
|
6
6
|
setPermissionMode,
|
|
7
|
+
SettingsManager,
|
|
7
8
|
type ExtensionAPI,
|
|
8
9
|
type ExtensionCommandContext,
|
|
9
10
|
type PermissionMode,
|
|
@@ -13,6 +14,7 @@ import { join } from "node:path";
|
|
|
13
14
|
const PLAN_ENTRY_TYPE = "plan-mode-state";
|
|
14
15
|
const PLAN_APPROVAL_ACTION_QUESTION_ID = "plan_mode_approval_action";
|
|
15
16
|
const PLAN_APPROVAL_PERMISSION_QUESTION_ID = "plan_mode_approval_permission";
|
|
17
|
+
const PLAN_SUGGEST_QUESTION_ID = "plan_mode_suggest_switch";
|
|
16
18
|
const PLAN_DIR_RE = /(^|[/\\])\.(?:lsd|gsd)[/\\]plan([/\\]|$)/;
|
|
17
19
|
const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.(?:lsd|gsd)(?:[\\/]+plan)?|rtk\s)/;
|
|
18
20
|
const SAFE_TOOLS = new Set([
|
|
@@ -72,10 +74,12 @@ const APPROVE_AUTO_LABEL = "Auto mode";
|
|
|
72
74
|
const APPROVE_BYPASS_LABEL = "Bypass mode";
|
|
73
75
|
const APPROVE_AUTO_SUBAGENT_LABEL = "Execute with subagent in auto mode";
|
|
74
76
|
const APPROVE_BYPASS_SUBAGENT_LABEL = "Execute with subagent in bypass mode";
|
|
77
|
+
const APPROVE_NEW_SESSION_LABEL = "New session with coding model"; // shown in second question when autoSwitchPlanModel is on
|
|
75
78
|
const REVIEW_LABEL = "Let other agent review";
|
|
76
79
|
const REVISE_LABEL = "Revise plan";
|
|
77
80
|
const CANCEL_LABEL = "Cancel";
|
|
78
81
|
const DEFAULT_PLAN_REVIEW_AGENT = "generic";
|
|
82
|
+
const DEFAULT_PLAN_CODING_AGENT = "worker";
|
|
79
83
|
|
|
80
84
|
type PlanApprovalStatus = "pending" | "approved" | "revising" | "cancelled";
|
|
81
85
|
type RestorablePermissionMode = Exclude<PermissionMode, "plan">;
|
|
@@ -108,6 +112,7 @@ const INITIAL_STATE: PlanModeState = {
|
|
|
108
112
|
|
|
109
113
|
let state: PlanModeState = { ...INITIAL_STATE };
|
|
110
114
|
let startedFromFlag = false;
|
|
115
|
+
let reasoningModelSwitchDone = false;
|
|
111
116
|
|
|
112
117
|
function isPlanModeActive(): boolean {
|
|
113
118
|
return getPermissionMode() === "plan";
|
|
@@ -124,7 +129,13 @@ function parseQualifiedModelRef(value: unknown): ModelRef | undefined {
|
|
|
124
129
|
return { provider, id };
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
function
|
|
132
|
+
function parseSubagentName(value: unknown): string | undefined {
|
|
133
|
+
if (typeof value !== "string") return undefined;
|
|
134
|
+
const trimmed = value.trim();
|
|
135
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readPlanModeSettings(): { reasoningModel?: string; reviewModel?: string; codingModel?: string; codingSubagent?: string } {
|
|
128
139
|
try {
|
|
129
140
|
const settingsPath = join(getAgentDir(), "settings.json");
|
|
130
141
|
if (!existsSync(settingsPath)) return {};
|
|
@@ -133,14 +144,19 @@ function readPlanModeSettings(): { reasoningModel?: string; reviewModel?: string
|
|
|
133
144
|
planModeReasoningModel?: unknown;
|
|
134
145
|
planModeReviewModel?: unknown;
|
|
135
146
|
planModeCodingModel?: unknown;
|
|
147
|
+
planModeCodingSubagent?: unknown;
|
|
148
|
+
planModeCodingAgent?: unknown;
|
|
136
149
|
};
|
|
137
150
|
const reasoningModel = parseQualifiedModelRef(parsed.planModeReasoningModel);
|
|
138
151
|
const reviewModel = parseQualifiedModelRef(parsed.planModeReviewModel);
|
|
139
152
|
const codingModel = parseQualifiedModelRef(parsed.planModeCodingModel);
|
|
153
|
+
const codingSubagent = parseSubagentName(parsed.planModeCodingSubagent)
|
|
154
|
+
?? parseSubagentName(parsed.planModeCodingAgent);
|
|
140
155
|
return {
|
|
141
156
|
reasoningModel: reasoningModel ? `${reasoningModel.provider}/${reasoningModel.id}` : undefined,
|
|
142
157
|
reviewModel: reviewModel ? `${reviewModel.provider}/${reviewModel.id}` : undefined,
|
|
143
158
|
codingModel: codingModel ? `${codingModel.provider}/${codingModel.id}` : undefined,
|
|
159
|
+
codingSubagent,
|
|
144
160
|
};
|
|
145
161
|
} catch {
|
|
146
162
|
return {};
|
|
@@ -159,6 +175,42 @@ export function readPlanModeCodingModel(): string | undefined {
|
|
|
159
175
|
return readPlanModeSettings().codingModel;
|
|
160
176
|
}
|
|
161
177
|
|
|
178
|
+
export function readPlanModeCodingSubagent(): string | undefined {
|
|
179
|
+
return readPlanModeSettings().codingSubagent;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function readAutoSuggestPlanModeSetting(): boolean {
|
|
183
|
+
try {
|
|
184
|
+
const settingsPath = join(getAgentDir(), "settings.json");
|
|
185
|
+
if (!existsSync(settingsPath)) return false;
|
|
186
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
187
|
+
const parsed = JSON.parse(raw) as { autoSuggestPlanMode?: unknown };
|
|
188
|
+
return parsed.autoSuggestPlanMode === true;
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function readAutoSwitchPlanModelSetting(): boolean {
|
|
195
|
+
try {
|
|
196
|
+
const settingsPath = join(getAgentDir(), "settings.json");
|
|
197
|
+
if (!existsSync(settingsPath)) return false;
|
|
198
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
199
|
+
const parsed = JSON.parse(raw) as { autoSwitchPlanModel?: unknown };
|
|
200
|
+
return parsed.autoSwitchPlanModel === true;
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildAutoSuggestPlanModeSystemPrompt(): string {
|
|
207
|
+
return [
|
|
208
|
+
`Plan-mode suggestion: if the user's latest request describes a large, multi-step, or ambiguous task — e.g. a refactor, multi-file change, new feature, migration, or anything that benefits from upfront investigation — proactively ask whether to switch to plan mode before making any edits.`,
|
|
209
|
+
`How to suggest: call ask_user_questions with a single question. Set the question id to exactly "${PLAN_SUGGEST_QUESTION_ID}". Ask: "This looks like a complex task. Would you like to switch to plan mode first?". Provide exactly two options: "Yes, switch to plan mode" (recommended) and "No, proceed directly". Do NOT call /plan yourself — wait for the user answer and the system will handle switching automatically.`,
|
|
210
|
+
"Do not suggest plan mode for simple, single-file, or read-only tasks. Do not suggest it if the user is already in plan mode or in the middle of an implementation. Only suggest it once per distinct task.",
|
|
211
|
+
].join(" ");
|
|
212
|
+
}
|
|
213
|
+
|
|
162
214
|
function sameModel(left: ModelRef | undefined, right: ModelRef | undefined): boolean {
|
|
163
215
|
return !!left && !!right && left.provider === right.provider && left.id === right.id;
|
|
164
216
|
}
|
|
@@ -170,6 +222,12 @@ function resolveModelFromContext(ctx: any, modelRef: ModelRef): any | undefined
|
|
|
170
222
|
|
|
171
223
|
function setPermissionModeAndEnv(mode: PermissionMode): void {
|
|
172
224
|
setPermissionMode(mode);
|
|
225
|
+
try {
|
|
226
|
+
const settingsManager = SettingsManager.create();
|
|
227
|
+
settingsManager.setPermissionMode(mode);
|
|
228
|
+
} catch {
|
|
229
|
+
// Best-effort persistence; if settings manager is unavailable, proceed with in-memory only
|
|
230
|
+
}
|
|
173
231
|
process.env.LUCENT_CODE_PERMISSION_MODE = mode;
|
|
174
232
|
}
|
|
175
233
|
|
|
@@ -210,6 +268,24 @@ function restoreStateFromSession(ctx: ExtensionCommandContext | any): void {
|
|
|
210
268
|
}
|
|
211
269
|
}
|
|
212
270
|
|
|
271
|
+
async function enablePlanModeWithModelSwitch(
|
|
272
|
+
pi: ExtensionAPI,
|
|
273
|
+
ctx: any,
|
|
274
|
+
currentModel: ModelRef | undefined,
|
|
275
|
+
next: Partial<Pick<PlanModeState, "task" | "latestPlanPath" | "approvalStatus" | "previousMode" | "preplanModel" | "targetPermissionMode">> = {},
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
enablePlanMode(pi, currentModel, next);
|
|
278
|
+
// Signal that before_agent_start should switch to the reasoning model on next turn
|
|
279
|
+
reasoningModelSwitchDone = false;
|
|
280
|
+
if (!readAutoSwitchPlanModelSetting()) return;
|
|
281
|
+
if (!readPlanModeReasoningModel()) {
|
|
282
|
+
ctx.ui?.notify?.(
|
|
283
|
+
"OpusPlan: set a Plan reasoning model in /settings to auto-switch on entry",
|
|
284
|
+
"info",
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
213
289
|
function enablePlanMode(
|
|
214
290
|
pi: ExtensionAPI,
|
|
215
291
|
currentModel: ModelRef | undefined,
|
|
@@ -239,6 +315,7 @@ function leavePlanMode(
|
|
|
239
315
|
nextPermissionMode: RestorablePermissionMode,
|
|
240
316
|
clearTask = false,
|
|
241
317
|
): RestorablePermissionMode {
|
|
318
|
+
reasoningModelSwitchDone = false;
|
|
242
319
|
setPermissionModeAndEnv(nextPermissionMode);
|
|
243
320
|
setState(pi, {
|
|
244
321
|
active: false,
|
|
@@ -275,14 +352,14 @@ function buildExecutionKickoffMessage(options: { permissionMode: RestorablePermi
|
|
|
275
352
|
}
|
|
276
353
|
|
|
277
354
|
const codingModel = readPlanModeCodingModel();
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
355
|
+
const codingSubagent = readPlanModeCodingSubagent() ?? DEFAULT_PLAN_CODING_AGENT;
|
|
356
|
+
const agentInvocationInstruction = codingModel
|
|
357
|
+
? `Invoke the subagent tool with exact parameters agent "${codingSubagent}" and model="${codingModel}" to implement the plan end-to-end.`
|
|
358
|
+
: `Invoke the subagent tool with exact parameter agent "${codingSubagent}" to implement the plan end-to-end.`;
|
|
281
359
|
|
|
282
360
|
const details: string[] = [
|
|
283
361
|
"Plan approved. Exit plan mode and execute the approved plan with a subagent now.",
|
|
284
|
-
|
|
285
|
-
modelInstruction,
|
|
362
|
+
agentInvocationInstruction,
|
|
286
363
|
`Execution permission mode is now \"${permissionMode}\".`,
|
|
287
364
|
];
|
|
288
365
|
if (task) details.push(`Original task: ${task}`);
|
|
@@ -291,6 +368,38 @@ function buildExecutionKickoffMessage(options: { permissionMode: RestorablePermi
|
|
|
291
368
|
return details.join(" ");
|
|
292
369
|
}
|
|
293
370
|
|
|
371
|
+
// Pending new-session payload — set before triggering the internal command
|
|
372
|
+
interface PendingNewSession {
|
|
373
|
+
codingModelRef: ModelRef | undefined;
|
|
374
|
+
codingSubagent: string;
|
|
375
|
+
planPath: string | undefined;
|
|
376
|
+
planContent: string | undefined;
|
|
377
|
+
task: string;
|
|
378
|
+
}
|
|
379
|
+
let pendingNewSession: PendingNewSession | null = null;
|
|
380
|
+
|
|
381
|
+
function scheduleNewSession(pi: ExtensionAPI, ctx: any): void {
|
|
382
|
+
const codingModelRef = parseQualifiedModelRef(readPlanModeCodingModel());
|
|
383
|
+
const codingSubagent = readPlanModeCodingSubagent() ?? DEFAULT_PLAN_CODING_AGENT;
|
|
384
|
+
const planPath = state.latestPlanPath;
|
|
385
|
+
const planContent = planPath ? readPlanArtifact(planPath) : undefined;
|
|
386
|
+
|
|
387
|
+
pendingNewSession = {
|
|
388
|
+
codingModelRef,
|
|
389
|
+
codingSubagent,
|
|
390
|
+
planPath,
|
|
391
|
+
planContent,
|
|
392
|
+
task: state.task,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
leavePlanMode(pi, "approved", "auto");
|
|
396
|
+
ctx.ui?.notify?.("Plan approved. Starting new session…", "info");
|
|
397
|
+
|
|
398
|
+
// Trigger the internal command which has ExtensionCommandContext (ctx.newSession available).
|
|
399
|
+
// Must use the /prefix so tryExecuteExtensionCommand parses the name correctly.
|
|
400
|
+
pi.executeSlashCommand("/plan-execute-new-session");
|
|
401
|
+
}
|
|
402
|
+
|
|
294
403
|
async function approvePlan(
|
|
295
404
|
pi: ExtensionAPI,
|
|
296
405
|
ctx: any,
|
|
@@ -307,7 +416,13 @@ async function approvePlan(
|
|
|
307
416
|
targetPermissionMode: permissionMode,
|
|
308
417
|
};
|
|
309
418
|
leavePlanMode(pi, "approved", permissionMode);
|
|
310
|
-
|
|
419
|
+
// Deliver the kickoff as a steering message so it is injected BEFORE the LLM
|
|
420
|
+
// produces its next assistant turn. Using "followUp" would defer delivery
|
|
421
|
+
// until the agent has no more tool calls, which lets the LLM call the
|
|
422
|
+
// subagent tool with the default session model BEFORE it ever sees the
|
|
423
|
+
// explicit model="<planModeCodingModel>" instruction. Steering ensures the
|
|
424
|
+
// configured plan-mode coding model reaches the subagent invocation.
|
|
425
|
+
await pi.sendUserMessage(buildExecutionKickoffMessage({ permissionMode, executeWithSubagent }), { deliverAs: "steer" });
|
|
311
426
|
}
|
|
312
427
|
|
|
313
428
|
async function cancelPlan(pi: ExtensionAPI, ctx: any, clearTask = true): Promise<RestorablePermissionMode> {
|
|
@@ -340,35 +455,52 @@ function readPlanArtifact(planPath: string): string | undefined {
|
|
|
340
455
|
}
|
|
341
456
|
}
|
|
342
457
|
|
|
343
|
-
function
|
|
458
|
+
function buildNewSessionOptionLabel(): string {
|
|
459
|
+
const codingModel = readPlanModeCodingModel();
|
|
460
|
+
const codingSubagent = readPlanModeCodingSubagent() ?? DEFAULT_PLAN_CODING_AGENT;
|
|
461
|
+
const modelSuffix = codingModel ? codingModel.split("/")[1] ?? codingModel : null;
|
|
462
|
+
// e.g. "Approve plan — new session (worker · claude-sonnet-4-6)"
|
|
463
|
+
// "Approve plan — new session (worker)"
|
|
464
|
+
const suffix = modelSuffix ? `${codingSubagent} · ${modelSuffix}` : codingSubagent;
|
|
465
|
+
return `${APPROVE_LABEL} — ${APPROVE_NEW_SESSION_LABEL} (${suffix})`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function buildApprovalActionInstructions(): string {
|
|
344
469
|
return [
|
|
345
|
-
"
|
|
346
|
-
`
|
|
347
|
-
|
|
348
|
-
`
|
|
349
|
-
|
|
350
|
-
`3. ${REVISE_LABEL}`,
|
|
351
|
-
`Second question id: \"${PLAN_APPROVAL_PERMISSION_QUESTION_ID}\". Ask which execution mode to use if the plan is approved.`,
|
|
352
|
-
"Use exactly these 4 options for the second question:",
|
|
353
|
-
`1. ${APPROVE_AUTO_LABEL} (Recommended)`,
|
|
354
|
-
`2. ${APPROVE_BYPASS_LABEL}`,
|
|
355
|
-
`3. ${APPROVE_AUTO_SUBAGENT_LABEL}`,
|
|
356
|
-
`4. ${APPROVE_BYPASS_SUBAGENT_LABEL}`,
|
|
357
|
-
`Do not include \"${CANCEL_LABEL}\" as an explicit option. If the user wants to cancel, they should choose \"None of the above\" on the first question and type \"${CANCEL_LABEL}\" in the free-text note.`,
|
|
358
|
-
`If the user selects \"${REVIEW_LABEL}\" or \"${REVISE_LABEL}\", ignore the second answer for now.`,
|
|
359
|
-
"If the dialog is dismissed or the user gives no answer, continue planning.",
|
|
470
|
+
"Ask for plan approval now using ask_user_questions.",
|
|
471
|
+
`One single-select question with id \"${PLAN_APPROVAL_ACTION_QUESTION_ID}\". Ask what to do next with the plan.`,
|
|
472
|
+
`Options: ${APPROVE_LABEL}, ${REVIEW_LABEL}, ${REVISE_LABEL}.`,
|
|
473
|
+
`Do not include \"${CANCEL_LABEL}\" as an explicit option — if the user wants to cancel they should choose \"None of the above\" and type \"${CANCEL_LABEL}\" in the note.`,
|
|
474
|
+
"Do not restate the plan. Just show the question.",
|
|
360
475
|
].join(" ");
|
|
361
476
|
}
|
|
362
477
|
|
|
478
|
+
function buildApprovalModeInstructions(): string {
|
|
479
|
+
const autoSwitchEnabled = readAutoSwitchPlanModelSetting();
|
|
480
|
+
const showNewSessionOption = autoSwitchEnabled;
|
|
481
|
+
const newSessionLabel = buildNewSessionOptionLabel();
|
|
482
|
+
|
|
483
|
+
const options = showNewSessionOption
|
|
484
|
+
? `${APPROVE_AUTO_LABEL}, ${APPROVE_BYPASS_LABEL}, ${APPROVE_AUTO_SUBAGENT_LABEL}, ${APPROVE_BYPASS_SUBAGENT_LABEL}, ${newSessionLabel}`
|
|
485
|
+
: `${APPROVE_AUTO_LABEL}, ${APPROVE_BYPASS_LABEL}, ${APPROVE_AUTO_SUBAGENT_LABEL}, ${APPROVE_BYPASS_SUBAGENT_LABEL}`;
|
|
486
|
+
|
|
487
|
+
return [
|
|
488
|
+
"Plan approved. Now ask which execution mode to use via ask_user_questions.",
|
|
489
|
+
`One single-select question with id \"${PLAN_APPROVAL_PERMISSION_QUESTION_ID}\".`,
|
|
490
|
+
`Options: ${options}.`,
|
|
491
|
+
].join(" ");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Keep for external callers that reference the combined form (headless path)
|
|
495
|
+
function buildApprovalDialogInstructions(): string {
|
|
496
|
+
return buildApprovalActionInstructions();
|
|
497
|
+
}
|
|
498
|
+
|
|
363
499
|
function buildApprovalSteeringMessage(planPath: string): string {
|
|
364
|
-
|
|
500
|
+
return [
|
|
365
501
|
`Plan artifact saved at ${planPath}.`,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
buildApprovalDialogInstructions(),
|
|
369
|
-
];
|
|
370
|
-
|
|
371
|
-
return details.join("\n\n");
|
|
502
|
+
buildApprovalActionInstructions(),
|
|
503
|
+
].join("\n\n");
|
|
372
504
|
}
|
|
373
505
|
|
|
374
506
|
function buildPlanPreviewMessage(planPath: string, planMarkdown?: string): string {
|
|
@@ -414,7 +546,7 @@ function buildReviewSteeringMessage(planPath: string, planMarkdown?: string): st
|
|
|
414
546
|
|
|
415
547
|
details.push(
|
|
416
548
|
"After the subagent responds, summarize its feedback for the user, present the current plan again, and then ask for approval again.",
|
|
417
|
-
|
|
549
|
+
buildApprovalActionInstructions(),
|
|
418
550
|
);
|
|
419
551
|
|
|
420
552
|
return details.join("\n\n");
|
|
@@ -472,6 +604,9 @@ export const __testing = {
|
|
|
472
604
|
buildApprovalSteeringMessage,
|
|
473
605
|
buildPlanPreviewMessage,
|
|
474
606
|
buildReviewSteeringMessage,
|
|
607
|
+
buildAutoSuggestPlanModeSystemPrompt,
|
|
608
|
+
readAutoSuggestPlanModeSetting,
|
|
609
|
+
PLAN_SUGGEST_QUESTION_ID,
|
|
475
610
|
};
|
|
476
611
|
|
|
477
612
|
export default function planCommand(pi: ExtensionAPI) {
|
|
@@ -483,16 +618,28 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
483
618
|
pi.on("session_start", async (_event, ctx) => {
|
|
484
619
|
restoreStateFromSession(ctx);
|
|
485
620
|
startedFromFlag = false;
|
|
621
|
+
reasoningModelSwitchDone = false;
|
|
486
622
|
if (state.active) {
|
|
487
623
|
setPermissionModeAndEnv("plan");
|
|
488
624
|
}
|
|
489
625
|
});
|
|
490
626
|
|
|
491
|
-
pi.on("before_agent_start", async () => {
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
627
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
628
|
+
if (isPlanModeActive()) {
|
|
629
|
+
// Switch to reasoning model once per plan mode activation
|
|
630
|
+
if (!reasoningModelSwitchDone && readAutoSwitchPlanModelSetting()) {
|
|
631
|
+
const reasoningModel = parseQualifiedModelRef(readPlanModeReasoningModel());
|
|
632
|
+
if (reasoningModel) {
|
|
633
|
+
reasoningModelSwitchDone = true;
|
|
634
|
+
await setModelIfNeeded(pi, ctx, reasoningModel);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return { systemPrompt: buildPlanModeSystemPrompt() };
|
|
638
|
+
}
|
|
639
|
+
if (readAutoSuggestPlanModeSetting()) {
|
|
640
|
+
return { systemPrompt: buildAutoSuggestPlanModeSystemPrompt() };
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
496
643
|
});
|
|
497
644
|
|
|
498
645
|
pi.on("input", async (event, ctx) => {
|
|
@@ -503,7 +650,7 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
503
650
|
|
|
504
651
|
startedFromFlag = true;
|
|
505
652
|
ensurePlanDir();
|
|
506
|
-
|
|
653
|
+
await enablePlanModeWithModelSwitch(pi, ctx, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
|
|
507
654
|
task: event.text.trim(),
|
|
508
655
|
approvalStatus: "pending",
|
|
509
656
|
latestPlanPath: undefined,
|
|
@@ -571,6 +718,35 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
571
718
|
return;
|
|
572
719
|
}
|
|
573
720
|
|
|
721
|
+
if (event.toolName === "ask_user_questions" && !isPlanModeActive()) {
|
|
722
|
+
const details = event.details as {
|
|
723
|
+
cancelled?: boolean;
|
|
724
|
+
response?: { answers?: Record<string, AskUserAnswer> };
|
|
725
|
+
} | undefined;
|
|
726
|
+
if (!details?.cancelled && details?.response?.answers) {
|
|
727
|
+
const suggestAnswer = details.response.answers[PLAN_SUGGEST_QUESTION_ID];
|
|
728
|
+
if (suggestAnswer) {
|
|
729
|
+
const selected = Array.isArray(suggestAnswer.selected) ? suggestAnswer.selected[0] : suggestAnswer.selected;
|
|
730
|
+
if (typeof selected === "string" && selected.toLowerCase().includes("yes")) {
|
|
731
|
+
ensurePlanDir();
|
|
732
|
+
await enablePlanModeWithModelSwitch(pi, ctx, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
|
|
733
|
+
task: state.task,
|
|
734
|
+
latestPlanPath: undefined,
|
|
735
|
+
approvalStatus: "pending",
|
|
736
|
+
targetPermissionMode: undefined,
|
|
737
|
+
});
|
|
738
|
+
ctx.ui?.notify?.("Plan mode enabled. Investigate and produce a plan before making changes.", "info");
|
|
739
|
+
pi.sendUserMessage(
|
|
740
|
+
"The user confirmed switching to plan mode. You are now in plan mode. Investigate the task and produce a persisted execution plan under .lsd/plan/ before making any source changes.",
|
|
741
|
+
{ deliverAs: "steer" },
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
574
750
|
if (!isPlanModeActive() || event.toolName !== "ask_user_questions") return;
|
|
575
751
|
|
|
576
752
|
const details = event.details as {
|
|
@@ -584,35 +760,48 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
584
760
|
const actionValues = getAnswerValues(actionAnswer);
|
|
585
761
|
const permissionValues = getAnswerValues(permissionAnswer);
|
|
586
762
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
763
|
+
// ── Second question answered (execution mode) ─────────────────────────
|
|
764
|
+
if (permissionValues.length > 0) {
|
|
765
|
+
if (selectionRequestsCancel(permissionValues)) {
|
|
766
|
+
await cancelPlan(pi, ctx, true);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
593
769
|
|
|
594
|
-
|
|
595
|
-
|
|
770
|
+
if (permissionValues[0]?.includes(APPROVE_NEW_SESSION_LABEL)) {
|
|
771
|
+
scheduleNewSession(pi, ctx);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
596
774
|
|
|
597
|
-
if (actionSelection.includes(APPROVE_LABEL)) {
|
|
598
775
|
const executionMode = approvalSelectionToExecutionMode(permissionValues[0]) ?? {
|
|
599
776
|
permissionMode: DEFAULT_APPROVAL_PERMISSION_MODE,
|
|
600
777
|
executeWithSubagent: false,
|
|
601
778
|
};
|
|
602
|
-
state = {
|
|
603
|
-
...state,
|
|
604
|
-
targetPermissionMode: executionMode.permissionMode,
|
|
605
|
-
};
|
|
606
|
-
|
|
779
|
+
state = { ...state, targetPermissionMode: executionMode.permissionMode };
|
|
607
780
|
if (executionMode.executeWithSubagent) {
|
|
608
781
|
const modeLabel = executionMode.permissionMode === "danger-full-access" ? "bypass" : "auto";
|
|
609
782
|
ctx.ui?.notify?.(`Plan approved: subagent(${modeLabel})`, "info");
|
|
610
783
|
}
|
|
611
|
-
|
|
612
784
|
await approvePlan(pi, ctx, executionMode.permissionMode, executionMode.executeWithSubagent);
|
|
613
785
|
return;
|
|
614
786
|
}
|
|
615
787
|
|
|
788
|
+
// ── First question answered (action) ──────────────────────────────────
|
|
789
|
+
if (actionValues.length === 0) return;
|
|
790
|
+
|
|
791
|
+
if (selectionRequestsCancel(actionValues)) {
|
|
792
|
+
await cancelPlan(pi, ctx, true);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const actionSelection = actionValues[0];
|
|
797
|
+
if (!actionSelection) return;
|
|
798
|
+
|
|
799
|
+
if (actionSelection.includes(APPROVE_LABEL)) {
|
|
800
|
+
// Steer the second question — handle in the next tool_result cycle
|
|
801
|
+
pi.sendUserMessage(buildApprovalModeInstructions(), { deliverAs: "steer" });
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
616
805
|
if (actionSelection.includes(REVIEW_LABEL)) {
|
|
617
806
|
setState(pi, {
|
|
618
807
|
...state,
|
|
@@ -654,16 +843,17 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
654
843
|
|
|
655
844
|
ensurePlanDir();
|
|
656
845
|
const task = args.trim();
|
|
657
|
-
|
|
846
|
+
await enablePlanModeWithModelSwitch(pi, ctx, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, {
|
|
658
847
|
task,
|
|
659
848
|
latestPlanPath: undefined,
|
|
660
849
|
approvalStatus: "pending",
|
|
661
850
|
targetPermissionMode: undefined,
|
|
662
851
|
});
|
|
852
|
+
const reasoningModel = readAutoSwitchPlanModelSetting() ? readPlanModeReasoningModel() : undefined;
|
|
663
853
|
ctx.ui.notify(
|
|
664
854
|
task
|
|
665
|
-
? `Plan mode enabled. Current task: ${task}`
|
|
666
|
-
:
|
|
855
|
+
? `Plan mode enabled${reasoningModel ? ` · ${reasoningModel.split("/")[1] ?? reasoningModel}` : ""}. Current task: ${task}`
|
|
856
|
+
: `Plan mode enabled${reasoningModel ? ` · ${reasoningModel.split("/")[1] ?? reasoningModel}` : ""}. Investigation is allowed; source changes stay blocked until you exit plan mode.`,
|
|
667
857
|
"info",
|
|
668
858
|
);
|
|
669
859
|
},
|
|
@@ -692,4 +882,36 @@ export default function planCommand(pi: ExtensionAPI) {
|
|
|
692
882
|
ctx.ui.notify("Plan mode cancelled.", "info");
|
|
693
883
|
},
|
|
694
884
|
});
|
|
885
|
+
|
|
886
|
+
// Internal command — called by scheduleNewSession() via pi.executeSlashCommand().
|
|
887
|
+
// Runs in ExtensionCommandContext so ctx.newSession() is available.
|
|
888
|
+
pi.registerCommand("plan-execute-new-session", {
|
|
889
|
+
description: "Internal: execute approved plan in a new session with the coding model",
|
|
890
|
+
async handler(_args: string, ctx: ExtensionCommandContext) {
|
|
891
|
+
const payload = pendingNewSession;
|
|
892
|
+
pendingNewSession = null;
|
|
893
|
+
if (!payload) return;
|
|
894
|
+
|
|
895
|
+
// Switch to coding model first
|
|
896
|
+
if (payload.codingModelRef) {
|
|
897
|
+
await setModelIfNeeded(pi, ctx, payload.codingModelRef);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const result = await ctx.newSession();
|
|
901
|
+
if (result.cancelled) return;
|
|
902
|
+
|
|
903
|
+
// Inject plan into the new session as a steer message
|
|
904
|
+
const parts: string[] = [
|
|
905
|
+
`Plan approved. You are acting as the ${payload.codingSubagent} agent. Implement the following plan now without re-investigating or re-planning.`,
|
|
906
|
+
];
|
|
907
|
+
if (payload.task) parts.push(`Original task: ${payload.task}`);
|
|
908
|
+
if (payload.planPath) parts.push(`Plan artifact: ${payload.planPath}`);
|
|
909
|
+
if (payload.planContent) {
|
|
910
|
+
parts.push(`Full plan:\n\`\`\`markdown\n${payload.planContent}\n\`\`\``);
|
|
911
|
+
} else if (payload.planPath) {
|
|
912
|
+
parts.push(`Read the plan from ${payload.planPath} before starting.`);
|
|
913
|
+
}
|
|
914
|
+
pi.sendUserMessage(parts.join("\n\n"), { deliverAs: "steer" });
|
|
915
|
+
},
|
|
916
|
+
});
|
|
695
917
|
}
|