pi-cc-extensions 0.8.34 → 0.8.36
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/extensions/compact-thinking.ts +128 -71
- package/extensions/fixed-editor.ts +10 -2
- package/package.json +1 -1
|
@@ -23,37 +23,24 @@ type CompactThinkingOwner = {
|
|
|
23
23
|
stop(event?: any, ctx?: any): void;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
type UpstreamHandler = (event: any, ctx: any) => void;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
pi: ExtensionAPI,
|
|
30
|
-
initialConfig: CompactThinkingConfig,
|
|
31
|
-
): CompactThinkingController {
|
|
32
|
-
const owner = {};
|
|
33
|
-
const host = globalThis as typeof globalThis & {
|
|
34
|
-
[COMPACT_THINKING_OWNER]?: CompactThinkingOwner;
|
|
35
|
-
};
|
|
36
|
-
host[COMPACT_THINKING_OWNER]?.stop();
|
|
37
|
-
|
|
38
|
-
const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
39
|
-
// Upstream requires this file while loading; mirror ccstyle into it and never read it back.
|
|
40
|
-
mkdirSync(agentDir, { recursive: true });
|
|
41
|
-
writeFileSync(
|
|
42
|
-
join(agentDir, "compact-thinking.json"),
|
|
43
|
-
`${JSON.stringify(initialConfig, null, 2)}\n`,
|
|
44
|
-
"utf8",
|
|
45
|
-
);
|
|
28
|
+
const COMPACT_THINKING_OWNER = Symbol.for("pi.ccstyle.compact-thinking-owner");
|
|
46
29
|
|
|
30
|
+
function loadPatchedUpstream(): {
|
|
31
|
+
config: CompactThinkingConfig;
|
|
32
|
+
compactThinking: (api: ExtensionAPI) => void;
|
|
33
|
+
} {
|
|
47
34
|
// Fork the upstream entry so a running subagent tool keeps the thinking
|
|
48
35
|
// loading animation until the model emits the next text/thinking boundary.
|
|
49
|
-
// Upstream finalizes on tool_execution_start, freezing the
|
|
50
|
-
// static "Thought for Xs" for the whole subagent run.
|
|
36
|
+
// Upstream finalizes on tool_execution_start / message_end, freezing the
|
|
37
|
+
// summary into a static "Thought for Xs" for the whole subagent run.
|
|
51
38
|
const upstreamRequire = createRequire(import.meta.url);
|
|
52
39
|
const upstreamIndexPath = upstreamRequire.resolve("pi-compact-thinking/index.ts");
|
|
53
40
|
const toolStartOriginal = ` pi.on("tool_execution_start", finishThinking);`;
|
|
54
41
|
const toolStartPatched = ` // Subagent tools can run for minutes: keep the thinking loading animation
|
|
55
|
-
// alive for the whole execution and only finalize once the
|
|
56
|
-
// next text/thinking boundary
|
|
42
|
+
// alive for the whole execution and only finalize once the tool ends or the
|
|
43
|
+
// model emits the next text/thinking boundary.
|
|
57
44
|
function resumeAgentThinking(message: AssistantMessage | undefined) {
|
|
58
45
|
if (activeThinking) return;
|
|
59
46
|
const content = message?.content;
|
|
@@ -62,12 +49,27 @@ export function installCompactThinking(
|
|
|
62
49
|
if (index < 0) return;
|
|
63
50
|
startThinking(message, index);
|
|
64
51
|
}
|
|
52
|
+
function messageHasAgentTool(message: AssistantMessage | undefined) {
|
|
53
|
+
return (
|
|
54
|
+
Array.isArray(message?.content) &&
|
|
55
|
+
message.content.some(
|
|
56
|
+
(content) =>
|
|
57
|
+
content?.type === "toolCall" &&
|
|
58
|
+
(content.name === "Agent" ||
|
|
59
|
+
content.name === "Agents" ||
|
|
60
|
+
content.args?.subagent_type != null),
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
65
64
|
pi.on("tool_execution_start", (event: any) => {
|
|
66
65
|
if (event.toolName === "Agent" || event.toolName === "Agents") {
|
|
67
66
|
resumeAgentThinking(latestComponent?.lastMessage ?? undefined);
|
|
68
67
|
} else {
|
|
69
68
|
finishThinking();
|
|
70
69
|
}
|
|
70
|
+
});
|
|
71
|
+
pi.on("tool_execution_end", (event: any) => {
|
|
72
|
+
if (event.toolName === "Agent" || event.toolName === "Agents") finishThinking();
|
|
71
73
|
});`;
|
|
72
74
|
const updateBoundaryOriginal = ` } else if (
|
|
73
75
|
update.type === "text_start" ||
|
|
@@ -82,33 +84,33 @@ export function installCompactThinking(
|
|
|
82
84
|
update.type === "toolcall_start" ||
|
|
83
85
|
update.type === "toolcall_delta"
|
|
84
86
|
) {
|
|
85
|
-
if (
|
|
86
|
-
Array.isArray(event.message?.content) &&
|
|
87
|
-
event.message.content.some(
|
|
88
|
-
(content) =>
|
|
89
|
-
content?.type === "toolCall" &&
|
|
90
|
-
(content.name === "Agent" ||
|
|
91
|
-
content.name === "Agents" ||
|
|
92
|
-
content.args?.subagent_type != null),
|
|
93
|
-
)
|
|
94
|
-
) {
|
|
87
|
+
if (messageHasAgentTool(event.message)) {
|
|
95
88
|
resumeAgentThinking(event.message);
|
|
96
89
|
} else {
|
|
97
90
|
finishThinking();
|
|
98
91
|
}
|
|
99
92
|
}`;
|
|
93
|
+
const messageEndOriginal = ` pi.on("message_end", (event) => {
|
|
94
|
+
if (event.message.role === "assistant") finishThinking();
|
|
95
|
+
});`;
|
|
96
|
+
const messageEndPatched = ` pi.on("message_end", (event) => {
|
|
97
|
+
if (event.message.role !== "assistant") return;
|
|
98
|
+
// Agent tool runs after message_end; keep the ticker until tool_execution_end
|
|
99
|
+
// or the next text/thinking boundary.
|
|
100
|
+
if (messageHasAgentTool(event.message)) return;
|
|
101
|
+
finishThinking();
|
|
102
|
+
});`;
|
|
103
|
+
|
|
100
104
|
let upstreamIndexSource = readFileSync(upstreamIndexPath, "utf8");
|
|
101
105
|
if (
|
|
102
106
|
upstreamIndexSource.includes(toolStartOriginal) &&
|
|
103
|
-
upstreamIndexSource.includes(updateBoundaryOriginal)
|
|
107
|
+
upstreamIndexSource.includes(updateBoundaryOriginal) &&
|
|
108
|
+
upstreamIndexSource.includes(messageEndOriginal)
|
|
104
109
|
) {
|
|
105
110
|
upstreamIndexSource = upstreamIndexSource
|
|
106
111
|
.replace(toolStartOriginal, toolStartPatched)
|
|
107
|
-
.replace(updateBoundaryOriginal, updateBoundaryPatched)
|
|
108
|
-
|
|
109
|
-
// Upstream changed shape: fall back to the untouched entry so the loader
|
|
110
|
-
// keeps working; only the subagent animation nicety is lost.
|
|
111
|
-
upstreamIndexSource = readFileSync(upstreamIndexPath, "utf8");
|
|
112
|
+
.replace(updateBoundaryOriginal, updateBoundaryPatched)
|
|
113
|
+
.replace(messageEndOriginal, messageEndPatched);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
// Reuse Pi's live modules so the upstream prototype patch reaches runtime components.
|
|
@@ -127,12 +129,34 @@ export function installCompactThinking(
|
|
|
127
129
|
default: (api: ExtensionAPI) => void;
|
|
128
130
|
}
|
|
129
131
|
).default;
|
|
130
|
-
|
|
132
|
+
return { config: upstreamConfig, compactThinking };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function installCompactThinking(
|
|
136
|
+
pi: ExtensionAPI,
|
|
137
|
+
initialConfig: CompactThinkingConfig,
|
|
138
|
+
): CompactThinkingController {
|
|
139
|
+
const owner = {};
|
|
140
|
+
const host = globalThis as typeof globalThis & {
|
|
141
|
+
[COMPACT_THINKING_OWNER]?: CompactThinkingOwner;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
145
|
+
// Upstream requires this file while loading; mirror ccstyle into it and never read it back.
|
|
146
|
+
mkdirSync(agentDir, { recursive: true });
|
|
147
|
+
writeFileSync(
|
|
148
|
+
join(agentDir, "compact-thinking.json"),
|
|
149
|
+
`${JSON.stringify(initialConfig, null, 2)}\n`,
|
|
150
|
+
"utf8",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
let upstreamConfig: CompactThinkingConfig | undefined;
|
|
154
|
+
let session: { event: any; ctx: any } | undefined;
|
|
155
|
+
let active = false;
|
|
156
|
+
// Stable pi.on wrappers delegate here so activate/reload never double-binds.
|
|
157
|
+
const delegates = new Map<string, UpstreamHandler>();
|
|
158
|
+
const boundEvents = new Set<string>();
|
|
131
159
|
|
|
132
|
-
// Upstream restores completedDurations from sessionManager.getBranch() (the
|
|
133
|
-
// current leaf path). After compaction or a branch switch, duration entries
|
|
134
|
-
// of older messages leave that path, so scrolling back renders a bare
|
|
135
|
-
// "Thinking..." instead of "Thought for Xs". Restore from every entry.
|
|
136
160
|
const restoreAllDurations = (ctx: any): any => {
|
|
137
161
|
const sessionManager = ctx?.sessionManager;
|
|
138
162
|
if (!sessionManager || typeof sessionManager.getEntries !== "function") return ctx;
|
|
@@ -145,40 +169,73 @@ export function installCompactThinking(
|
|
|
145
169
|
};
|
|
146
170
|
};
|
|
147
171
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
const bind = (eventName: string) => {
|
|
173
|
+
if (boundEvents.has(eventName)) return;
|
|
174
|
+
boundEvents.add(eventName);
|
|
175
|
+
pi.on(eventName as any, (e: any, ctx: any) => {
|
|
176
|
+
if (!active) return;
|
|
177
|
+
const handler = delegates.get(eventName);
|
|
178
|
+
if (!handler) return;
|
|
179
|
+
handler(e, eventName === "session_tree" ? restoreAllDurations(ctx) : ctx);
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
151
183
|
const stop = (event?: any, ctx?: any) => {
|
|
152
|
-
if (
|
|
153
|
-
|
|
184
|
+
if (!active) return;
|
|
185
|
+
active = false;
|
|
186
|
+
const shutdown = delegates.get("session_shutdown");
|
|
187
|
+
delegates.clear();
|
|
154
188
|
shutdown?.(event ?? session?.event ?? {}, ctx ?? session?.ctx ?? { mode: "rpc", ui: {} });
|
|
155
189
|
if (host[COMPACT_THINKING_OWNER]?.owner === owner) delete host[COMPACT_THINKING_OWNER];
|
|
156
190
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
191
|
+
|
|
192
|
+
const activate = (event: any, ctx: any) => {
|
|
193
|
+
// Headless subagent runtimes share this process. Never steal the parent
|
|
194
|
+
// TUI prototype patch or kill its thinking ticker.
|
|
195
|
+
if (ctx?.mode !== "tui") return;
|
|
196
|
+
|
|
197
|
+
host[COMPACT_THINKING_OWNER]?.stop(event, ctx);
|
|
198
|
+
session = { event, ctx };
|
|
199
|
+
|
|
200
|
+
const loaded = loadPatchedUpstream();
|
|
201
|
+
upstreamConfig = loaded.config;
|
|
202
|
+
Object.assign(upstreamConfig, initialConfig);
|
|
203
|
+
delegates.clear();
|
|
204
|
+
|
|
205
|
+
loaded.compactThinking({
|
|
206
|
+
on(eventName: string, handler: UpstreamHandler) {
|
|
207
|
+
if (eventName === "session_start") {
|
|
208
|
+
// Already inside session_start — run immediately.
|
|
209
|
+
handler(event, restoreAllDurations(ctx));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (eventName === "session_shutdown") {
|
|
213
|
+
delegates.set(eventName, handler);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
delegates.set(eventName, handler);
|
|
217
|
+
bind(eventName);
|
|
218
|
+
},
|
|
219
|
+
appendEntry: (...args: any[]) => (pi.appendEntry as any)(...args),
|
|
220
|
+
} as unknown as ExtensionAPI);
|
|
221
|
+
|
|
222
|
+
active = true;
|
|
223
|
+
host[COMPACT_THINKING_OWNER] = { owner, stop };
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
pi.on("session_start", (event, ctx) => {
|
|
227
|
+
session = { event, ctx };
|
|
228
|
+
activate(event, ctx);
|
|
229
|
+
});
|
|
230
|
+
pi.on("session_shutdown", (event, ctx) => {
|
|
231
|
+
if (host[COMPACT_THINKING_OWNER]?.owner === owner) stop(event, ctx);
|
|
232
|
+
session = undefined;
|
|
233
|
+
});
|
|
178
234
|
|
|
179
235
|
return {
|
|
180
236
|
updateConfig(next) {
|
|
181
|
-
Object.assign(
|
|
237
|
+
Object.assign(initialConfig, next);
|
|
238
|
+
if (upstreamConfig) Object.assign(upstreamConfig, next);
|
|
182
239
|
},
|
|
183
240
|
};
|
|
184
241
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import * as piTui from "@earendil-works/pi-tui";
|
|
2
4
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
3
5
|
import { createJiti } from "jiti";
|
|
4
6
|
|
|
5
7
|
type LifecycleHandler = (event: any, ctx: any) => void;
|
|
6
8
|
type FixedEditorExtension = (pi: ExtensionAPI) => void;
|
|
7
9
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
+
// Upstream publishes TS only. Nested jiti must reuse Pi's live modules — peer
|
|
11
|
+
// deps are not resolvable from ~/.pi/agent/npm/node_modules/pi-cc-extensions.
|
|
12
|
+
const jiti = createJiti(import.meta.url, {
|
|
13
|
+
virtualModules: {
|
|
14
|
+
"@earendil-works/pi-coding-agent": piCodingAgent,
|
|
15
|
+
"@earendil-works/pi-tui": piTui,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
10
18
|
// Match pi-fixed-editor's own `.js` import specifier; Jiti caches `.ts` separately.
|
|
11
19
|
const terminalSplit = jiti("@tifan/pi-fixed-editor/src/terminal-split.js") as {
|
|
12
20
|
TerminalSplitCompositor?: { prototype: Record<PropertyKey, any> };
|
package/package.json
CHANGED