@selesai/code 0.1.14 → 0.1.15

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.
@@ -2,7 +2,6 @@
2
2
  * Interactive mode for the coding agent.
3
3
  * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
4
4
  */
5
- import * as crypto from "node:crypto";
6
5
  import * as fs from "node:fs";
7
6
  import * as os from "node:os";
8
7
  import * as path from "node:path";
@@ -27,8 +26,9 @@ import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
27
26
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
28
27
  import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
29
28
  import { copyToClipboard } from "../../utils/clipboard.js";
30
- import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
29
+ import { readClipboardImage } from "../../utils/clipboard-image.js";
31
30
  import { parseGitUrl } from "../../utils/git.js";
31
+ import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";
32
32
  import { getCwdRelativePath } from "../../utils/paths.js";
33
33
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
34
34
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
@@ -152,6 +152,7 @@ export class InteractiveMode {
152
152
  isInitialized = false;
153
153
  onInputCallback;
154
154
  pendingUserInputs = [];
155
+ pendingInputImages = [];
155
156
  loadingAnimation = undefined;
156
157
  workingMessage = undefined;
157
158
  workingVisible = true;
@@ -595,7 +596,7 @@ export class InteractiveMode {
595
596
  while (true) {
596
597
  const userInput = await this.getUserInput();
597
598
  try {
598
- await this.session.prompt(userInput);
599
+ await this.session.prompt(userInput.text, { images: userInput.images });
599
600
  }
600
601
  catch (error) {
601
602
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1996,25 +1997,54 @@ export class InteractiveMode {
1996
1997
  if (!image) {
1997
1998
  return;
1998
1999
  }
1999
- // Write to temp file
2000
- const tmpDir = os.tmpdir();
2001
- const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2002
- const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2003
- const filePath = path.join(tmpDir, fileName);
2004
- fs.writeFileSync(filePath, Buffer.from(image.bytes));
2005
- // Insert file path directly
2006
- this.editor.insertTextAtCursor?.(filePath);
2000
+ let attachment;
2001
+ let dimensionNote;
2002
+ const autoResizeImages = this.settingsManager.getImageAutoResize();
2003
+ if (autoResizeImages) {
2004
+ const resized = await resizeImage(image.bytes, image.mimeType);
2005
+ if (!resized) {
2006
+ this.showWarning("Clipboard image could not be resized below the inline image size limit.");
2007
+ return;
2008
+ }
2009
+ dimensionNote = formatDimensionNote(resized);
2010
+ attachment = {
2011
+ type: "image",
2012
+ mimeType: resized.mimeType,
2013
+ data: resized.data,
2014
+ };
2015
+ }
2016
+ else {
2017
+ attachment = {
2018
+ type: "image",
2019
+ mimeType: image.mimeType,
2020
+ data: Buffer.from(image.bytes).toString("base64"),
2021
+ };
2022
+ }
2023
+ this.pendingInputImages.push(attachment);
2024
+ const label = dimensionNote
2025
+ ? `[clipboard image ${this.pendingInputImages.length}: ${dimensionNote}]`
2026
+ : `[clipboard image ${this.pendingInputImages.length}]`;
2027
+ this.editor.insertTextAtCursor?.(label);
2007
2028
  this.ui.requestRender();
2008
2029
  }
2009
2030
  catch {
2010
2031
  // Silently ignore clipboard errors (may not have permission, etc.)
2011
2032
  }
2012
2033
  }
2034
+ consumePendingInputImages() {
2035
+ if (this.pendingInputImages.length === 0) {
2036
+ return undefined;
2037
+ }
2038
+ const images = this.pendingInputImages;
2039
+ this.pendingInputImages = [];
2040
+ return images;
2041
+ }
2013
2042
  setupEditorSubmitHandler() {
2014
2043
  this.defaultEditor.onSubmit = async (text) => {
2015
2044
  text = text.trim();
2016
2045
  if (!text)
2017
2046
  return;
2047
+ const images = this.consumePendingInputImages();
2018
2048
  // Handle commands
2019
2049
  if (text === "/settings") {
2020
2050
  this.showSettingsSelector();
@@ -2165,10 +2195,10 @@ export class InteractiveMode {
2165
2195
  if (this.isExtensionCommand(text)) {
2166
2196
  this.editor.addToHistory?.(text);
2167
2197
  this.editor.setText("");
2168
- await this.session.prompt(text);
2198
+ await this.session.prompt(text, { images });
2169
2199
  }
2170
2200
  else {
2171
- this.queueCompactionMessage(text, "steer");
2201
+ this.queueCompactionMessage(text, "steer", images);
2172
2202
  }
2173
2203
  return;
2174
2204
  }
@@ -2177,7 +2207,7 @@ export class InteractiveMode {
2177
2207
  if (this.session.isStreaming) {
2178
2208
  this.editor.addToHistory?.(text);
2179
2209
  this.editor.setText("");
2180
- await this.session.prompt(text, { streamingBehavior: "steer" });
2210
+ await this.session.prompt(text, { streamingBehavior: "steer", images });
2181
2211
  this.updatePendingMessagesDisplay();
2182
2212
  this.ui.requestRender();
2183
2213
  return;
@@ -2185,11 +2215,12 @@ export class InteractiveMode {
2185
2215
  // Normal message submission
2186
2216
  // First, move any pending bash components to chat
2187
2217
  this.flushPendingBashComponents();
2218
+ const input = { text, images };
2188
2219
  if (this.onInputCallback) {
2189
- this.onInputCallback(text);
2220
+ this.onInputCallback(input);
2190
2221
  }
2191
2222
  else {
2192
- this.pendingUserInputs.push(text);
2223
+ this.pendingUserInputs.push(input);
2193
2224
  }
2194
2225
  this.editor.addToHistory?.(text);
2195
2226
  };
@@ -2685,9 +2716,9 @@ export class InteractiveMode {
2685
2716
  return queuedInput;
2686
2717
  }
2687
2718
  return new Promise((resolve) => {
2688
- this.onInputCallback = (text) => {
2719
+ this.onInputCallback = (input) => {
2689
2720
  this.onInputCallback = undefined;
2690
- resolve(text);
2721
+ resolve(input);
2691
2722
  };
2692
2723
  });
2693
2724
  }
@@ -2882,22 +2913,24 @@ export class InteractiveMode {
2882
2913
  return;
2883
2914
  // Queue input during compaction (extension commands execute immediately)
2884
2915
  if (this.session.isCompacting) {
2916
+ const images = this.consumePendingInputImages();
2885
2917
  if (this.isExtensionCommand(text)) {
2886
2918
  this.editor.addToHistory?.(text);
2887
2919
  this.editor.setText("");
2888
- await this.session.prompt(text);
2920
+ await this.session.prompt(text, { images });
2889
2921
  }
2890
2922
  else {
2891
- this.queueCompactionMessage(text, "followUp");
2923
+ this.queueCompactionMessage(text, "followUp", images);
2892
2924
  }
2893
2925
  return;
2894
2926
  }
2895
2927
  // Alt+Enter queues a follow-up message (waits until agent finishes)
2896
2928
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
2897
2929
  if (this.session.isStreaming) {
2930
+ const images = this.consumePendingInputImages();
2898
2931
  this.editor.addToHistory?.(text);
2899
2932
  this.editor.setText("");
2900
- await this.session.prompt(text, { streamingBehavior: "followUp" });
2933
+ await this.session.prompt(text, { streamingBehavior: "followUp", images });
2901
2934
  this.updatePendingMessagesDisplay();
2902
2935
  this.ui.requestRender();
2903
2936
  }
@@ -3040,6 +3073,7 @@ export class InteractiveMode {
3040
3073
  // =========================================================================
3041
3074
  clearEditor() {
3042
3075
  this.editor.setText("");
3076
+ this.pendingInputImages = [];
3043
3077
  this.ui.requestRender();
3044
3078
  }
3045
3079
  showError(errorMessage) {
@@ -3157,8 +3191,8 @@ export class InteractiveMode {
3157
3191
  }
3158
3192
  return allQueued.length;
3159
3193
  }
3160
- queueCompactionMessage(text, mode) {
3161
- this.compactionQueuedMessages.push({ text, mode });
3194
+ queueCompactionMessage(text, mode, images) {
3195
+ this.compactionQueuedMessages.push({ text, images, mode });
3162
3196
  this.editor.addToHistory?.(text);
3163
3197
  this.editor.setText("");
3164
3198
  this.updatePendingMessagesDisplay();
@@ -3190,13 +3224,13 @@ export class InteractiveMode {
3190
3224
  // When retry is pending, queue messages for the retry turn
3191
3225
  for (const message of queuedMessages) {
3192
3226
  if (this.isExtensionCommand(message.text)) {
3193
- await this.session.prompt(message.text);
3227
+ await this.session.prompt(message.text, { images: message.images });
3194
3228
  }
3195
3229
  else if (message.mode === "followUp") {
3196
- await this.session.followUp(message.text);
3230
+ await this.session.followUp(message.text, message.images);
3197
3231
  }
3198
3232
  else {
3199
- await this.session.steer(message.text);
3233
+ await this.session.steer(message.text, message.images);
3200
3234
  }
3201
3235
  }
3202
3236
  this.updatePendingMessagesDisplay();
@@ -3207,7 +3241,7 @@ export class InteractiveMode {
3207
3241
  if (firstPromptIndex === -1) {
3208
3242
  // All extension commands - execute them all
3209
3243
  for (const message of queuedMessages) {
3210
- await this.session.prompt(message.text);
3244
+ await this.session.prompt(message.text, { images: message.images });
3211
3245
  }
3212
3246
  return;
3213
3247
  }
@@ -3216,22 +3250,22 @@ export class InteractiveMode {
3216
3250
  const firstPrompt = queuedMessages[firstPromptIndex];
3217
3251
  const rest = queuedMessages.slice(firstPromptIndex + 1);
3218
3252
  for (const message of preCommands) {
3219
- await this.session.prompt(message.text);
3253
+ await this.session.prompt(message.text, { images: message.images });
3220
3254
  }
3221
3255
  // Send first prompt (starts streaming)
3222
- const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
3256
+ const promptPromise = this.session.prompt(firstPrompt.text, { images: firstPrompt.images }).catch((error) => {
3223
3257
  restoreQueue(error);
3224
3258
  });
3225
3259
  // Queue remaining messages
3226
3260
  for (const message of rest) {
3227
3261
  if (this.isExtensionCommand(message.text)) {
3228
- await this.session.prompt(message.text);
3262
+ await this.session.prompt(message.text, { images: message.images });
3229
3263
  }
3230
3264
  else if (message.mode === "followUp") {
3231
- await this.session.followUp(message.text);
3265
+ await this.session.followUp(message.text, message.images);
3232
3266
  }
3233
3267
  else {
3234
- await this.session.steer(message.text);
3268
+ await this.session.steer(message.text, message.images);
3235
3269
  }
3236
3270
  }
3237
3271
  this.updatePendingMessagesDisplay();