@saccolabs/pi-claude-cli 0.4.5 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saccolabs/pi-claude-cli",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Pi coding agent extension that routes LLM calls through the Claude Code CLI",
5
5
  "main": "index.ts",
6
6
  "keywords": [
@@ -140,15 +140,22 @@ function buildCustomToolResultPrompt(messages: any[]): string | null {
140
140
  /**
141
141
  * Build a prompt for a resumed session.
142
142
  *
143
- * When resuming via --resume, the CLI already has the full conversation history.
144
- * We only need to send the new content since the last turn: the last assistant
145
- * response's tool results (if any) followed by the latest user message.
143
+ * When resuming via --resume, the CLI already has the full conversation history
144
+ * up through the most recent assistant turn it produced. We only need to send
145
+ * the delta since that turn: trailing tool results for the last assistant
146
+ * tool_use, and/or a new user message.
146
147
  *
147
- * For tool_use flows: pi sends [user, assistant(toolCall), toolResult, ...]
148
- * We need to include tool results so the resumed session sees them, plus the
149
- * final user message.
148
+ * Why anchor on the last assistant message rather than the last user message?
149
+ * Pi's tool loop appends [user, assistant(toolUse), toolResult,
150
+ * assistant(toolUse), toolResult, ...] — the only `user` entry stays at index 0
151
+ * across many provider invocations. Anchoring there made every iteration replay
152
+ * the entire transcript, and when that first user message carried an image the
153
+ * image branch below returned early with just the image, so tool results never
154
+ * reached the model at all. The model then re-issued the same tool call
155
+ * indefinitely while each turn re-billed the whole accumulated context.
150
156
  *
151
- * Falls back to full prompt if the message structure is unexpected.
157
+ * Returns "" when there's nothing new to send (e.g. only an assistant message
158
+ * exists in the context — can happen mid-shutdown).
152
159
  */
153
160
  export function buildResumePrompt(context: {
154
161
  messages: any[];
@@ -156,28 +163,16 @@ export function buildResumePrompt(context: {
156
163
  const messages = context.messages;
157
164
  if (messages.length === 0) return "";
158
165
 
159
- // Find the last user message
160
- const finalUserIndex = findFinalUserMessageIndex(messages);
161
- if (finalUserIndex < 0) return "";
162
-
163
- // Collect new messages: everything from the last assistant turn onwards
164
- // (tool results from the last assistant + the new user message)
165
- const newMessages: any[] = [];
166
-
167
- // Walk backwards from finalUserIndex to find where new content starts.
168
- // Include trailing toolResult messages that follow the last assistant turn.
169
- let startIdx = finalUserIndex;
170
- for (let i = finalUserIndex - 1; i >= 0; i--) {
171
- if (messages[i].role === "toolResult") {
172
- startIdx = i;
173
- } else {
166
+ let lastAssistantIdx = -1;
167
+ for (let i = messages.length - 1; i >= 0; i--) {
168
+ if (messages[i].role === "assistant") {
169
+ lastAssistantIdx = i;
174
170
  break;
175
171
  }
176
172
  }
177
173
 
178
- for (let i = startIdx; i < messages.length; i++) {
179
- newMessages.push(messages[i]);
180
- }
174
+ const newMessages = messages.slice(lastAssistantIdx + 1);
175
+ if (newMessages.length === 0) return "";
181
176
 
182
177
  // If there are only tool results + one user message, build a combined prompt
183
178
  const parts: string[] = [];