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.
Files changed (144) hide show
  1. package/dist/resources/extensions/slash-commands/context.js +15 -8
  2. package/dist/resources/extensions/slash-commands/index.js +2 -0
  3. package/dist/resources/extensions/slash-commands/init.js +47 -0
  4. package/dist/resources/extensions/slash-commands/plan.js +241 -54
  5. package/dist/resources/extensions/slash-commands/tools.js +47 -21
  6. package/dist/resources/extensions/subagent/index.js +5 -10
  7. package/dist/startup-model-validation.d.ts +1 -1
  8. package/package.json +1 -1
  9. package/packages/pi-agent-core/dist/types.d.ts +2 -1
  10. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  11. package/packages/pi-agent-core/dist/types.js.map +1 -1
  12. package/packages/pi-agent-core/src/types.ts +2 -1
  13. package/packages/pi-ai/dist/providers/amazon-bedrock.js +10 -3
  14. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  15. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +1 -1
  16. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  17. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -1
  18. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  19. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -2
  20. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  21. package/packages/pi-ai/dist/providers/anthropic.js +2 -2
  22. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  23. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  24. package/packages/pi-ai/dist/providers/azure-openai-responses.js +2 -1
  25. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  26. package/packages/pi-ai/dist/providers/google-gemini-cli.js +2 -0
  27. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  28. package/packages/pi-ai/dist/providers/google-vertex.js +2 -0
  29. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  30. package/packages/pi-ai/dist/providers/google.js +2 -0
  31. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.js +2 -1
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  36. package/packages/pi-ai/dist/providers/openai-completions.js +2 -1
  37. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  38. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/providers/openai-responses.js +2 -1
  40. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  41. package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
  42. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  43. package/packages/pi-ai/dist/providers/simple-options.js +6 -2
  44. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  45. package/packages/pi-ai/dist/types.d.ts +1 -1
  46. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  47. package/packages/pi-ai/dist/types.js.map +1 -1
  48. package/packages/pi-ai/src/providers/amazon-bedrock.ts +11 -4
  49. package/packages/pi-ai/src/providers/anthropic-shared.ts +5 -2
  50. package/packages/pi-ai/src/providers/anthropic.ts +2 -2
  51. package/packages/pi-ai/src/providers/azure-openai-responses.ts +2 -1
  52. package/packages/pi-ai/src/providers/google-gemini-cli.ts +3 -1
  53. package/packages/pi-ai/src/providers/google-vertex.ts +3 -1
  54. package/packages/pi-ai/src/providers/google.ts +3 -1
  55. package/packages/pi-ai/src/providers/openai-codex-responses.ts +2 -1
  56. package/packages/pi-ai/src/providers/openai-completions.ts +2 -1
  57. package/packages/pi-ai/src/providers/openai-responses.ts +2 -1
  58. package/packages/pi-ai/src/providers/simple-options.ts +5 -3
  59. package/packages/pi-ai/src/types.ts +1 -1
  60. package/packages/pi-coding-agent/dist/cli/args.js +1 -1
  61. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +10 -0
  63. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/agent-session.js +57 -20
  65. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/classifier-service.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/classifier-service.js +34 -61
  68. package/packages/pi-coding-agent/dist/core/classifier-service.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +1 -1
  70. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/compaction/utils.js +3 -1
  72. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js +59 -7
  74. package/packages/pi-coding-agent/dist/core/resource-loader-lsd-md.test.js.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -4
  76. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -1
  78. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/sdk.js +19 -20
  80. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/sdk.test.js +80 -0
  82. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +15 -5
  84. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/settings-manager.js +31 -5
  86. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -1
  88. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/system-prompt.js +28 -68
  90. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js +1 -1
  92. package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js +5 -0
  94. package/packages/pi-coding-agent/dist/core/tools/bash-interceptor.test.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +28 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +44 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +36 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +41 -5
  110. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +2 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  115. package/packages/pi-coding-agent/package.json +1 -1
  116. package/packages/pi-coding-agent/src/cli/args.ts +1 -1
  117. package/packages/pi-coding-agent/src/core/agent-session.ts +62 -19
  118. package/packages/pi-coding-agent/src/core/classifier-service.ts +35 -63
  119. package/packages/pi-coding-agent/src/core/compaction/utils.ts +3 -1
  120. package/packages/pi-coding-agent/src/core/resource-loader-lsd-md.test.ts +67 -7
  121. package/packages/pi-coding-agent/src/core/resource-loader.ts +4 -4
  122. package/packages/pi-coding-agent/src/core/sdk.test.ts +100 -0
  123. package/packages/pi-coding-agent/src/core/sdk.ts +24 -21
  124. package/packages/pi-coding-agent/src/core/settings-manager.ts +42 -8
  125. package/packages/pi-coding-agent/src/core/system-prompt.ts +39 -82
  126. package/packages/pi-coding-agent/src/core/tools/bash-interceptor.test.ts +6 -0
  127. package/packages/pi-coding-agent/src/core/tools/bash-interceptor.ts +1 -1
  128. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +26 -0
  129. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +53 -1
  130. package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -0
  131. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +41 -0
  132. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -7
  133. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +3 -1
  134. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  135. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  136. package/pkg/dist/modes/interactive/theme/theme.js +2 -0
  137. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  138. package/pkg/package.json +1 -1
  139. package/src/resources/extensions/slash-commands/context.ts +15 -8
  140. package/src/resources/extensions/slash-commands/index.ts +2 -0
  141. package/src/resources/extensions/slash-commands/init.ts +55 -0
  142. package/src/resources/extensions/slash-commands/plan.ts +277 -55
  143. package/src/resources/extensions/slash-commands/tools.ts +47 -21
  144. package/src/resources/extensions/subagent/index.ts +5 -10
@@ -25,7 +25,7 @@ import type {
25
25
  ThinkingLevel,
26
26
  } from "@gsd/pi-agent-core";
27
27
  import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@gsd/pi-ai";
28
- import { modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
28
+ import { modelsAreEqual, resetApiProviders, supportsAdaptiveThinking, supportsXhigh } from "@gsd/pi-ai";
29
29
  import { Type } from "@sinclair/typebox";
30
30
  import { getDocsPath } from "../config.js";
31
31
  import { getErrorMessage } from "../utils/error.js";
@@ -237,6 +237,12 @@ const THINKING_LEVELS: ThinkingLevel[] = ["off", "minimal", "low", "medium", "hi
237
237
  /** Thinking levels including xhigh (for supported models) */
238
238
  const THINKING_LEVELS_WITH_XHIGH: ThinkingLevel[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
239
239
 
240
+ /** Thinking levels for models supporting adaptive thinking (Claude Opus/Sonnet 4.6+) */
241
+ const THINKING_LEVELS_WITH_ADAPTIVE: ThinkingLevel[] = ["off", "adaptive", "minimal", "low", "medium", "high"];
242
+
243
+ /** Thinking levels for models supporting both adaptive and xhigh */
244
+ const THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH: ThinkingLevel[] = ["off", "adaptive", "minimal", "low", "medium", "high", "xhigh"];
245
+
240
246
  // ============================================================================
241
247
  // AgentSession Class
242
248
  // ============================================================================
@@ -257,6 +263,8 @@ export class AgentSession {
257
263
  private _steeringMessages: string[] = [];
258
264
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
259
265
  private _followUpMessages: string[] = [];
266
+ /** Tracks texts of extension-injected steer messages that should be hidden from UI. */
267
+ private _hiddenSteeringTexts = new Set<string>();
260
268
  /** Messages queued to be included with the next user prompt as context ("asides"). */
261
269
  private _pendingNextTurnMessages: CustomMessage[] = [];
262
270
 
@@ -367,10 +375,16 @@ export class AgentSession {
367
375
 
368
376
  this._buildRuntime({
369
377
  activeToolNames: this._initialActiveToolNames,
370
- includeAllExtensionTools: true,
378
+ includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
371
379
  });
372
380
  }
373
381
 
382
+ private _shouldIncludeAllExtensionTools(): boolean {
383
+ // Tool profiles now provide curated active sets. Do not auto-activate every
384
+ // extension tool at startup, or the saved profile gets effectively ignored.
385
+ return false;
386
+ }
387
+
374
388
  private async _waitForAutomatedFollowUps(): Promise<void> {
375
389
  while (this._retryHandler.isRetrying || this._compactionOrchestrator.hasPendingAutoCompaction) {
376
390
  await this._retryHandler.waitForRetry();
@@ -1118,10 +1132,11 @@ export class AgentSession {
1118
1132
  "Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
1119
1133
  );
1120
1134
  }
1135
+ const isExtensionSteer = options.streamingBehavior === "steer" && options.source === "extension";
1121
1136
  if (options.streamingBehavior === "followUp") {
1122
1137
  await this._queueFollowUp(expandedText, currentImages);
1123
1138
  } else {
1124
- await this._queueSteer(expandedText, currentImages);
1139
+ await this._queueSteer(expandedText, currentImages, isExtensionSteer);
1125
1140
  }
1126
1141
  return;
1127
1142
  }
@@ -1181,6 +1196,10 @@ export class AgentSession {
1181
1196
  if (currentImages) {
1182
1197
  userContent.push(...currentImages);
1183
1198
  }
1199
+ // Track extension-injected messages so the UI can hide them
1200
+ if (options?.source === "extension") {
1201
+ this._hiddenSteeringTexts.add(expandedText);
1202
+ }
1184
1203
  messages.push({
1185
1204
  role: "user",
1186
1205
  content: userContent,
@@ -1422,9 +1441,13 @@ export class AgentSession {
1422
1441
 
1423
1442
  /**
1424
1443
  * Internal: Queue a steering message (already expanded, no extension command check).
1444
+ * @param hidden If true, the message will be hidden from the UI (used for extension-injected steers).
1425
1445
  */
1426
- private async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {
1446
+ private async _queueSteer(text: string, images?: ImageContent[], hidden?: boolean): Promise<void> {
1427
1447
  this._steeringMessages.push(text);
1448
+ if (hidden) {
1449
+ this._hiddenSteeringTexts.add(text);
1450
+ }
1428
1451
  const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
1429
1452
  if (images) {
1430
1453
  content.push(...images);
@@ -1617,6 +1640,11 @@ export class AgentSession {
1617
1640
  return this._steeringMessages.length + this._followUpMessages.length;
1618
1641
  }
1619
1642
 
1643
+ /** Returns true if the given message text was injected by an extension and should be hidden from the UI. */
1644
+ isHiddenSteeringMessage(text: string): boolean {
1645
+ return this._hiddenSteeringTexts.has(text);
1646
+ }
1647
+
1620
1648
  /** Get pending steering messages (read-only) */
1621
1649
  getSteeringMessages(): readonly string[] {
1622
1650
  return this._steeringMessages;
@@ -1687,6 +1715,7 @@ export class AgentSession {
1687
1715
  this._steeringMessages = [];
1688
1716
  this._followUpMessages = [];
1689
1717
  this._pendingNextTurnMessages = [];
1718
+ this._hiddenSteeringTexts = new Set();
1690
1719
 
1691
1720
  this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1692
1721
 
@@ -1697,7 +1726,7 @@ export class AgentSession {
1697
1726
  if (this._cwd !== previousCwd) {
1698
1727
  this._buildRuntime({
1699
1728
  activeToolNames: this.getActiveToolNames(),
1700
- includeAllExtensionTools: true,
1729
+ includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
1701
1730
  });
1702
1731
  }
1703
1732
 
@@ -1775,7 +1804,7 @@ export class AgentSession {
1775
1804
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1776
1805
  }
1777
1806
 
1778
- const thinkingLevel = this._getThinkingLevelForModelSwitch();
1807
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(model);
1779
1808
  await this._applyModelChange(model, thinkingLevel, "set", options);
1780
1809
  }
1781
1810
 
@@ -1812,7 +1841,7 @@ export class AgentSession {
1812
1841
 
1813
1842
  // Explicit scoped model thinking level overrides current session level;
1814
1843
  // undefined scoped model thinking level inherits the current session preference.
1815
- const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1844
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);
1816
1845
  await this._applyModelChange(next.model, thinkingLevel, "cycle", options);
1817
1846
 
1818
1847
  return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
@@ -1830,7 +1859,7 @@ export class AgentSession {
1830
1859
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1831
1860
  const nextModel = availableModels[nextIndex];
1832
1861
 
1833
- const thinkingLevel = this._getThinkingLevelForModelSwitch();
1862
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);
1834
1863
  await this._applyModelChange(nextModel, thinkingLevel, "cycle", options);
1835
1864
 
1836
1865
  return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
@@ -1885,7 +1914,18 @@ export class AgentSession {
1885
1914
  */
1886
1915
  getAvailableThinkingLevels(): ThinkingLevel[] {
1887
1916
  if (!this.supportsThinking()) return ["off"];
1888
- return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
1917
+ const adaptive = this.supportsAdaptiveThinking();
1918
+ const xhigh = this.supportsXhighThinking();
1919
+ if (adaptive && xhigh) return THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH;
1920
+ if (adaptive) return THINKING_LEVELS_WITH_ADAPTIVE;
1921
+ return xhigh ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
1922
+ }
1923
+
1924
+ /**
1925
+ * Check if current model supports adaptive thinking (Claude Opus/Sonnet 4.6+).
1926
+ */
1927
+ supportsAdaptiveThinking(): boolean {
1928
+ return this.model ? supportsAdaptiveThinking(this.model.id) : false;
1889
1929
  }
1890
1930
 
1891
1931
  /**
@@ -1902,18 +1942,26 @@ export class AgentSession {
1902
1942
  return !!this.model?.reasoning;
1903
1943
  }
1904
1944
 
1905
- private _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel): ThinkingLevel {
1945
+ private _getThinkingLevelForModelSwitch(targetModel: Model<any>, explicitLevel?: ThinkingLevel): ThinkingLevel {
1906
1946
  if (explicitLevel !== undefined) {
1907
1947
  return explicitLevel;
1908
1948
  }
1909
- 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) {
1910
1958
  return this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
1911
1959
  }
1912
1960
  return this.thinkingLevel;
1913
1961
  }
1914
1962
 
1915
1963
  private _clampThinkingLevel(level: ThinkingLevel, availableLevels: ThinkingLevel[]): ThinkingLevel {
1916
- const ordered = THINKING_LEVELS_WITH_XHIGH;
1964
+ const ordered = THINKING_LEVELS_WITH_ADAPTIVE_AND_XHIGH;
1917
1965
  const available = new Set(availableLevels);
1918
1966
  const requestedIndex = ordered.indexOf(level);
1919
1967
  if (requestedIndex === -1) {
@@ -2294,12 +2342,6 @@ export class AgentSession {
2294
2342
  for (const tool of wrappedExtensionTools) {
2295
2343
  nextActiveToolNames.push(tool.name);
2296
2344
  }
2297
- } else if (!options?.activeToolNames) {
2298
- for (const toolName of this._toolRegistry.keys()) {
2299
- if (!previousRegistryNames.has(toolName)) {
2300
- nextActiveToolNames.push(toolName);
2301
- }
2302
- }
2303
2345
  }
2304
2346
 
2305
2347
  this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
@@ -2375,7 +2417,7 @@ export class AgentSession {
2375
2417
  this._buildRuntime({
2376
2418
  activeToolNames: this.getActiveToolNames(),
2377
2419
  flagValues: previousFlagValues,
2378
- includeAllExtensionTools: true,
2420
+ includeAllExtensionTools: this._shouldIncludeAllExtensionTools(),
2379
2421
  });
2380
2422
 
2381
2423
  const hasBindings =
@@ -2585,6 +2627,7 @@ export class AgentSession {
2585
2627
  this._steeringMessages = [];
2586
2628
  this._followUpMessages = [];
2587
2629
  this._pendingNextTurnMessages = [];
2630
+ this._hiddenSteeringTexts = new Set();
2588
2631
 
2589
2632
  // Set new session
2590
2633
  this.sessionManager.setSessionFile(sessionPath);
@@ -110,6 +110,39 @@ const BUILT_IN_BASH_ALLOW_PATTERNS = [
110
110
  "more *",
111
111
  ] as const;
112
112
 
113
+ const CLASSIFIER_SYSTEM_PROMPT = `You are a security classifier for an autonomous coding agent. Your job is to decide ALLOW or DENY for a pending tool call.
114
+
115
+ DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
116
+
117
+ ALWAYS ALLOW:
118
+ - Read-only commands: find, grep, ls, cat, head, tail, wc, stat, file, du, df, echo, pwd, env, which, sort, awk, sed -n
119
+ - Git read operations: status, log, diff, show, branch, blame, remote
120
+ - Build/test/lint: npm run *, npx *, tsc, eslint, jest, vitest, cargo, make
121
+ - Writing or editing files inside the project directory
122
+ - Installing packages: npm install, pip install, cargo add
123
+ - Operations clearly implied by the user's recent messages
124
+
125
+ DENY only these specific violations:
126
+ - Deleting files with rm, unlink, rmdir (unless user explicitly asked to delete/remove)
127
+ - Commands targeting system paths outside the project (/etc, /usr, /bin, /System, ~/.ssh)
128
+ - sudo, su, chmod, chown on system paths
129
+ - curl/wget piped directly to sh/bash/zsh
130
+ - curl/wget sending local files or env vars to external hosts (e.g. -d @~/.ssh/id_rsa, -d "$(env)")
131
+ - ssh, scp, sftp — remote shell/file access is out of scope
132
+ - nc/ncat in listen/server mode (-l flag)
133
+ - kill -9, killall
134
+ - crontab modifications
135
+ - dd, mkfs — disk/filesystem writes
136
+ - sed -i — in-place editing that can silently corrupt files
137
+ - npm publish, pip publish, twine upload
138
+ - git push --force / git push -f
139
+ - Accessing credential files unrelated to the task (~/.aws, ~/.ssh/id_rsa, /etc/passwd)
140
+ - Commands that clearly contradict the user's stated intent
141
+
142
+ Use the user's recent messages to judge intent. If the user asked for something and the tool call implements it, ALLOW.
143
+
144
+ Output exactly one word: ALLOW or DENY.`;
145
+
113
146
  export interface ClassifierContext {
114
147
  userMessages: string[];
115
148
  projectInstructions?: string;
@@ -259,46 +292,13 @@ export class ClassifierService {
259
292
  argsJson: string,
260
293
  context: ClassifierContext,
261
294
  ): Promise<boolean> {
262
- const systemPrompt = `You are a security classifier for an autonomous coding agent. Your job is to decide ALLOW or DENY for a pending tool call.
263
-
264
- DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
265
-
266
- ALWAYS ALLOW:
267
- - Any read-only operation: find, grep, ls, cat, head, tail, wc, stat, file, du, df, echo, pwd, env, which, sort, awk, sed -n
268
- - Any git read operation: git status, git log, git diff, git show, git branch, git blame, git remote
269
- - Build/test/lint: npm run *, npx *, tsc, eslint, jest, vitest, cargo, make
270
- - Writing or editing files inside the project directory
271
- - Installing packages: npm install, pip install, cargo add
272
- - Operations clearly implied by the user's recent messages
273
-
274
- DENY only these specific violations:
275
- - Deleting files with rm, unlink, rmdir (unless user explicitly said "delete" or "remove" that file)
276
- - Commands outside the project directory targeting system paths (/etc, /usr, /bin, /System, ~/.ssh)
277
- - sudo, su, chmod/chown on system paths
278
- - curl/wget piped directly to sh/bash (arbitrary code execution)
279
- - curl/wget sending local files or env vars to external hosts (e.g. -d @~/.ssh/id_rsa, -d "$(env)")
280
- - ssh, scp, sftp — remote shell/file access is out of scope for a coding agent
281
- - nc/ncat in listen/server mode (-l flag) — reverse shell risk
282
- - kill -9, killall — terminating arbitrary processes
283
- - crontab — modifying scheduled jobs
284
- - dd, mkfs — disk/filesystem writes
285
- - sed -i — in-place file editing that can silently corrupt files; agent should use proper edit tools
286
- - npm publish, pip publish, twine upload — publishing packages requires explicit user action
287
- - git push --force / git push -f — destructive remote history rewrite
288
- - Accessing credential files unrelated to the current task (~/.aws, ~/.ssh/id_rsa, /etc/passwd)
289
- - Commands that clearly contradict the user's stated intent
290
-
291
- Use the user's recent messages to judge intent. If the user asked for something and the tool call implements it, ALLOW.
292
-
293
- Output exactly one word: ALLOW or DENY.`;
294
-
295
295
  const userPrompt = this.buildUserPrompt(toolName, argsJson, context);
296
296
 
297
297
  return new Promise<boolean>((resolve, reject) => {
298
298
  const data = JSON.stringify({
299
299
  model: modelId,
300
300
  max_tokens: 10,
301
- system: systemPrompt,
301
+ system: CLASSIFIER_SYSTEM_PROMPT,
302
302
  messages: [{ role: "user", content: userPrompt }],
303
303
  });
304
304
  const headers: Record<string, string | number> = {
@@ -349,39 +349,11 @@ Output exactly one word: ALLOW or DENY.`;
349
349
  argsJson: string,
350
350
  context: ClassifierContext,
351
351
  ): Promise<boolean> {
352
- const systemPrompt = `You are a security classifier for an autonomous coding agent. Decide ALLOW or DENY for a pending tool call.
353
-
354
- DEFAULT: ALLOW. The user enabled auto mode because they trust the agent. Only deny clear security violations.
355
-
356
- ALLOW:
357
- - Read-only commands
358
- - Git read commands
359
- - Build/test/lint commands
360
- - Writing/editing files inside the project
361
- - Commands clearly requested by the user
362
-
363
- DENY:
364
- - rm/unlink/rmdir unless the user explicitly asked to delete/remove that file
365
- - system-path modifications (/etc, /usr, /bin, /System, ~/.ssh)
366
- - sudo/su
367
- - curl/wget piped to shell
368
- - exfiltrating local secrets/files
369
- - ssh/scp/sftp
370
- - nc/ncat -l
371
- - kill -9 / killall
372
- - crontab
373
- - dd / mkfs
374
- - sed -i
375
- - npm/pip/twine publish
376
- - git push --force / -f
377
-
378
- Output exactly one word: ALLOW or DENY.`;
379
-
380
352
  const userPrompt = this.buildUserPrompt(toolName, argsJson, context);
381
353
 
382
354
  return new Promise<boolean>((resolve, reject) => {
383
355
  const data = JSON.stringify({
384
- system_instruction: { parts: [{ text: systemPrompt }] },
356
+ system_instruction: { parts: [{ text: CLASSIFIER_SYSTEM_PROMPT }] },
385
357
  contents: [{ role: "user", parts: [{ text: userPrompt }] }],
386
358
  generationConfig: { temperature: 0, maxOutputTokens: 8 },
387
359
  });
@@ -267,4 +267,6 @@ export function serializeConversation(messages: Message[]): string {
267
267
 
268
268
  export const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
269
269
 
270
- Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;
270
+ Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.
271
+
272
+ CRITICAL: Never reproduce secrets, API keys, tokens, passwords, or credentials in the summary. If you encounter any, redact them as [REDACTED].`;
@@ -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,32 +263,24 @@ 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";
258
274
  }
259
275
 
260
276
  const editMode = settingsManager.getEditMode();
261
- const toolSearchEnabled = settingsManager.getToolSearch();
262
- const defaultActiveToolNames: string[] = toolSearchEnabled
263
- ? (editMode === "hashline"
264
- ? ["hashline_read", "bash", "lsp", "tool_search", "tool_enable"]
265
- : ["read", "bash", "lsp", "tool_search", "tool_enable"])
266
- : editMode === "hashline"
267
- ? [
268
- "hashline_read",
269
- "bash",
270
- "hashline_edit",
271
- "write",
272
- "lsp",
273
- "pty_start",
274
- "pty_send",
275
- "pty_read",
276
- "pty_wait",
277
- "pty_resize",
278
- "pty_kill",
279
- ]
280
- : ["read", "bash", "edit", "write", "lsp", "pty_start", "pty_send", "pty_read", "pty_wait", "pty_resize", "pty_kill"];
277
+ const toolProfile = settingsManager.getToolProfile();
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;
281
284
  const initialActiveToolNames: string[] = options.tools
282
285
  ? options.tools.map((t) => t.name).filter((n): n is string => typeof n === "string" && n in allTools)
283
286
  : defaultActiveToolNames;