lsd-pi 1.1.4 → 1.1.6

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 (175) hide show
  1. package/README.md +2 -1
  2. package/dist/headless-ui.js +2 -0
  3. package/dist/onboarding.js +11 -8
  4. package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
  5. package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
  6. package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
  7. package/dist/resources/extensions/cache-timer/index.js +5 -0
  8. package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  9. package/dist/resources/extensions/codex-rotate/README.md +9 -3
  10. package/dist/resources/extensions/codex-rotate/commands.js +15 -8
  11. package/dist/resources/extensions/codex-rotate/index.js +17 -8
  12. package/dist/resources/extensions/memory/auto-extract.js +196 -80
  13. package/dist/resources/extensions/memory/dream.js +86 -19
  14. package/dist/resources/extensions/shared/rtk.js +89 -87
  15. package/dist/resources/extensions/subagent/index.js +33 -7
  16. package/dist/startup-model-validation.js +12 -2
  17. package/dist/update-check.js +2 -2
  18. package/dist/update-cmd.js +3 -3
  19. package/dist/welcome-screen.js +43 -14
  20. package/package.json +3 -2
  21. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts +2 -0
  22. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts.map +1 -0
  23. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js +46 -0
  24. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js.map +1 -0
  25. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
  26. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  27. package/packages/pi-coding-agent/dist/core/agent-session.js +43 -4
  28. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  29. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
  30. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  31. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  32. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  33. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  34. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  35. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  36. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
  37. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
  38. package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
  39. package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
  40. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  41. package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
  42. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  43. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
  44. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/settings-manager.js +18 -0
  46. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
  48. package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
  49. package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
  50. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
  51. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
  53. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
  55. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
  57. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  59. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +36 -22
  61. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  62. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
  63. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +23 -62
  65. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
  68. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
  71. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
  73. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
  74. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
  75. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
  76. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
  77. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +36 -0
  79. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -2
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -77
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +4 -13
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  91. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
  92. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +49 -13
  94. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +251 -39
  107. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
  109. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
  111. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
  112. package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
  113. package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
  114. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
  115. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
  117. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
  118. package/packages/pi-coding-agent/package.json +9 -4
  119. package/packages/pi-coding-agent/src/core/agent-session.clear-queue.test.ts +50 -0
  120. package/packages/pi-coding-agent/src/core/agent-session.ts +50 -4
  121. package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
  122. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  123. package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
  124. package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
  125. package/packages/pi-coding-agent/src/core/settings-manager.ts +27 -0
  126. package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
  127. package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
  128. package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
  129. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +37 -24
  130. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +22 -70
  131. package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
  132. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
  133. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +45 -0
  135. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
  136. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +104 -81
  137. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +5 -19
  138. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +55 -13
  139. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
  140. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +296 -48
  143. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
  144. package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
  145. package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
  146. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts +2 -0
  147. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts.map +1 -0
  148. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js +105 -0
  149. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js.map +1 -0
  150. package/packages/pi-tui/dist/components/editor.d.ts +4 -0
  151. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  152. package/packages/pi-tui/dist/components/editor.js +57 -3
  153. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  154. package/packages/pi-tui/dist/components/loader.d.ts +26 -6
  155. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  156. package/packages/pi-tui/dist/components/loader.js +178 -18
  157. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  158. package/packages/pi-tui/src/components/editor.ts +65 -3
  159. package/packages/pi-tui/src/components/loader.ts +196 -19
  160. package/pkg/dist/modes/interactive/theme/themes.js +2 -2
  161. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  162. package/pkg/package.json +1 -1
  163. package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
  164. package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
  165. package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
  166. package/src/resources/extensions/cache-timer/index.ts +102 -96
  167. package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  168. package/src/resources/extensions/codex-rotate/README.md +9 -3
  169. package/src/resources/extensions/codex-rotate/commands.ts +335 -329
  170. package/src/resources/extensions/codex-rotate/index.ts +85 -75
  171. package/src/resources/extensions/memory/auto-extract.ts +330 -204
  172. package/src/resources/extensions/memory/dream.ts +88 -21
  173. package/src/resources/extensions/memory/tests/auto-extract.test.ts +200 -144
  174. package/src/resources/extensions/shared/rtk.js +112 -0
  175. package/src/resources/extensions/subagent/index.ts +35 -6
@@ -18,12 +18,14 @@ import { resolveModelScope } from "../../core/model-resolver.js";
18
18
  import { SessionManager } from "../../core/session-manager.js";
19
19
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
20
20
  import { getPermissionMode, setPermissionMode, setFileChangeApprovalHandler, setClassifierHandler, setNetworkApprovalHandler, } from "../../core/tool-approval.js";
21
+ import { isPtyAvailable } from "../../core/pty-executor.js";
21
22
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
22
23
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
23
24
  import { resetStdinForTui } from "../../utils/reset-stdin.js";
24
25
  import { ensureTool } from "../../utils/tools-manager.js";
25
26
  import { AssistantMessageComponent } from "./components/assistant-message.js";
26
27
  import { BashExecutionComponent } from "./components/bash-execution.js";
28
+ import { EmbeddedTerminalComponent } from "./components/embedded-terminal.js";
27
29
  import { BorderedLoader } from "./components/bordered-loader.js";
28
30
  import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
29
31
  import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
@@ -79,7 +81,24 @@ export class InteractiveMode {
79
81
  this.isInitialized = false;
80
82
  this.loadingAnimation = undefined;
81
83
  this.pendingWorkingMessage = undefined;
82
- this.defaultWorkingMessage = "Working...";
84
+ this.defaultWorkingMessage = "Cooking…";
85
+ this.workingMessages = [
86
+ "Cooking…",
87
+ "Vibing…",
88
+ "Brainworming…",
89
+ "Scheming…",
90
+ "Spelunking…",
91
+ "Manifesting…",
92
+ "Yak shaving…",
93
+ "Grokking…",
94
+ "Pondering…",
95
+ "Going deep…",
96
+ "Caffeinating…",
97
+ "On a mission…",
98
+ "Consulting the void…",
99
+ "Doing the thing…",
100
+ "Almost maybe…",
101
+ ];
83
102
  this.lastSigintTime = 0;
84
103
  this.lastEscapeTime = 0;
85
104
  this.changelogMarkdown = undefined;
@@ -91,10 +110,14 @@ export class InteractiveMode {
91
110
  this.streamingMessage = undefined;
92
111
  // Tool execution tracking: toolCallId -> component
93
112
  this.pendingTools = new Map();
113
+ this.agentPtyComponents = new Map();
94
114
  // Tool output expansion state
95
115
  this.toolOutputExpanded = false;
96
116
  // Thinking block visibility state
97
117
  this.hideThinkingBlock = false;
118
+ // Pin last prompt feature
119
+ this.pinLastPromptEnabled = false;
120
+ this.lastSentPromptText = undefined;
98
121
  // Skill commands: command name -> skill file path
99
122
  this.skillCommands = new Map();
100
123
  // Track if editor is in bash mode (text starts with !)
@@ -157,6 +180,8 @@ export class InteractiveMode {
157
180
  this.footerDataProvider.setSandboxStatus("disabled");
158
181
  // Load hide thinking block setting
159
182
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
183
+ // Load pin last prompt setting
184
+ this.pinLastPromptEnabled = this.settingsManager.getPinLastPrompt();
160
185
  // Register themes from resource loader and initialize
161
186
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
162
187
  initTheme(this.settingsManager.getTheme(), true, this.settingsManager.getThemeAccent());
@@ -419,6 +444,7 @@ export class InteractiveMode {
419
444
  // Main interactive loop
420
445
  while (true) {
421
446
  const userInput = await this.getUserInput();
447
+ this.recordLastSentPrompt(userInput);
422
448
  try {
423
449
  await this.session.prompt(userInput);
424
450
  }
@@ -1085,7 +1111,7 @@ export class InteractiveMode {
1085
1111
  this.defaultEditor.onExtensionShortcut = undefined;
1086
1112
  this.updateTerminalTitle();
1087
1113
  if (this.loadingAnimation) {
1088
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1114
+ this.loadingAnimation.setCycleMessages(this.workingMessages);
1089
1115
  }
1090
1116
  }
1091
1117
  // Maximum total widget lines to prevent viewport overflow
@@ -1093,6 +1119,41 @@ export class InteractiveMode {
1093
1119
  /**
1094
1120
  * Render all extension widgets to the widget container.
1095
1121
  */
1122
+ /**
1123
+ * Record the last user-sent prompt text and refresh the widget.
1124
+ * Called from the main loop and input-controller paths.
1125
+ */
1126
+ recordLastSentPrompt(text) {
1127
+ // Ignore slash commands and bash commands — they're not "topic" prompts
1128
+ const trimmed = text.trim();
1129
+ if (trimmed.startsWith("/") || trimmed.startsWith("!"))
1130
+ return;
1131
+ this.lastSentPromptText = trimmed;
1132
+ this.updatePinLastPromptWidget();
1133
+ }
1134
+ /**
1135
+ * Update (or remove) the "pin last prompt" widget above the editor.
1136
+ * Uses the built-in extension widget slot with key "__pin-last-prompt__".
1137
+ */
1138
+ updatePinLastPromptWidget() {
1139
+ const key = "__pin-last-prompt__";
1140
+ if (!this.pinLastPromptEnabled || !this.lastSentPromptText) {
1141
+ // Remove widget
1142
+ const existing = this.extensionWidgetsAbove.get(key);
1143
+ if (existing?.dispose)
1144
+ existing.dispose();
1145
+ this.extensionWidgetsAbove.delete(key);
1146
+ }
1147
+ else {
1148
+ // Truncate to one line (max 200 chars) to keep it compact
1149
+ const raw = this.lastSentPromptText.replace(/[\r\n]+/g, " ").trim();
1150
+ const label = raw.length > 200 ? raw.slice(0, 197) + "…" : raw;
1151
+ const container = new Container();
1152
+ container.addChild(new Text(theme.fg("dim", ` last prompt: ${label}`), 0, 0));
1153
+ this.extensionWidgetsAbove.set(key, container);
1154
+ }
1155
+ this.renderWidgets();
1156
+ }
1096
1157
  renderWidgets() {
1097
1158
  if (!this.widgetContainerAbove || !this.widgetContainerBelow)
1098
1159
  return;
@@ -1253,6 +1314,12 @@ export class InteractiveMode {
1253
1314
  const result = await this.showExtensionSelector(`${title}\n${message}`, ["Yes", "No"], opts);
1254
1315
  return result === "Yes";
1255
1316
  }
1317
+ /** Reset the loading animation back to cycling working messages. */
1318
+ resetLoadingMessage() {
1319
+ if (this.loadingAnimation) {
1320
+ this.loadingAnimation.setCycleMessages(this.workingMessages);
1321
+ }
1322
+ }
1256
1323
  _registerApprovalHandlers() {
1257
1324
  const classifierService = new ClassifierService(this.session.modelRegistry.authStorage);
1258
1325
  setFileChangeApprovalHandler(async (request) => {
@@ -1260,9 +1327,7 @@ export class InteractiveMode {
1260
1327
  const actionLabel = request.action.toUpperCase();
1261
1328
  const title = `Allow ${actionLabel}: ${request.path}\n${request.message}`;
1262
1329
  const approved = await this._showApprovalConfirm(title);
1263
- if (this.loadingAnimation) {
1264
- this.loadingAnimation.setMessage(this.defaultWorkingMessage);
1265
- }
1330
+ this.resetLoadingMessage();
1266
1331
  return approved;
1267
1332
  });
1268
1333
  setClassifierHandler(async (request) => {
@@ -1299,25 +1364,19 @@ export class InteractiveMode {
1299
1364
  sessionId: this.session.sessionId,
1300
1365
  });
1301
1366
  if (decision.approved) {
1302
- if (this.loadingAnimation) {
1303
- this.loadingAnimation.setMessage(this.defaultWorkingMessage);
1304
- }
1367
+ this.resetLoadingMessage();
1305
1368
  return true;
1306
1369
  }
1307
1370
  this.loadingAnimation?.setMessage("Waiting for approval…");
1308
1371
  const title = `Classifier ${decision.source === "rule" ? "blocked" : "did not approve"} ${request.toolName}\n${decision.reason}\n${argsPreview}`;
1309
1372
  const approved = await this._showApprovalConfirm(title);
1310
- if (this.loadingAnimation) {
1311
- this.loadingAnimation.setMessage(this.defaultWorkingMessage);
1312
- }
1373
+ this.resetLoadingMessage();
1313
1374
  return approved;
1314
1375
  });
1315
1376
  setNetworkApprovalHandler(async (request) => {
1316
1377
  this.loadingAnimation?.setMessage("Waiting for network approval…");
1317
1378
  const result = await this.showExtensionSelector(`${request.message}\n${request.command}`, ["Allow once", "Allow for session", "Deny"]);
1318
- if (this.loadingAnimation) {
1319
- this.loadingAnimation.setMessage(this.defaultWorkingMessage);
1320
- }
1379
+ this.resetLoadingMessage();
1321
1380
  if (result === "Allow once")
1322
1381
  return "allow-once";
1323
1382
  if (result === "Allow for session")
@@ -1606,6 +1665,7 @@ export class InteractiveMode {
1606
1665
  this.defaultEditor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
1607
1666
  this.defaultEditor.onAction("cyclePermissionMode", () => this.cyclePermissionMode());
1608
1667
  this.defaultEditor.onAction("showHotkeys", () => showHotkeys(this.getSlashCommandContext()));
1668
+ this.defaultEditor.onAction("terminalFocus", () => this.toggleEmbeddedTerminalFocus());
1609
1669
  // Global debug handler on TUI (works regardless of focus)
1610
1670
  this.ui.onDebug = () => this.handleDebugCommand();
1611
1671
  this.defaultEditor.onAction("selectModel", () => this.showModelSelector());
@@ -1921,7 +1981,7 @@ export class InteractiveMode {
1921
1981
  break;
1922
1982
  }
1923
1983
  case "assistant": {
1924
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.settingsManager.getTimestampFormat());
1984
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.settingsManager.getTimestampFormat(), this.session.thinkingLevel || "off");
1925
1985
  this.chatContainer.addChild(assistantComponent);
1926
1986
  break;
1927
1987
  }
@@ -2225,8 +2285,37 @@ export class InteractiveMode {
2225
2285
  for (const component of this.pendingBashComponents) {
2226
2286
  component.setExpanded(expanded);
2227
2287
  }
2288
+ this.updateEditorExpandHint();
2228
2289
  this.ui.requestRender();
2229
2290
  }
2291
+ /** Append/remove the "ctrl+o to expand" hint in the editor bottom border. */
2292
+ updateEditorExpandHint() {
2293
+ const expandKey = appKey(this.keybindings, "expandTools");
2294
+ const collapseHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " collapse")}`;
2295
+ const expandHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " : verbose")}`;
2296
+ // The base hint set during agent_start
2297
+ const enterKey = theme.fg("dim", "↵");
2298
+ const followUpKey = theme.fg("dim", appKey(this.keybindings, "followUp"));
2299
+ const steerLabel = theme.fg("muted", " steer");
2300
+ const queueLabel = theme.fg("muted", " queue");
2301
+ const baseHint = `${enterKey}${steerLabel} ${followUpKey}${queueLabel}`;
2302
+ // Check if there are any expandable tool outputs in the chat
2303
+ const hasToolOutputs = this.chatContainer.children.some(isExpandable) ||
2304
+ !!this.bashComponent ||
2305
+ this.pendingBashComponents.length > 0;
2306
+ const activeHint = this.toolOutputExpanded ? collapseHint : expandHint;
2307
+ if (this.loadingAnimation) {
2308
+ // Agent is running — always show expand/collapse hint when there are tool outputs
2309
+ this.defaultEditor.bottomHint = hasToolOutputs
2310
+ ? `${baseHint} ${activeHint}`
2311
+ : baseHint;
2312
+ }
2313
+ else if (hasToolOutputs) {
2314
+ // Idle — show expand/collapse hint so user knows ctrl+o works
2315
+ this.defaultEditor.bottomHint = activeHint;
2316
+ }
2317
+ // If no tool outputs and idle, leave bottomHint as-is (cleared by agent_end)
2318
+ }
2230
2319
  toggleThinkingBlockVisibility() {
2231
2320
  this.hideThinkingBlock = !this.hideThinkingBlock;
2232
2321
  this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
@@ -2517,6 +2606,7 @@ export class InteractiveMode {
2517
2606
  enableSkillCommands: this.settingsManager.getEnableSkillCommands(),
2518
2607
  codexRotate: this.settingsManager.getCodexRotate(),
2519
2608
  cacheTimer: this.settingsManager.getCacheTimer(),
2609
+ pinLastPrompt: this.settingsManager.getPinLastPrompt(),
2520
2610
  steeringMode: this.session.steeringMode,
2521
2611
  followUpMode: this.session.followUpMode,
2522
2612
  transport: this.settingsManager.getTransport(),
@@ -2540,6 +2630,8 @@ export class InteractiveMode {
2540
2630
  toolOutputMode: this.settingsManager.getToolOutputMode(),
2541
2631
  rtk: this.settingsManager.getRtk(),
2542
2632
  editorScheme: this.settingsManager.getEditorScheme(),
2633
+ autoDream: this.settingsManager.getAutoDream(),
2634
+ autoMemory: this.settingsManager.getAutoMemory(),
2543
2635
  sandboxEnabled: this.settingsManager.getSandboxSettings().enabled ?? true,
2544
2636
  sandboxNetworkMode: this.settingsManager.getSandboxSettings().networkMode
2545
2637
  ?? (this.settingsManager.getSandboxSettings().networkEnabled === true ? "allow" : this.settingsManager.getSandboxSettings().networkEnabled === false ? "deny" : "ask"),
@@ -2593,10 +2685,24 @@ export class InteractiveMode {
2593
2685
  this.settingsManager.setCacheTimer(enabled);
2594
2686
  this.showStatus(`Cache timer: ${enabled ? "enabled" : "disabled"}`);
2595
2687
  },
2688
+ onPinLastPromptChange: (enabled) => {
2689
+ this.settingsManager.setPinLastPrompt(enabled);
2690
+ this.pinLastPromptEnabled = enabled;
2691
+ this.updatePinLastPromptWidget();
2692
+ this.showStatus(`Pin last prompt: ${enabled ? "enabled" : "disabled"}`);
2693
+ },
2596
2694
  onRtkChange: (enabled) => {
2597
2695
  this.settingsManager.setRtk(enabled);
2598
2696
  this.showStatus(`RTK: ${enabled ? "enabled" : "disabled"} (restart required)`);
2599
2697
  },
2698
+ onAutoDreamChange: (enabled) => {
2699
+ this.settingsManager.setAutoDream(enabled);
2700
+ this.showStatus(`Auto dream: ${enabled ? "enabled" : "disabled"}`);
2701
+ },
2702
+ onAutoMemoryChange: (enabled) => {
2703
+ this.settingsManager.setAutoMemory(enabled);
2704
+ this.showStatus(`Auto memory: ${enabled ? "enabled" : "disabled"}`);
2705
+ },
2600
2706
  onSteeringModeChange: (mode) => {
2601
2707
  this.session.setSteeringMode(mode);
2602
2708
  },
@@ -2949,7 +3055,7 @@ export class InteractiveMode {
2949
3055
  this.session.abortBranchSummary();
2950
3056
  };
2951
3057
  this.chatContainer.addChild(new Spacer(1));
2952
- summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
3058
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("text", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
2953
3059
  this.statusContainer.addChild(summaryLoader);
2954
3060
  this.ui.requestRender();
2955
3061
  }
@@ -3344,6 +3450,63 @@ export class InteractiveMode {
3344
3450
  this.handleDaxnuts();
3345
3451
  }
3346
3452
  }
3453
+ getAgentPtyComponent(sessionId) {
3454
+ return this.agentPtyComponents.get(sessionId);
3455
+ }
3456
+ ensureAgentPtyComponent(sessionId, command) {
3457
+ let component = this.agentPtyComponents.get(sessionId);
3458
+ if (component)
3459
+ return component;
3460
+ component = new EmbeddedTerminalComponent(command || `PTY ${sessionId}`, this.ui, this.settingsManager.getToolOutputMode(), appKey(this.keybindings, "terminalFocus"), false);
3461
+ component.setStatusOverride(theme.fg("muted", "Agent-controlled terminal session"));
3462
+ component.setExpanded(true);
3463
+ this.chatContainer.addChild(component);
3464
+ this.agentPtyComponents.set(sessionId, component);
3465
+ return component;
3466
+ }
3467
+ updateAgentPtyComponent(sessionId, options) {
3468
+ const component = this.ensureAgentPtyComponent(sessionId, options?.command);
3469
+ if (options?.screenText !== undefined) {
3470
+ component.setScreenText(options.screenText);
3471
+ }
3472
+ if (options?.completed) {
3473
+ component.setStatusOverride(undefined);
3474
+ component.setComplete(options.exitCode, options.cancelled ?? false);
3475
+ }
3476
+ else {
3477
+ component.setStatusOverride(theme.fg("muted", "Agent-controlled terminal session"));
3478
+ }
3479
+ }
3480
+ clearAgentPtyComponents() {
3481
+ for (const [, component] of this.agentPtyComponents) {
3482
+ this.chatContainer.removeChild(component);
3483
+ }
3484
+ this.agentPtyComponents.clear();
3485
+ }
3486
+ focusEmbeddedTerminal(component) {
3487
+ this.focusedEmbeddedTerminal = component;
3488
+ this.lastEmbeddedTerminalSize = undefined;
3489
+ this.ui.setFocus(component);
3490
+ component.invalidate();
3491
+ this.ui.requestRender();
3492
+ }
3493
+ releaseEmbeddedTerminalFocus() {
3494
+ const focused = this.focusedEmbeddedTerminal;
3495
+ this.focusedEmbeddedTerminal = undefined;
3496
+ this.lastEmbeddedTerminalSize = undefined;
3497
+ this.ui.setFocus(this.editor);
3498
+ focused?.invalidate();
3499
+ this.ui.requestRender();
3500
+ }
3501
+ toggleEmbeddedTerminalFocus() {
3502
+ if (this.focusedEmbeddedTerminal) {
3503
+ this.releaseEmbeddedTerminalFocus();
3504
+ return;
3505
+ }
3506
+ if (this.bashComponent instanceof EmbeddedTerminalComponent) {
3507
+ this.focusEmbeddedTerminal(this.bashComponent);
3508
+ }
3509
+ }
3347
3510
  async handleBashCommand(command, excludeFromContext = false, displayCommand, loginShell) {
3348
3511
  const extensionRunner = this.session.extensionRunner;
3349
3512
  const label = displayCommand || command;
@@ -3360,20 +3523,21 @@ export class InteractiveMode {
3360
3523
  if (eventResult?.result) {
3361
3524
  const result = eventResult.result;
3362
3525
  // Create UI component for display
3363
- this.bashComponent = new BashExecutionComponent(label, this.ui, excludeFromContext, this.settingsManager.getToolOutputMode(), isRtkEnabled(), result.sandboxed ?? false);
3364
- this.bashComponent.setExpanded(this.toolOutputExpanded);
3526
+ const component = new BashExecutionComponent(label, this.ui, excludeFromContext, this.settingsManager.getToolOutputMode(), isRtkEnabled(), result.sandboxed ?? false);
3527
+ this.bashComponent = component;
3528
+ component.setExpanded(this.toolOutputExpanded);
3365
3529
  if (this.session.isStreaming) {
3366
- this.pendingMessagesContainer.addChild(this.bashComponent);
3367
- this.pendingBashComponents.push(this.bashComponent);
3530
+ this.pendingMessagesContainer.addChild(component);
3531
+ this.pendingBashComponents.push(component);
3368
3532
  }
3369
3533
  else {
3370
- this.chatContainer.addChild(this.bashComponent);
3534
+ this.chatContainer.addChild(component);
3371
3535
  }
3372
3536
  // Show output and complete
3373
3537
  if (result.output) {
3374
- this.bashComponent.appendOutput(result.output);
3538
+ component.appendOutput(result.output);
3375
3539
  }
3376
- this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath, result.sandboxed);
3540
+ component.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath, result.sandboxed);
3377
3541
  // Record the result in session
3378
3542
  this.session.recordBashResult(command, result, { excludeFromContext });
3379
3543
  this.bashComponent = undefined;
@@ -3382,33 +3546,73 @@ export class InteractiveMode {
3382
3546
  }
3383
3547
  // Normal execution path (possibly with custom operations)
3384
3548
  const isDeferred = this.session.isStreaming;
3385
- this.bashComponent = new BashExecutionComponent(label, this.ui, excludeFromContext, this.settingsManager.getToolOutputMode(), isRtkEnabled(), false);
3386
- this.bashComponent.setExpanded(this.toolOutputExpanded);
3549
+ const canUsePty = !eventResult?.operations && await isPtyAvailable();
3550
+ if (canUsePty) {
3551
+ const component = new EmbeddedTerminalComponent(label, this.ui, this.settingsManager.getToolOutputMode(), appKey(this.keybindings, "terminalFocus"), excludeFromContext);
3552
+ this.bashComponent = component;
3553
+ component.setExpanded(this.toolOutputExpanded);
3554
+ if (isDeferred) {
3555
+ this.pendingMessagesContainer.addChild(component);
3556
+ this.pendingBashComponents.push(component);
3557
+ }
3558
+ else {
3559
+ this.chatContainer.addChild(component);
3560
+ }
3561
+ this.ui.requestRender();
3562
+ try {
3563
+ const session = await this.session.executeBashInteractive(command, {
3564
+ onChunk: (chunk) => {
3565
+ component.appendOutput(chunk);
3566
+ this.ui.requestRender();
3567
+ },
3568
+ cols: Math.max(40, this.ui.terminal.columns - 2),
3569
+ rows: Math.max(10, this.ui.terminal.rows - 8),
3570
+ loginShell,
3571
+ });
3572
+ component.setHandle(session.handle, () => this.releaseEmbeddedTerminalFocus());
3573
+ if (!isDeferred) {
3574
+ this.focusEmbeddedTerminal(component);
3575
+ }
3576
+ const result = await session.result;
3577
+ component.setComplete(result.exitCode, result.cancelled);
3578
+ this.session.recordBashResult(command, result, { excludeFromContext });
3579
+ }
3580
+ catch (error) {
3581
+ component.setComplete(undefined, false);
3582
+ this.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
3583
+ }
3584
+ finally {
3585
+ if (this.focusedEmbeddedTerminal === component) {
3586
+ this.releaseEmbeddedTerminalFocus();
3587
+ }
3588
+ this.session.clearBashAbortController();
3589
+ this.bashComponent = undefined;
3590
+ this.ui.requestRender();
3591
+ }
3592
+ return;
3593
+ }
3594
+ const fallbackComponent = new BashExecutionComponent(label, this.ui, excludeFromContext, this.settingsManager.getToolOutputMode(), isRtkEnabled(), false);
3595
+ this.bashComponent = fallbackComponent;
3596
+ fallbackComponent.setExpanded(this.toolOutputExpanded);
3387
3597
  if (isDeferred) {
3388
3598
  // Show in pending area when agent is streaming
3389
- this.pendingMessagesContainer.addChild(this.bashComponent);
3390
- this.pendingBashComponents.push(this.bashComponent);
3599
+ this.pendingMessagesContainer.addChild(fallbackComponent);
3600
+ this.pendingBashComponents.push(fallbackComponent);
3391
3601
  }
3392
3602
  else {
3393
3603
  // Show in chat immediately when agent is idle
3394
- this.chatContainer.addChild(this.bashComponent);
3604
+ this.chatContainer.addChild(fallbackComponent);
3395
3605
  }
3396
3606
  this.ui.requestRender();
3397
3607
  try {
3398
3608
  const result = await this.session.executeBash(command, (chunk) => {
3399
- if (this.bashComponent) {
3400
- this.bashComponent.appendOutput(chunk);
3401
- this.ui.requestRender();
3402
- }
3609
+ fallbackComponent.appendOutput(chunk);
3610
+ this.ui.requestRender();
3403
3611
  }, { excludeFromContext, operations: eventResult?.operations, loginShell });
3404
- if (this.bashComponent) {
3405
- this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath, result.sandboxed);
3406
- }
3612
+ fallbackComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath, result.sandboxed);
3407
3613
  }
3408
3614
  catch (error) {
3409
- if (this.bashComponent) {
3410
- this.bashComponent.setComplete(undefined, false);
3411
- }
3615
+ fallbackComponent.setComplete(undefined, false);
3412
3616
  this.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
3413
3617
  }
3414
3618
  this.bashComponent = undefined;
@@ -3430,7 +3634,7 @@ export class InteractiveMode {
3430
3634
  this.chatContainer.addChild(new Spacer(1));
3431
3635
  const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
3432
3636
  const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
3433
- const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3637
+ const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("text", spinner), (text) => theme.fg("muted", text), label);
3434
3638
  this.statusContainer.addChild(compactingLoader);
3435
3639
  this.ui.requestRender();
3436
3640
  let result;
@@ -3463,6 +3667,14 @@ export class InteractiveMode {
3463
3667
  requestRender(force = false) {
3464
3668
  if (!this.isInitialized)
3465
3669
  return;
3670
+ if (this.focusedEmbeddedTerminal) {
3671
+ const cols = Math.max(40, this.ui.terminal.columns - 2);
3672
+ const rows = Math.max(10, this.ui.terminal.rows - 8);
3673
+ if (!this.lastEmbeddedTerminalSize || this.lastEmbeddedTerminalSize.cols !== cols || this.lastEmbeddedTerminalSize.rows !== rows) {
3674
+ this.focusedEmbeddedTerminal.resize(cols, rows);
3675
+ this.lastEmbeddedTerminalSize = { cols, rows };
3676
+ }
3677
+ }
3466
3678
  this.ui.requestRender(force);
3467
3679
  }
3468
3680
  stop() {