pi-qq 0.1.8 → 0.1.10

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 (3) hide show
  1. package/README.md +25 -1
  2. package/package.json +2 -1
  3. package/qq.ts +23 -18
package/README.md CHANGED
@@ -1,9 +1,33 @@
1
1
  # pi-qq
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/pi-qq.svg)](https://www.npmjs.com/package/pi-qq)
4
+ [![npm downloads](https://img.shields.io/npm/dm/pi-qq.svg)](https://www.npmjs.com/package/pi-qq)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+
3
7
  Ask quick side questions about your current [pi](https://pi.dev) session without polluting the main transcript.
4
8
 
9
+ ```text
10
+ You: refactor this auth flow…
11
+ agent: [making changes]
12
+ You (alt+q): is there a reason we're not using the existing AuthClient?
13
+ ↳ overlay: Yes — AuthClient does X, but this path needs Y because…
14
+ You: [keeps editing, transcript untouched]
15
+ ```
16
+
5
17
  `pi-qq` adds `/qq <question>` plus an **alt+q** / **Option+Q** shortcut that toggles `/qq ` in the editor. Answers appear in a dismissible bottom overlay, can be reopened from in-memory `/qq-history`, and never enter the main conversation.
6
18
 
19
+ ## Try these first
20
+
21
+ ```text
22
+ /qq is this safe to merge?
23
+ /qq why are we doing it this way and not X?
24
+ /qq summarize what's happened so far
25
+ /qq what's the risk in this plan?
26
+ /qq what files have we touched this session?
27
+ /qq --recent did the last tool call succeed?
28
+ /qq --full what decisions have we made so far?
29
+ ```
30
+
7
31
  ## Why try it?
8
32
 
9
33
  - **A real side channel:** ask `/qq why are we changing this file?` while the main agent keeps working. The answer shows in a bottom overlay and does not enter the main transcript.
@@ -74,7 +98,7 @@ Press **alt+q** / **Option+Q** to toggle `/qq ` at the front of the editor:
74
98
  - The main transcript is never polluted by `/qq` questions or answers.
75
99
  - The side call receives read-only main-session context.
76
100
  - Recent mode sends only the latest messages for speed; full mode sends broader but still bounded context, not unlimited history.
77
- - Large text parts are clipped, thinking blocks are removed, and images are omitted from the side-call context for speed.
101
+ - Large text parts are clipped; images, tool calls, and tool results are converted into plain-text background so the side call never uses provider tool protocol.
78
102
  - The side call has no tools.
79
103
  - Recent `/qq` answers are kept in memory only so `/qq-history` can reopen them after dismissal.
80
104
  - `/qq-history` is view-only; it is not used as context for future `/qq` model calls.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "pi-qq",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Ask transcript-safe, context-aware side questions in Pi with /qq or alt+q, then reopen recent answers with /qq-history.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension",
8
+ "pi-qq",
8
9
  "quick-question",
9
10
  "side-question",
10
11
  "context-aware",
package/qq.ts CHANGED
@@ -145,33 +145,38 @@ function clipText(text: string): string {
145
145
  return `${text.slice(0, MAX_TEXT_CHARS_PER_PART)}\n[…truncated for /qq speed…]`;
146
146
  }
147
147
 
148
- function trimContentArray<T extends Array<{ type: string }>>(content: T): T {
149
- return content
150
- .filter((part) => part.type !== "thinking")
151
- .map((part) => {
152
- if (part.type === "text" && "text" in part && typeof part.text === "string") {
153
- return { ...part, text: clipText(part.text) };
154
- }
155
- if (part.type === "image") {
156
- return { type: "text", text: "[image omitted for /qq speed]" };
157
- }
158
- return part;
159
- }) as T;
148
+ function contentPartsToText(content: Array<{ type: string }>): string {
149
+ const parts: string[] = [];
150
+ for (const part of content) {
151
+ if (part.type === "text" && "text" in part && typeof part.text === "string") {
152
+ parts.push(clipText(part.text));
153
+ } else if (part.type === "image") {
154
+ parts.push("[image omitted for /qq speed]");
155
+ } else if (part.type === "toolCall" && "name" in part && typeof part.name === "string") {
156
+ parts.push(`[assistant requested tool: ${part.name}]`);
157
+ }
158
+ }
159
+ return parts.join("\n").trim();
160
160
  }
161
161
 
162
- function trimUserContent(content: UserMessage["content"]): UserMessage["content"] {
163
- if (typeof content === "string") return clipText(content);
164
- return trimContentArray(content);
162
+ function userContentToText(content: UserMessage["content"]): string {
163
+ return typeof content === "string" ? clipText(content) : contentPartsToText(content);
165
164
  }
166
165
 
167
166
  function trimMessageForContext(message: Message): Message {
168
167
  if (message.role === "assistant") {
169
- return { ...message, content: trimContentArray(message.content) };
168
+ const text = contentPartsToText(message.content) || "[assistant message omitted for /qq speed]";
169
+ return { ...message, content: [{ type: "text", text }] };
170
170
  }
171
171
  if (message.role === "user") {
172
- return { ...message, content: trimUserContent(message.content) };
172
+ return { ...message, content: [{ type: "text", text: userContentToText(message.content) }] };
173
173
  }
174
- return { ...message, content: trimContentArray(message.content) };
174
+ const text = contentPartsToText(message.content) || "[tool result omitted for /qq speed]";
175
+ return {
176
+ role: "user",
177
+ content: [{ type: "text", text: `[tool result: ${message.toolName}]\n${text}` }],
178
+ timestamp: message.timestamp,
179
+ };
175
180
  }
176
181
 
177
182
  function selectRecentContextMessages(messages: Message[]): Message[] {