lsd-pi 1.1.10 → 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.
Files changed (49) hide show
  1. package/dist/resources/extensions/slash-commands/index.js +2 -0
  2. package/dist/resources/extensions/slash-commands/init.js +47 -0
  3. package/dist/resources/extensions/slash-commands/plan.js +231 -50
  4. package/dist/resources/extensions/slash-commands/tools.js +14 -27
  5. package/dist/resources/extensions/subagent/index.js +5 -10
  6. package/package.json +1 -1
  7. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  8. package/packages/pi-coding-agent/dist/core/agent-session.js +11 -5
  9. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  10. package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js +59 -7
  11. package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js.map +1 -1
  12. package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -4
  13. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  14. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -1
  15. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  16. package/packages/pi-coding-agent/dist/core/sdk.js +18 -7
  17. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  18. package/packages/pi-coding-agent/dist/core/sdk.test.js +80 -0
  19. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  20. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -5
  21. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  22. package/packages/pi-coding-agent/dist/core/settings-manager.js +23 -9
  23. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  24. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -4
  25. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  26. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +32 -5
  27. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  28. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  29. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -0
  30. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  31. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  32. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +34 -25
  33. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  34. package/packages/pi-coding-agent/package.json +1 -1
  35. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -5
  36. package/packages/pi-coding-agent/src/core/resource-loader-lsd-md.test.ts +67 -7
  37. package/packages/pi-coding-agent/src/core/resource-loader.ts +4 -4
  38. package/packages/pi-coding-agent/src/core/sdk.test.ts +100 -0
  39. package/packages/pi-coding-agent/src/core/sdk.ts +23 -8
  40. package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -15
  41. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +41 -10
  42. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +11 -0
  43. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +43 -27
  44. package/pkg/package.json +1 -1
  45. package/src/resources/extensions/slash-commands/index.ts +2 -0
  46. package/src/resources/extensions/slash-commands/init.ts +55 -0
  47. package/src/resources/extensions/slash-commands/plan.ts +268 -52
  48. package/src/resources/extensions/slash-commands/tools.ts +15 -29
  49. package/src/resources/extensions/subagent/index.ts +5 -10
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -1804,7 +1804,7 @@ export class AgentSession {
1804
1804
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1805
1805
  }
1806
1806
 
1807
- const thinkingLevel = this._getThinkingLevelForModelSwitch();
1807
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(model);
1808
1808
  await this._applyModelChange(model, thinkingLevel, "set", options);
1809
1809
  }
1810
1810
 
@@ -1841,7 +1841,7 @@ export class AgentSession {
1841
1841
 
1842
1842
  // Explicit scoped model thinking level overrides current session level;
1843
1843
  // undefined scoped model thinking level inherits the current session preference.
1844
- const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1844
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);
1845
1845
  await this._applyModelChange(next.model, thinkingLevel, "cycle", options);
1846
1846
 
1847
1847
  return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
@@ -1859,7 +1859,7 @@ export class AgentSession {
1859
1859
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1860
1860
  const nextModel = availableModels[nextIndex];
1861
1861
 
1862
- const thinkingLevel = this._getThinkingLevelForModelSwitch();
1862
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);
1863
1863
  await this._applyModelChange(nextModel, thinkingLevel, "cycle", options);
1864
1864
 
1865
1865
  return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
@@ -1942,11 +1942,19 @@ export class AgentSession {
1942
1942
  return !!this.model?.reasoning;
1943
1943
  }
1944
1944
 
1945
- private _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel): ThinkingLevel {
1945
+ private _getThinkingLevelForModelSwitch(targetModel: Model<any>, explicitLevel?: ThinkingLevel): ThinkingLevel {
1946
1946
  if (explicitLevel !== undefined) {
1947
1947
  return explicitLevel;
1948
1948
  }
1949
- if (!this.supportsThinking()) {
1949
+ if (
1950
+ targetModel.provider === "anthropic" &&
1951
+ targetModel.reasoning === true &&
1952
+ this.settingsManager.getAnthropicAdaptiveByDefault() &&
1953
+ supportsAdaptiveThinking(targetModel.id)
1954
+ ) {
1955
+ return "adaptive";
1956
+ }
1957
+ if (!targetModel.reasoning) {
1950
1958
  return this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
1951
1959
  }
1952
1960
  return this.thinkingLevel;
@@ -7,7 +7,7 @@ import test from "node:test";
7
7
  import { DefaultResourceLoader } from "./resource-loader.js";
8
8
  import { SettingsManager } from "./settings-manager.js";
9
9
 
10
- test("resource loader reads global lsd.md from the app root", async (t) => {
10
+ test("resource loader reads global LSD.md from the app root", async (t) => {
11
11
  const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
12
12
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
13
13
 
@@ -16,7 +16,7 @@ test("resource loader reads global lsd.md from the app root", async (t) => {
16
16
  const cwd = join(tmp, "project");
17
17
  mkdirSync(agentDir, { recursive: true });
18
18
  mkdirSync(cwd, { recursive: true });
19
- writeFileSync(join(appRoot, "lsd.md"), "# global lsd\n", "utf-8");
19
+ writeFileSync(join(appRoot, "LSD.md"), "# global lsd\n", "utf-8");
20
20
 
21
21
  const loader = new DefaultResourceLoader({
22
22
  cwd,
@@ -31,11 +31,11 @@ test("resource loader reads global lsd.md from the app root", async (t) => {
31
31
 
32
32
  const contextFiles = loader.getAgentsFiles().agentsFiles;
33
33
  assert.equal(contextFiles.length, 1);
34
- assert.equal(contextFiles[0]?.path, join(appRoot, "lsd.md"));
34
+ assert.equal(contextFiles[0]?.path, join(appRoot, "LSD.md"));
35
35
  assert.equal(contextFiles[0]?.content, "# global lsd\n");
36
36
  });
37
37
 
38
- test("resource loader reads ancestor .lsd/lsd.md before AGENTS.md in the same directory", async (t) => {
38
+ test("resource loader uses project LSD.md before CLAUDE.md or AGENTS.md in the same directory", async (t) => {
39
39
  const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
40
40
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
41
41
 
@@ -44,9 +44,9 @@ test("resource loader reads ancestor .lsd/lsd.md before AGENTS.md in the same di
44
44
  const projectRoot = join(tmp, "project");
45
45
  const cwd = join(projectRoot, "src", "feature");
46
46
  mkdirSync(agentDir, { recursive: true });
47
- mkdirSync(join(projectRoot, ".lsd"), { recursive: true });
48
47
  mkdirSync(cwd, { recursive: true });
49
- writeFileSync(join(projectRoot, ".lsd", "lsd.md"), "# project lsd\n", "utf-8");
48
+ writeFileSync(join(projectRoot, "LSD.md"), "# project lsd\n", "utf-8");
49
+ writeFileSync(join(projectRoot, "CLAUDE.md"), "# claude fallback\n", "utf-8");
50
50
  writeFileSync(join(projectRoot, "AGENTS.md"), "# agents fallback\n", "utf-8");
51
51
 
52
52
  const loader = new DefaultResourceLoader({
@@ -62,6 +62,66 @@ test("resource loader reads ancestor .lsd/lsd.md before AGENTS.md in the same di
62
62
 
63
63
  const contextFiles = loader.getAgentsFiles().agentsFiles;
64
64
  assert.equal(contextFiles.length, 1);
65
- assert.equal(contextFiles[0]?.path, join(projectRoot, ".lsd", "lsd.md"));
65
+ assert.equal(contextFiles[0]?.path, join(projectRoot, "LSD.md"));
66
66
  assert.equal(contextFiles[0]?.content, "# project lsd\n");
67
67
  });
68
+
69
+ test("resource loader loads both global and project LSD.md files", async (t) => {
70
+ const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
71
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
72
+
73
+ const appRoot = join(tmp, ".lsd");
74
+ const agentDir = join(appRoot, "agent");
75
+ const projectRoot = join(tmp, "project");
76
+ const cwd = join(projectRoot, "src", "feature");
77
+ mkdirSync(agentDir, { recursive: true });
78
+ mkdirSync(cwd, { recursive: true });
79
+ writeFileSync(join(appRoot, "LSD.md"), "# global lsd\n", "utf-8");
80
+ writeFileSync(join(projectRoot, "LSD.md"), "# project lsd\n", "utf-8");
81
+
82
+ const loader = new DefaultResourceLoader({
83
+ cwd,
84
+ agentDir,
85
+ settingsManager: SettingsManager.inMemory(),
86
+ noExtensions: true,
87
+ noSkills: true,
88
+ noPromptTemplates: true,
89
+ noThemes: true,
90
+ });
91
+ await loader.reload();
92
+
93
+ const contextFiles = loader.getAgentsFiles().agentsFiles;
94
+ assert.equal(contextFiles.length, 2);
95
+ assert.equal(contextFiles[0]?.path, join(appRoot, "LSD.md"));
96
+ assert.equal(contextFiles[1]?.path, join(projectRoot, "LSD.md"));
97
+ });
98
+
99
+ test("resource loader falls back to CLAUDE.md when no LSD.md exists", async (t) => {
100
+ const tmp = mkdtempSync(join(tmpdir(), "resource-loader-lsd-md-"));
101
+ t.after(() => rmSync(tmp, { recursive: true, force: true }));
102
+
103
+ const appRoot = join(tmp, ".lsd");
104
+ const agentDir = join(appRoot, "agent");
105
+ const projectRoot = join(tmp, "project");
106
+ const cwd = join(projectRoot, "src", "feature");
107
+ mkdirSync(agentDir, { recursive: true });
108
+ mkdirSync(cwd, { recursive: true });
109
+ writeFileSync(join(projectRoot, "CLAUDE.md"), "# claude fallback\n", "utf-8");
110
+ writeFileSync(join(projectRoot, "AGENTS.md"), "# agents fallback\n", "utf-8");
111
+
112
+ const loader = new DefaultResourceLoader({
113
+ cwd,
114
+ agentDir,
115
+ settingsManager: SettingsManager.inMemory(),
116
+ noExtensions: true,
117
+ noSkills: true,
118
+ noPromptTemplates: true,
119
+ noThemes: true,
120
+ });
121
+ await loader.reload();
122
+
123
+ const contextFiles = loader.getAgentsFiles().agentsFiles;
124
+ assert.equal(contextFiles.length, 1);
125
+ assert.equal(contextFiles[0]?.path, join(projectRoot, "CLAUDE.md"));
126
+ assert.equal(contextFiles[0]?.content, "# claude fallback\n");
127
+ });
@@ -71,14 +71,14 @@ function tryReadContextFile(filePath: string): { path: string; content: string }
71
71
 
72
72
  function loadContextFileFromDir(dir: string): { path: string; content: string } | null {
73
73
  const candidates = [
74
- join(dir, "lsd.md"),
75
- join(dir, ".lsd", "lsd.md"),
76
- join(dir, CONFIG_DIR_NAME, "lsd.md"),
77
74
  join(dir, "LSD.md"),
78
75
  join(dir, ".lsd", "LSD.md"),
79
76
  join(dir, CONFIG_DIR_NAME, "LSD.md"),
80
- join(dir, "AGENTS.md"),
77
+ join(dir, "lsd.md"),
78
+ join(dir, ".lsd", "lsd.md"),
79
+ join(dir, CONFIG_DIR_NAME, "lsd.md"),
81
80
  join(dir, "CLAUDE.md"),
81
+ join(dir, "AGENTS.md"),
82
82
  ];
83
83
  for (const filePath of new Set(candidates)) {
84
84
  const loaded = tryReadContextFile(filePath);
@@ -80,3 +80,103 @@ test("createAgentSession restores last session model even when API key cannot be
80
80
  }
81
81
  }
82
82
  });
83
+
84
+ test("createAgentSession defaults to adaptive for supported Anthropic models when enabled", async () => {
85
+ const tempDir = join(
86
+ process.cwd(),
87
+ ".tmp-tests",
88
+ `sdk-anthropic-adaptive-${Date.now()}-${Math.random().toString(36).slice(2)}`,
89
+ );
90
+ mkdirSync(tempDir, { recursive: true });
91
+ tempDirs.push(tempDir);
92
+
93
+ const authStorage = AuthStorage.inMemory({
94
+ anthropic: { type: "api_key", key: "test-anthropic-key" },
95
+ });
96
+ const modelRegistry = new ModelRegistry(authStorage, join(tempDir, "models.json"));
97
+ const settingsManager = SettingsManager.inMemory();
98
+ settingsManager.setDefaultModelAndProvider("anthropic", "claude-sonnet-4-6");
99
+ settingsManager.setDefaultThinkingLevel("high");
100
+ settingsManager.setAnthropicAdaptiveByDefault(true);
101
+ const sessionManager = SessionManager.inMemory(tempDir);
102
+ const resourceLoader = new DefaultResourceLoader({
103
+ cwd: tempDir,
104
+ agentDir: tempDir,
105
+ settingsManager,
106
+ noExtensions: true,
107
+ noSkills: true,
108
+ noPromptTemplates: true,
109
+ noThemes: true,
110
+ });
111
+
112
+ const { session } = await createAgentSession({
113
+ cwd: tempDir,
114
+ agentDir: tempDir,
115
+ authStorage,
116
+ modelRegistry,
117
+ settingsManager,
118
+ sessionManager,
119
+ resourceLoader,
120
+ });
121
+
122
+ assert.equal(session.model?.provider, "anthropic");
123
+ assert.equal(session.model?.id, "claude-sonnet-4-6");
124
+ assert.equal(session.thinkingLevel, "adaptive");
125
+ assert.equal(settingsManager.getDefaultThinkingLevel(), "high");
126
+ session.dispose();
127
+ });
128
+
129
+ test("AgentSession switches supported Anthropic models to adaptive when the setting is enabled", async () => {
130
+ const tempDir = join(
131
+ process.cwd(),
132
+ ".tmp-tests",
133
+ `sdk-anthropic-switch-${Date.now()}-${Math.random().toString(36).slice(2)}`,
134
+ );
135
+ mkdirSync(tempDir, { recursive: true });
136
+ tempDirs.push(tempDir);
137
+
138
+ const authStorage = AuthStorage.inMemory({
139
+ anthropic: { type: "api_key", key: "test-anthropic-key" },
140
+ openai: { type: "api_key", key: "test-openai-key" },
141
+ });
142
+ const modelRegistry = new ModelRegistry(authStorage, join(tempDir, "models.json"));
143
+ const settingsManager = SettingsManager.inMemory();
144
+ settingsManager.setDefaultModelAndProvider("openai", "gpt-5.4");
145
+ settingsManager.setDefaultThinkingLevel("high");
146
+ settingsManager.setAnthropicAdaptiveByDefault(true);
147
+ const sessionManager = SessionManager.inMemory(tempDir);
148
+ const resourceLoader = new DefaultResourceLoader({
149
+ cwd: tempDir,
150
+ agentDir: tempDir,
151
+ settingsManager,
152
+ noExtensions: true,
153
+ noSkills: true,
154
+ noPromptTemplates: true,
155
+ noThemes: true,
156
+ });
157
+
158
+ const { session } = await createAgentSession({
159
+ cwd: tempDir,
160
+ agentDir: tempDir,
161
+ authStorage,
162
+ modelRegistry,
163
+ settingsManager,
164
+ sessionManager,
165
+ resourceLoader,
166
+ });
167
+
168
+ assert.equal(session.model?.provider, "openai");
169
+ assert.equal(session.thinkingLevel, "high");
170
+
171
+ const anthropicModel = modelRegistry.getAvailable().find((model) =>
172
+ model.provider === "anthropic" && model.id === "claude-sonnet-4-6"
173
+ );
174
+ if (!anthropicModel) throw new Error("expected claude-sonnet-4-6 to be available");
175
+
176
+ await session.setModel(anthropicModel);
177
+
178
+ assert.equal(session.model?.provider, "anthropic");
179
+ assert.equal(session.model?.id, "claude-sonnet-4-6");
180
+ assert.equal(session.thinkingLevel, "adaptive");
181
+ session.dispose();
182
+ });
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { Agent, type AgentMessage, type ThinkingLevel } from "@gsd/pi-agent-core";
3
- import type { Message, Model } from "@gsd/pi-ai";
3
+ import { supportsAdaptiveThinking, type Message, type Model } from "@gsd/pi-ai";
4
4
  import { getAgentDir, getDocsPath } from "../config.js";
5
5
  import { AgentSession } from "./agent-session.js";
6
6
  import { AuthStorage } from "./auth-storage.js";
@@ -140,6 +140,17 @@ function getDefaultAgentDir(): string {
140
140
  return getAgentDir();
141
141
  }
142
142
 
143
+ function shouldPreferAdaptiveThinkingByDefault(
144
+ model: Model<any> | undefined,
145
+ settingsManager: SettingsManager,
146
+ ): boolean {
147
+ return !!model &&
148
+ model.provider === "anthropic" &&
149
+ model.reasoning === true &&
150
+ settingsManager.getAnthropicAdaptiveByDefault() &&
151
+ supportsAdaptiveThinking(model.id);
152
+ }
153
+
143
154
  /**
144
155
  * Create an AgentSession with the specified options.
145
156
  *
@@ -252,6 +263,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
252
263
  thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
253
264
  }
254
265
 
266
+ const preferAdaptiveByDefault = shouldPreferAdaptiveThinkingByDefault(model, settingsManager);
267
+ if (preferAdaptiveByDefault) {
268
+ thinkingLevel = "adaptive";
269
+ }
270
+
255
271
  // Clamp to model capabilities
256
272
  if (!model || !model.reasoning) {
257
273
  thinkingLevel = "off";
@@ -259,13 +275,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
259
275
 
260
276
  const editMode = settingsManager.getEditMode();
261
277
  const toolProfile = settingsManager.getToolProfile();
262
- const defaultActiveToolNames: string[] = toolProfile === "minimal"
263
- ? (editMode === "hashline"
264
- ? ["hashline_read", "bash", "lsp", "tool_search", "tool_enable"]
265
- : ["read", "bash", "lsp", "tool_search", "tool_enable"])
266
- : editMode === "hashline"
267
- ? ["hashline_read", "bash", "hashline_edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent"]
268
- : ["read", "bash", "edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent"];
278
+ const balancedToolNames = editMode === "hashline"
279
+ ? ["hashline_read", "bash", "hashline_edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent", "ask_user_questions"]
280
+ : ["read", "bash", "edit", "write", "lsp", "bg_shell", "tool_search", "tool_enable", "Skill", "subagent", "await_subagent", "ask_user_questions"];
281
+ const defaultActiveToolNames: string[] = toolProfile === "full"
282
+ ? Object.keys(allTools)
283
+ : balancedToolNames;
269
284
  const initialActiveToolNames: string[] = options.tools
270
285
  ? options.tools.map((t) => t.name).filter((n): n is string => typeof n === "string" && n in allTools)
271
286
  : defaultActiveToolNames;
@@ -127,9 +127,12 @@ export interface Settings {
127
127
  planModeReasoningModel?: string;
128
128
  planModeReviewModel?: string;
129
129
  planModeCodingModel?: string;
130
+ autoSuggestPlanMode?: boolean; // default: false — append a system-prompt instruction so the LLM proposes plan mode for large tasks
131
+ autoSwitchPlanModel?: boolean; // default: false — enable opusplan-style model switching (auto-switch to reasoning model on entry, new-session option on approval)
130
132
  permissionMode?: "danger-full-access" | "accept-on-edit" | "auto" | "plan";
131
133
  classifierModel?: string;
132
134
  defaultThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
135
+ anthropicAdaptiveByDefault?: boolean; // default: false — prefer adaptive thinking when using supported Anthropic models
133
136
  transport?: TransportSetting; // default: "sse"
134
137
  steeringMode?: "all" | "one-at-a-time";
135
138
  followUpMode?: "all" | "one-at-a-time";
@@ -149,8 +152,8 @@ export interface Settings {
149
152
  prompts?: string[]; // Array of local prompt template paths or directories
150
153
  themes?: string[]; // Array of local theme file paths or directories
151
154
  enableSkillCommands?: boolean; // default: true - register skills as /skill:name commands
152
- toolSearch?: boolean; // legacy boolean toggle for lazy tool-search mode; superseded by toolProfile
153
- toolProfile?: "minimal" | "balanced"; // default: "balanced"
155
+ toolSearch?: boolean; // legacy boolean toggle from deprecated minimal profile; retained for migration only
156
+ toolProfile?: "balanced" | "full"; // default: "balanced"
154
157
  terminal?: TerminalSettings;
155
158
  images?: ImageSettings;
156
159
  enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
@@ -806,6 +809,22 @@ export class SettingsManager {
806
809
  this.setGlobalSetting("planModeCodingModel", modelRef.trim());
807
810
  }
808
811
 
812
+ getAutoSuggestPlanMode(): boolean {
813
+ return this.settings.autoSuggestPlanMode ?? false;
814
+ }
815
+
816
+ setAutoSuggestPlanMode(enabled: boolean): void {
817
+ this.setGlobalSetting("autoSuggestPlanMode", enabled);
818
+ }
819
+
820
+ getAutoSwitchPlanModel(): boolean {
821
+ return this.settings.autoSwitchPlanModel ?? false;
822
+ }
823
+
824
+ setAutoSwitchPlanModel(enabled: boolean): void {
825
+ this.setGlobalSetting("autoSwitchPlanModel", enabled);
826
+ }
827
+
809
828
  setPermissionMode(mode: "danger-full-access" | "accept-on-edit" | "auto" | "plan"): void {
810
829
  this.setGlobalSetting("permissionMode", mode);
811
830
  }
@@ -882,6 +901,14 @@ export class SettingsManager {
882
901
  this.setGlobalSetting("defaultThinkingLevel", level);
883
902
  }
884
903
 
904
+ getAnthropicAdaptiveByDefault(): boolean {
905
+ return this.settings.anthropicAdaptiveByDefault ?? false;
906
+ }
907
+
908
+ setAnthropicAdaptiveByDefault(enabled: boolean): void {
909
+ this.setGlobalSetting("anthropicAdaptiveByDefault", enabled);
910
+ }
911
+
885
912
  getTransport(): TransportSetting {
886
913
  return this.settings.transport ?? "sse";
887
914
  }
@@ -1067,24 +1094,18 @@ export class SettingsManager {
1067
1094
  this.setGlobalSetting("enableSkillCommands", enabled);
1068
1095
  }
1069
1096
 
1070
- getToolProfile(): "minimal" | "balanced" {
1097
+ getToolProfile(): "balanced" | "full" {
1071
1098
  const profile = this.settings.toolProfile;
1072
- if (profile === "minimal" || profile === "balanced") return profile;
1073
- if (this.settings.toolSearch !== undefined) return this.settings.toolSearch ? "minimal" : "balanced";
1099
+ if (profile === "balanced" || profile === "full") return profile;
1100
+ // Migrate legacy minimal/toolSearch settings to balanced.
1101
+ if (this.settings.toolSearch !== undefined) return "balanced";
1074
1102
  return "balanced";
1075
1103
  }
1076
1104
 
1077
- setToolProfile(profile: "minimal" | "balanced"): void {
1105
+ setToolProfile(profile: "balanced" | "full"): void {
1078
1106
  this.setGlobalSetting("toolProfile", profile);
1079
- this.setGlobalSetting("toolSearch", profile === "minimal");
1080
- }
1081
-
1082
- getToolSearch(): boolean {
1083
- return this.getToolProfile() === "minimal";
1084
- }
1085
-
1086
- setToolSearch(enabled: boolean): void {
1087
- this.setToolProfile(enabled ? "minimal" : "balanced");
1107
+ // Keep legacy field in sync with deprecated minimal mode removal.
1108
+ this.setGlobalSetting("toolSearch", false);
1088
1109
  }
1089
1110
 
1090
1111
  getThinkingBudgets(): ThinkingBudgetsSettings | undefined {
@@ -33,12 +33,13 @@ export interface SettingsConfig {
33
33
  planModeReasoningModel: string;
34
34
  planModeReviewModel: string;
35
35
  planModeCodingModel: string;
36
+ autoSuggestPlanMode: boolean;
37
+ autoSwitchPlanModel: boolean;
36
38
  showImages: boolean;
37
39
  autoResizeImages: boolean;
38
40
  blockImages: boolean;
39
41
  enableSkillCommands: boolean;
40
- toolSearch: boolean;
41
- toolProfile: "minimal" | "balanced";
42
+ toolProfile: "balanced" | "full";
42
43
  codexRotate: boolean;
43
44
  cacheTimer: boolean;
44
45
  pinLastPrompt: boolean;
@@ -46,6 +47,7 @@ export interface SettingsConfig {
46
47
  followUpMode: "all" | "one-at-a-time";
47
48
  transport: Transport;
48
49
  thinkingLevel: ThinkingLevel;
50
+ anthropicAdaptiveByDefault: boolean;
49
51
  availableThinkingLevels: ThinkingLevel[];
50
52
  currentTheme: string;
51
53
  availableThemes: string[];
@@ -87,6 +89,8 @@ export interface SettingsCallbacks {
87
89
  onPlanModeReasoningModelChange: (modelRef: string) => void;
88
90
  onPlanModeReviewModelChange: (modelRef: string) => void;
89
91
  onPlanModeCodingModelChange: (modelRef: string) => void;
92
+ onAutoSuggestPlanModeChange: (enabled: boolean) => void;
93
+ onAutoSwitchPlanModelChange: (enabled: boolean) => void;
90
94
  onAutoDreamChange: (enabled: boolean) => void;
91
95
  onAutoMemoryChange: (enabled: boolean) => void;
92
96
  onTelegramLiveRelayAutoConnectChange: (enabled: boolean) => void;
@@ -96,8 +100,7 @@ export interface SettingsCallbacks {
96
100
  onAutoResizeImagesChange: (enabled: boolean) => void;
97
101
  onBlockImagesChange: (blocked: boolean) => void;
98
102
  onEnableSkillCommandsChange: (enabled: boolean) => void;
99
- onToolSearchChange: (enabled: boolean) => void;
100
- onToolProfileChange: (profile: "minimal" | "balanced") => void;
103
+ onToolProfileChange: (profile: "balanced" | "full") => void;
101
104
  onCodexRotateChange: (enabled: boolean) => void;
102
105
  onCacheTimerChange: (enabled: boolean) => void;
103
106
  onPinLastPromptChange: (enabled: boolean) => void;
@@ -105,6 +108,7 @@ export interface SettingsCallbacks {
105
108
  onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
106
109
  onTransportChange: (transport: Transport) => void;
107
110
  onThinkingLevelChange: (level: ThinkingLevel) => void;
111
+ onAnthropicAdaptiveByDefaultChange: (enabled: boolean) => void;
108
112
  onThemeChange: (theme: string) => void;
109
113
  onThemePreview?: (theme: string) => void;
110
114
  onThemeAccentChange: (accent: string) => void;
@@ -255,6 +259,20 @@ export class SettingsSelectorComponent extends Container {
255
259
  currentValue: config.planModeCodingModel,
256
260
  submenu: config.planModeCodingModelSubmenu,
257
261
  },
262
+ {
263
+ id: "auto-suggest-plan-mode",
264
+ label: "Auto-suggest plan mode",
265
+ description: "Ask the LLM to suggest plan mode for large/complex tasks",
266
+ currentValue: config.autoSuggestPlanMode ? "true" : "false",
267
+ values: ["true", "false"],
268
+ },
269
+ {
270
+ id: "auto-switch-plan-model",
271
+ label: "OpusPlan mode",
272
+ description: "On plan entry: auto-switch to Plan reasoning model (must be set). On approval: offer new session with Plan coding model + worker agent",
273
+ currentValue: config.autoSwitchPlanModel ? "true" : "false",
274
+ values: ["true", "false"],
275
+ },
258
276
  {
259
277
  id: "steering-mode",
260
278
  label: "Steering mode",
@@ -356,6 +374,13 @@ export class SettingsSelectorComponent extends Container {
356
374
  () => done(),
357
375
  ),
358
376
  },
377
+ {
378
+ id: "anthropic-adaptive-default",
379
+ label: "Anthropic adaptive",
380
+ description: "Default to adaptive thinking for supported Anthropic models",
381
+ currentValue: config.anthropicAdaptiveByDefault ? "true" : "false",
382
+ values: ["true", "false"],
383
+ },
359
384
  {
360
385
  id: "theme",
361
386
  label: "Theme",
@@ -455,9 +480,9 @@ export class SettingsSelectorComponent extends Container {
455
480
  items.splice(skillCommandsIndex + 1, 0, {
456
481
  id: "tool-profile",
457
482
  label: "Tool profile",
458
- description: "Choose the default active tool set: balanced for day-to-day coding, minimal for lazy tool-search mode",
483
+ description: "Choose the default active tool set: balanced for day-to-day coding, full to enable all available tools",
459
484
  currentValue: config.toolProfile,
460
- values: ["balanced", "minimal"],
485
+ values: ["balanced", "full"],
461
486
  });
462
487
 
463
488
  // Codex rotate toggle (insert after tool-profile)
@@ -633,6 +658,12 @@ export class SettingsSelectorComponent extends Container {
633
658
  case "plan-mode-coding-model":
634
659
  callbacks.onPlanModeCodingModelChange(newValue);
635
660
  break;
661
+ case "auto-suggest-plan-mode":
662
+ callbacks.onAutoSuggestPlanModeChange(newValue === "true");
663
+ break;
664
+ case "auto-switch-plan-model":
665
+ callbacks.onAutoSwitchPlanModelChange(newValue === "true");
666
+ break;
636
667
  case "show-images":
637
668
  callbacks.onShowImagesChange(newValue === "true");
638
669
  break;
@@ -645,11 +676,8 @@ export class SettingsSelectorComponent extends Container {
645
676
  case "skill-commands":
646
677
  callbacks.onEnableSkillCommandsChange(newValue === "true");
647
678
  break;
648
- case "tool-search":
649
- callbacks.onToolSearchChange(newValue === "true");
650
- break;
651
679
  case "tool-profile":
652
- callbacks.onToolProfileChange(newValue as "minimal" | "balanced");
680
+ callbacks.onToolProfileChange(newValue as "balanced" | "full");
653
681
  break;
654
682
  case "codex-rotate":
655
683
  callbacks.onCodexRotateChange(newValue === "true");
@@ -681,6 +709,9 @@ export class SettingsSelectorComponent extends Container {
681
709
  case "transport":
682
710
  callbacks.onTransportChange(newValue as Transport);
683
711
  break;
712
+ case "anthropic-adaptive-default":
713
+ callbacks.onAnthropicAdaptiveByDefaultChange(newValue === "true");
714
+ break;
684
715
  case "hide-thinking":
685
716
  callbacks.onHideThinkingBlockChange(newValue === "true");
686
717
  break;
@@ -508,6 +508,17 @@ export class ToolExecutionComponent extends Container {
508
508
  if (resultComponent !== undefined) {
509
509
  this.contentBox.addChild(resultComponent);
510
510
  customRendererHasContent = true;
511
+ // bg_shell: show /bg hint after start/list/digest actions
512
+ if (this.toolName === 'bg_shell' && this.result && !this.isPartial) {
513
+ const details = this.result.details as Record<string, unknown> | undefined;
514
+ const action = details?.action as string | undefined;
515
+ if (action === 'start' || action === 'list' || action === 'digest') {
516
+ this.contentBox.addChild(new Text(
517
+ '\n' + theme.fg('muted', 'Tip: run /bg to manage background processes'),
518
+ 0, 0,
519
+ ));
520
+ }
521
+ }
511
522
  }
512
523
  }
513
524
  } catch {