@caupulican/pi-adaptative 0.80.3 → 0.80.5

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.
@@ -20,13 +20,14 @@ import { createCompactionSummaryMessage } from "../../core/messages.js";
20
20
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
21
21
  import { DefaultPackageManager } from "../../core/package-manager.js";
22
22
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
23
+ import { getPendingReloadBlockers } from "../../core/reload-blockers.js";
23
24
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
24
25
  import { SessionManager } from "../../core/session-manager.js";
25
26
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
26
27
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
27
28
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
28
29
  import { copyToClipboard } from "../../utils/clipboard.js";
29
- import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
30
+ import { readClipboardImage } from "../../utils/clipboard-image.js";
30
31
  import { parseGitUrl } from "../../utils/git.js";
31
32
  import { getCwdRelativePath, resolvePath } from "../../utils/paths.js";
32
33
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
@@ -206,6 +207,8 @@ export class InteractiveMode {
206
207
  isInitialized = false;
207
208
  onInputCallback;
208
209
  pendingUserInputs = [];
210
+ pendingClipboardImages = [];
211
+ clipboardImageCounter = 0;
209
212
  loadingAnimation = undefined;
210
213
  workingMessage = undefined;
211
214
  workingVisible = true;
@@ -641,7 +644,7 @@ export class InteractiveMode {
641
644
  while (true) {
642
645
  const userInput = await this.getUserInput();
643
646
  try {
644
- await this.session.prompt(userInput);
647
+ await this.session.prompt(userInput.text, { images: userInput.images });
645
648
  }
646
649
  catch (error) {
647
650
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -2107,22 +2110,50 @@ export class InteractiveMode {
2107
2110
  try {
2108
2111
  const image = await readClipboardImage();
2109
2112
  if (!image) {
2113
+ this.showStatus("No image found on the clipboard");
2110
2114
  return;
2111
2115
  }
2112
- // Write to temp file
2113
- const tmpDir = os.tmpdir();
2114
- const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2115
- const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2116
- const filePath = path.join(tmpDir, fileName);
2117
- fs.writeFileSync(filePath, Buffer.from(image.bytes));
2118
- // Insert file path directly
2119
- this.editor.insertTextAtCursor?.(filePath);
2116
+ const label = this.nextClipboardImageLabel();
2117
+ const mimeType = image.mimeType.split(";")[0]?.trim().toLowerCase() || image.mimeType;
2118
+ this.pendingClipboardImages.push({
2119
+ label,
2120
+ content: {
2121
+ type: "image",
2122
+ data: Buffer.from(image.bytes).toString("base64"),
2123
+ mimeType,
2124
+ },
2125
+ });
2126
+ this.editor.insertTextAtCursor?.(`${label} `);
2127
+ this.showStatus(`Attached clipboard image ${label} (${mimeType})`);
2120
2128
  this.ui.requestRender();
2121
2129
  }
2122
- catch {
2123
- // Silently ignore clipboard errors (may not have permission, etc.)
2130
+ catch (error) {
2131
+ const message = error instanceof Error ? error.message : String(error);
2132
+ this.showWarning(`Failed to paste image: ${message}`);
2124
2133
  }
2125
2134
  }
2135
+ nextClipboardImageLabel() {
2136
+ if (this.pendingClipboardImages.length === 0) {
2137
+ this.clipboardImageCounter = 0;
2138
+ }
2139
+ this.clipboardImageCounter += 1;
2140
+ return `[Image #${this.clipboardImageCounter}]`;
2141
+ }
2142
+ takeClipboardImagesForText(text) {
2143
+ if (this.pendingClipboardImages.length === 0) {
2144
+ return undefined;
2145
+ }
2146
+ const images = this.pendingClipboardImages
2147
+ .filter((image) => text.includes(image.label))
2148
+ .map((image) => image.content);
2149
+ this.pendingClipboardImages = [];
2150
+ this.clipboardImageCounter = 0;
2151
+ return images.length > 0 ? images : undefined;
2152
+ }
2153
+ buildUserInputSubmission(text) {
2154
+ const images = this.takeClipboardImagesForText(text);
2155
+ return images ? { text, images } : { text };
2156
+ }
2126
2157
  setupEditorSubmitHandler() {
2127
2158
  this.defaultEditor.onSubmit = async (text) => {
2128
2159
  text = text.trim();
@@ -2286,16 +2317,18 @@ export class InteractiveMode {
2286
2317
  await this.session.prompt(text);
2287
2318
  }
2288
2319
  else {
2289
- this.queueCompactionMessage(text, "steer");
2320
+ const images = this.takeClipboardImagesForText(text);
2321
+ this.queueCompactionMessage(text, "steer", images);
2290
2322
  }
2291
2323
  return;
2292
2324
  }
2293
2325
  // If streaming, use prompt() with steer behavior
2294
2326
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
2295
2327
  if (this.session.isStreaming) {
2328
+ const images = this.takeClipboardImagesForText(text);
2296
2329
  this.editor.addToHistory?.(text);
2297
2330
  this.editor.setText("");
2298
- await this.session.prompt(text, { streamingBehavior: "steer" });
2331
+ await this.session.prompt(text, { streamingBehavior: "steer", images });
2299
2332
  this.updatePendingMessagesDisplay();
2300
2333
  this.ui.requestRender();
2301
2334
  return;
@@ -2303,11 +2336,12 @@ export class InteractiveMode {
2303
2336
  // Normal message submission
2304
2337
  // First, move any pending bash components to chat
2305
2338
  this.flushPendingBashComponents();
2339
+ const submission = this.buildUserInputSubmission(text);
2306
2340
  if (this.onInputCallback) {
2307
- this.onInputCallback(text);
2341
+ this.onInputCallback(submission);
2308
2342
  }
2309
2343
  else {
2310
- this.pendingUserInputs.push(text);
2344
+ this.pendingUserInputs.push(submission);
2311
2345
  }
2312
2346
  this.editor.addToHistory?.(text);
2313
2347
  };
@@ -2776,9 +2810,9 @@ export class InteractiveMode {
2776
2810
  return queuedInput;
2777
2811
  }
2778
2812
  return new Promise((resolve) => {
2779
- this.onInputCallback = (text) => {
2813
+ this.onInputCallback = (submission) => {
2780
2814
  this.onInputCallback = undefined;
2781
- resolve(text);
2815
+ resolve(submission);
2782
2816
  };
2783
2817
  });
2784
2818
  }
@@ -2975,23 +3009,25 @@ export class InteractiveMode {
2975
3009
  await this.session.prompt(text);
2976
3010
  }
2977
3011
  else {
2978
- this.queueCompactionMessage(text, "followUp");
3012
+ const images = this.takeClipboardImagesForText(text);
3013
+ this.queueCompactionMessage(text, "followUp", images);
2979
3014
  }
2980
3015
  return;
2981
3016
  }
2982
3017
  // Alt+Enter queues a follow-up message (waits until agent finishes)
2983
3018
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
2984
3019
  if (this.session.isStreaming) {
3020
+ const images = this.takeClipboardImagesForText(text);
2985
3021
  this.editor.addToHistory?.(text);
2986
3022
  this.editor.setText("");
2987
- await this.session.prompt(text, { streamingBehavior: "followUp" });
3023
+ await this.session.prompt(text, { streamingBehavior: "followUp", images });
2988
3024
  this.updatePendingMessagesDisplay();
2989
3025
  this.ui.requestRender();
2990
3026
  }
2991
3027
  // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
2992
3028
  else if (this.editor.onSubmit) {
3029
+ await this.editor.onSubmit(text);
2993
3030
  this.editor.setText("");
2994
- this.editor.onSubmit(text);
2995
3031
  }
2996
3032
  }
2997
3033
  handleDequeue() {
@@ -3246,8 +3282,8 @@ export class InteractiveMode {
3246
3282
  }
3247
3283
  return allQueued.length;
3248
3284
  }
3249
- queueCompactionMessage(text, mode) {
3250
- this.compactionQueuedMessages.push({ text, mode });
3285
+ queueCompactionMessage(text, mode, images) {
3286
+ this.compactionQueuedMessages.push({ text, mode, images });
3251
3287
  this.editor.addToHistory?.(text);
3252
3288
  this.editor.setText("");
3253
3289
  this.updatePendingMessagesDisplay();
@@ -3282,10 +3318,10 @@ export class InteractiveMode {
3282
3318
  await this.session.prompt(message.text);
3283
3319
  }
3284
3320
  else if (message.mode === "followUp") {
3285
- await this.session.followUp(message.text);
3321
+ await this.session.followUp(message.text, message.images);
3286
3322
  }
3287
3323
  else {
3288
- await this.session.steer(message.text);
3324
+ await this.session.steer(message.text, message.images);
3289
3325
  }
3290
3326
  }
3291
3327
  this.updatePendingMessagesDisplay();
@@ -3308,7 +3344,7 @@ export class InteractiveMode {
3308
3344
  await this.session.prompt(message.text);
3309
3345
  }
3310
3346
  // Send first prompt (starts streaming)
3311
- const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
3347
+ const promptPromise = this.session.prompt(firstPrompt.text, { images: firstPrompt.images }).catch((error) => {
3312
3348
  restoreQueue(error);
3313
3349
  });
3314
3350
  // Queue remaining messages
@@ -3317,10 +3353,10 @@ export class InteractiveMode {
3317
3353
  await this.session.prompt(message.text);
3318
3354
  }
3319
3355
  else if (message.mode === "followUp") {
3320
- await this.session.followUp(message.text);
3356
+ await this.session.followUp(message.text, message.images);
3321
3357
  }
3322
3358
  else {
3323
- await this.session.steer(message.text);
3359
+ await this.session.steer(message.text, message.images);
3324
3360
  }
3325
3361
  }
3326
3362
  this.updatePendingMessagesDisplay();
@@ -3684,6 +3720,9 @@ export class InteractiveMode {
3684
3720
  const promptPath = path.join(dir, `${runId}.prompt.md`);
3685
3721
  const outFd = fs.openSync(logPath, "a");
3686
3722
  const kind = options.promptKind ?? "auto";
3723
+ const sessionDir = path.join(dir, "sessions");
3724
+ const sessionId = `auto-learn-${kind}-${runId}`;
3725
+ fs.mkdirSync(sessionDir, { recursive: true });
3687
3726
  const prompt = this.buildAutoLearnPrompt(reason, settings, {
3688
3727
  kind,
3689
3728
  turnDigest: options.turnDigest,
@@ -3696,6 +3735,10 @@ export class InteractiveMode {
3696
3735
  `Auto Learn ${runId}`,
3697
3736
  "--model",
3698
3737
  modelPattern,
3738
+ "--session-dir",
3739
+ sessionDir,
3740
+ "--session-id",
3741
+ sessionId,
3699
3742
  prompt,
3700
3743
  ];
3701
3744
  const child = spawn(spawnTarget.command, args, {
@@ -3728,6 +3771,8 @@ export class InteractiveMode {
3728
3771
  expiresAt: now + settings.leaseMinutes * 60 * 1000,
3729
3772
  cwd: this.sessionManager.getCwd(),
3730
3773
  logPath,
3774
+ sessionDir,
3775
+ sessionId,
3731
3776
  promptPath,
3732
3777
  kind,
3733
3778
  autonomyMode: this.settingsManager.getAutonomySettings().mode,
@@ -3914,13 +3959,30 @@ export class InteractiveMode {
3914
3959
  const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
3915
3960
  const runLines = runs.length
3916
3961
  ? runs
3917
- .map(([id, run]) => `- ${id}: ${run.model}, kind=${run.kind ?? "auto"}, authority=${run.authority ?? "unknown"}, pid=${run.pid ?? "?"}, log=${run.logPath}`)
3962
+ .map(([id, run]) => {
3963
+ const session = [
3964
+ run.sessionId ? `session=${run.sessionId}` : "",
3965
+ run.sessionDir ? `sessionDir=${run.sessionDir}` : "",
3966
+ ]
3967
+ .filter(Boolean)
3968
+ .join(", ");
3969
+ const sessionText = session ? `, ${session}` : "";
3970
+ return `- ${id}: ${run.model}, kind=${run.kind ?? "auto"}, authority=${run.authority ?? "unknown"}, pid=${run.pid ?? "?"}${sessionText}, log=${run.logPath}`;
3971
+ })
3918
3972
  .join("\n")
3919
3973
  : "- none";
3974
+ const reloadBlockers = getPendingReloadBlockers({
3975
+ ownPid: process.pid,
3976
+ ownSessionId: this.sessionManager.getSessionId(),
3977
+ ownSessionFile: this.sessionManager.getSessionFile(),
3978
+ });
3979
+ const reloadBlockerLines = reloadBlockers.pending
3980
+ ? reloadBlockers.descriptions.map((description) => `- ${description}`).join("\n")
3981
+ : "- none";
3920
3982
  const reflectionLast = state.lastReflectionByTenant?.[this.getAutoLearnTenantKey()] ?? 0;
3921
3983
  const reflectionCooldownRemainingMs = Math.max(0, reflectionLast + settings.reflectionCooldownMinutes * 60 * 1000 - Date.now());
3922
3984
  const reflectionCooldownText = reflectionCooldownRemainingMs > 0 ? `${Math.ceil(reflectionCooldownRemainingMs / 60000)}m remaining` : "ready";
3923
- return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nReflection review: ${settings.reflectionReview ? "enabled" : "disabled"} (tool trigger ${settings.reflectionMinToolCalls}, cooldown ${reflectionCooldownText})\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nRuns:\n${runLines}`;
3985
+ return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nReflection review: ${settings.reflectionReview ? "enabled" : "disabled"} (tool trigger ${settings.reflectionMinToolCalls}, cooldown ${reflectionCooldownText})\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nPi auto-reload blockers: ${reloadBlockers.pending ? reloadBlockers.reason : "none"}\n${reloadBlockerLines}\nRuns:\n${runLines}`;
3924
3986
  }
3925
3987
  formatAutonomyStatus() {
3926
3988
  const autonomy = this.settingsManager.getAutonomySettings();