@selesai/code 0.1.14 → 0.1.16

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";
@@ -29,6 +28,7 @@ import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelo
29
28
  import { copyToClipboard } from "../../utils/clipboard.js";
30
29
  import { extensionForImageMimeType, 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";
@@ -1994,27 +1995,66 @@ export class InteractiveMode {
1994
1995
  try {
1995
1996
  const image = await readClipboardImage();
1996
1997
  if (!image) {
1998
+ this.showWarning("No image found in clipboard");
1997
1999
  return;
1998
2000
  }
1999
- // Write to temp file
2000
2001
  const tmpDir = os.tmpdir();
2001
2002
  const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2002
- const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2003
+ const fileName = `pi-clipboard-${Date.now()}-${this.pendingInputImages.length + 1}.${ext}`;
2003
2004
  const filePath = path.join(tmpDir, fileName);
2004
2005
  fs.writeFileSync(filePath, Buffer.from(image.bytes));
2005
- // Insert file path directly
2006
- this.editor.insertTextAtCursor?.(filePath);
2006
+ let attachment;
2007
+ let dimensionNote;
2008
+ const autoResizeImages = this.settingsManager.getImageAutoResize();
2009
+ if (autoResizeImages) {
2010
+ const resized = await resizeImage(image.bytes, image.mimeType).catch(() => null);
2011
+ if (!resized) {
2012
+ this.showWarning("Clipboard image attached without resize");
2013
+ attachment = {
2014
+ type: "image",
2015
+ mimeType: image.mimeType,
2016
+ data: Buffer.from(image.bytes).toString("base64"),
2017
+ };
2018
+ }
2019
+ else {
2020
+ dimensionNote = formatDimensionNote(resized);
2021
+ attachment = {
2022
+ type: "image",
2023
+ mimeType: resized.mimeType,
2024
+ data: resized.data,
2025
+ };
2026
+ }
2027
+ }
2028
+ else {
2029
+ attachment = {
2030
+ type: "image",
2031
+ mimeType: image.mimeType,
2032
+ data: Buffer.from(image.bytes).toString("base64"),
2033
+ };
2034
+ }
2035
+ this.pendingInputImages.push(attachment);
2036
+ const label = dimensionNote ? `${filePath} ${dimensionNote}` : filePath;
2037
+ this.editor.insertTextAtCursor?.(label);
2007
2038
  this.ui.requestRender();
2008
2039
  }
2009
- catch {
2010
- // Silently ignore clipboard errors (may not have permission, etc.)
2040
+ catch (error) {
2041
+ this.showWarning(`Failed to paste image: ${error instanceof Error ? error.message : String(error)}`);
2042
+ }
2043
+ }
2044
+ consumePendingInputImages() {
2045
+ if (this.pendingInputImages.length === 0) {
2046
+ return undefined;
2011
2047
  }
2048
+ const images = this.pendingInputImages;
2049
+ this.pendingInputImages = [];
2050
+ return images;
2012
2051
  }
2013
2052
  setupEditorSubmitHandler() {
2014
2053
  this.defaultEditor.onSubmit = async (text) => {
2015
2054
  text = text.trim();
2016
2055
  if (!text)
2017
2056
  return;
2057
+ const images = this.consumePendingInputImages();
2018
2058
  // Handle commands
2019
2059
  if (text === "/settings") {
2020
2060
  this.showSettingsSelector();
@@ -2165,10 +2205,10 @@ export class InteractiveMode {
2165
2205
  if (this.isExtensionCommand(text)) {
2166
2206
  this.editor.addToHistory?.(text);
2167
2207
  this.editor.setText("");
2168
- await this.session.prompt(text);
2208
+ await this.session.prompt(text, { images });
2169
2209
  }
2170
2210
  else {
2171
- this.queueCompactionMessage(text, "steer");
2211
+ this.queueCompactionMessage(text, "steer", images);
2172
2212
  }
2173
2213
  return;
2174
2214
  }
@@ -2177,7 +2217,7 @@ export class InteractiveMode {
2177
2217
  if (this.session.isStreaming) {
2178
2218
  this.editor.addToHistory?.(text);
2179
2219
  this.editor.setText("");
2180
- await this.session.prompt(text, { streamingBehavior: "steer" });
2220
+ await this.session.prompt(text, { streamingBehavior: "steer", images });
2181
2221
  this.updatePendingMessagesDisplay();
2182
2222
  this.ui.requestRender();
2183
2223
  return;
@@ -2185,11 +2225,12 @@ export class InteractiveMode {
2185
2225
  // Normal message submission
2186
2226
  // First, move any pending bash components to chat
2187
2227
  this.flushPendingBashComponents();
2228
+ const input = { text, images };
2188
2229
  if (this.onInputCallback) {
2189
- this.onInputCallback(text);
2230
+ this.onInputCallback(input);
2190
2231
  }
2191
2232
  else {
2192
- this.pendingUserInputs.push(text);
2233
+ this.pendingUserInputs.push(input);
2193
2234
  }
2194
2235
  this.editor.addToHistory?.(text);
2195
2236
  };
@@ -2685,9 +2726,9 @@ export class InteractiveMode {
2685
2726
  return queuedInput;
2686
2727
  }
2687
2728
  return new Promise((resolve) => {
2688
- this.onInputCallback = (text) => {
2729
+ this.onInputCallback = (input) => {
2689
2730
  this.onInputCallback = undefined;
2690
- resolve(text);
2731
+ resolve(input);
2691
2732
  };
2692
2733
  });
2693
2734
  }
@@ -2882,22 +2923,24 @@ export class InteractiveMode {
2882
2923
  return;
2883
2924
  // Queue input during compaction (extension commands execute immediately)
2884
2925
  if (this.session.isCompacting) {
2926
+ const images = this.consumePendingInputImages();
2885
2927
  if (this.isExtensionCommand(text)) {
2886
2928
  this.editor.addToHistory?.(text);
2887
2929
  this.editor.setText("");
2888
- await this.session.prompt(text);
2930
+ await this.session.prompt(text, { images });
2889
2931
  }
2890
2932
  else {
2891
- this.queueCompactionMessage(text, "followUp");
2933
+ this.queueCompactionMessage(text, "followUp", images);
2892
2934
  }
2893
2935
  return;
2894
2936
  }
2895
2937
  // Alt+Enter queues a follow-up message (waits until agent finishes)
2896
2938
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
2897
2939
  if (this.session.isStreaming) {
2940
+ const images = this.consumePendingInputImages();
2898
2941
  this.editor.addToHistory?.(text);
2899
2942
  this.editor.setText("");
2900
- await this.session.prompt(text, { streamingBehavior: "followUp" });
2943
+ await this.session.prompt(text, { streamingBehavior: "followUp", images });
2901
2944
  this.updatePendingMessagesDisplay();
2902
2945
  this.ui.requestRender();
2903
2946
  }
@@ -3040,6 +3083,7 @@ export class InteractiveMode {
3040
3083
  // =========================================================================
3041
3084
  clearEditor() {
3042
3085
  this.editor.setText("");
3086
+ this.pendingInputImages = [];
3043
3087
  this.ui.requestRender();
3044
3088
  }
3045
3089
  showError(errorMessage) {
@@ -3157,8 +3201,8 @@ export class InteractiveMode {
3157
3201
  }
3158
3202
  return allQueued.length;
3159
3203
  }
3160
- queueCompactionMessage(text, mode) {
3161
- this.compactionQueuedMessages.push({ text, mode });
3204
+ queueCompactionMessage(text, mode, images) {
3205
+ this.compactionQueuedMessages.push({ text, images, mode });
3162
3206
  this.editor.addToHistory?.(text);
3163
3207
  this.editor.setText("");
3164
3208
  this.updatePendingMessagesDisplay();
@@ -3190,13 +3234,13 @@ export class InteractiveMode {
3190
3234
  // When retry is pending, queue messages for the retry turn
3191
3235
  for (const message of queuedMessages) {
3192
3236
  if (this.isExtensionCommand(message.text)) {
3193
- await this.session.prompt(message.text);
3237
+ await this.session.prompt(message.text, { images: message.images });
3194
3238
  }
3195
3239
  else if (message.mode === "followUp") {
3196
- await this.session.followUp(message.text);
3240
+ await this.session.followUp(message.text, message.images);
3197
3241
  }
3198
3242
  else {
3199
- await this.session.steer(message.text);
3243
+ await this.session.steer(message.text, message.images);
3200
3244
  }
3201
3245
  }
3202
3246
  this.updatePendingMessagesDisplay();
@@ -3207,7 +3251,7 @@ export class InteractiveMode {
3207
3251
  if (firstPromptIndex === -1) {
3208
3252
  // All extension commands - execute them all
3209
3253
  for (const message of queuedMessages) {
3210
- await this.session.prompt(message.text);
3254
+ await this.session.prompt(message.text, { images: message.images });
3211
3255
  }
3212
3256
  return;
3213
3257
  }
@@ -3216,22 +3260,22 @@ export class InteractiveMode {
3216
3260
  const firstPrompt = queuedMessages[firstPromptIndex];
3217
3261
  const rest = queuedMessages.slice(firstPromptIndex + 1);
3218
3262
  for (const message of preCommands) {
3219
- await this.session.prompt(message.text);
3263
+ await this.session.prompt(message.text, { images: message.images });
3220
3264
  }
3221
3265
  // Send first prompt (starts streaming)
3222
- const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
3266
+ const promptPromise = this.session.prompt(firstPrompt.text, { images: firstPrompt.images }).catch((error) => {
3223
3267
  restoreQueue(error);
3224
3268
  });
3225
3269
  // Queue remaining messages
3226
3270
  for (const message of rest) {
3227
3271
  if (this.isExtensionCommand(message.text)) {
3228
- await this.session.prompt(message.text);
3272
+ await this.session.prompt(message.text, { images: message.images });
3229
3273
  }
3230
3274
  else if (message.mode === "followUp") {
3231
- await this.session.followUp(message.text);
3275
+ await this.session.followUp(message.text, message.images);
3232
3276
  }
3233
3277
  else {
3234
- await this.session.steer(message.text);
3278
+ await this.session.steer(message.text, message.images);
3235
3279
  }
3236
3280
  }
3237
3281
  this.updatePendingMessagesDisplay();