@quandev104/pi-style 0.2.0 → 0.2.1
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/CHANGELOG.md +6 -0
- package/dist/extensions/pi-style.js +691 -296
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/index.ts +3 -2
- package/extension-src/pi-style/app/runtime.ts +72 -84
- package/extension-src/pi-style/app/snapshot.ts +41 -2
- package/extension-src/pi-style/features/editor/index.ts +80 -61
- package/extension-src/pi-style/features/messages/index.ts +194 -56
- package/extension-src/pi-style/features/tools/bash-execution.ts +12 -1
- package/extension-src/pi-style/features/tools/boxed/bash.ts +99 -31
- package/extension-src/pi-style/features/tools/boxed/batch.ts +44 -10
- package/extension-src/pi-style/features/tools/boxed/edit.ts +30 -15
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +31 -15
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -14
- package/extension-src/pi-style/features/tools/boxed/shared.ts +25 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +1 -1
- package/extension-src/pi-style/pi/index.ts +26 -13
- package/extension-src/pi-style/pi/session-usage.ts +204 -21
- package/extension-src/pi-style/shared/box.ts +13 -2
- package/package.json +1 -1
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
// consume no vertical space.
|
|
9
9
|
//
|
|
10
10
|
// Design notes:
|
|
11
|
-
// -
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// - Live batches render directly from the registry so member completions (which
|
|
12
|
+
// trigger ui.requestRender via Pi's tool_execution_end handler) are picked up
|
|
13
|
+
// without cross-component invalidation plumbing. Once a batch is finalized,
|
|
14
|
+
// width/config-stable renders reuse the cached line array.
|
|
15
15
|
// - Batch boundaries: a new batch starts when the active batch is closed. The
|
|
16
16
|
// active batch closes when a non-batchable tool call is dispatched
|
|
17
17
|
// (boxed/index.ts), when a new message starts (pi/index.ts), and on session
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import type { Component } from "@earendil-works/pi-tui";
|
|
33
33
|
import { stripAnsi } from "../../../shared/ansi.js";
|
|
34
|
-
import { type BoxTheme, dimLine, formatToolTitlePrefix } from "../../../shared/box.js";
|
|
34
|
+
import { type BoxTheme, dimLine, formatToolTitlePrefix, themeCacheKey } from "../../../shared/box.js";
|
|
35
35
|
import { safeTruncateToWidth } from "../../../shared/render-budget.js";
|
|
36
36
|
import {
|
|
37
37
|
fileIcon,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
TREE_CHILD_INDENT,
|
|
43
43
|
TREE_INDENT,
|
|
44
44
|
} from "./output-tree.js";
|
|
45
|
-
import { getToolsRenderConfig } from "./session-config.js";
|
|
45
|
+
import { getToolsRenderCacheSignature, getToolsRenderConfig } from "./session-config.js";
|
|
46
46
|
import type { BoxedToolContext } from "./shared.js";
|
|
47
47
|
|
|
48
48
|
/** Quiet tools whose calls group into a single batch panel. */
|
|
@@ -78,6 +78,11 @@ export interface BatchMember {
|
|
|
78
78
|
outputEntries?: string[];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
type BatchRenderCache = {
|
|
82
|
+
key: string;
|
|
83
|
+
lines: string[];
|
|
84
|
+
};
|
|
85
|
+
|
|
81
86
|
export interface BatchState {
|
|
82
87
|
readonly meta: BatchToolMeta;
|
|
83
88
|
readonly leaderId: string;
|
|
@@ -85,6 +90,8 @@ export interface BatchState {
|
|
|
85
90
|
completedAt?: number;
|
|
86
91
|
closed: boolean;
|
|
87
92
|
readonly members: BatchMember[];
|
|
93
|
+
revision: number;
|
|
94
|
+
renderCache?: BatchRenderCache;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
/** Tree head limit: only the first few members are listed, the rest collapse. */
|
|
@@ -130,6 +137,7 @@ function createBatch(
|
|
|
130
137
|
leaderId,
|
|
131
138
|
startedAt: performance.now(),
|
|
132
139
|
closed: false,
|
|
140
|
+
revision: 0,
|
|
133
141
|
members: [
|
|
134
142
|
{
|
|
135
143
|
toolCallId: leaderId,
|
|
@@ -146,6 +154,11 @@ function createBatch(
|
|
|
146
154
|
return batch;
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
function bumpBatchRevision(batch: BatchState): void {
|
|
158
|
+
batch.revision++;
|
|
159
|
+
delete batch.renderCache;
|
|
160
|
+
}
|
|
161
|
+
|
|
149
162
|
/**
|
|
150
163
|
* Register a call renderer invocation. Idempotent per toolCallId: re-fires
|
|
151
164
|
* (updateDisplay on the same component) reuse the call's existing batch, even
|
|
@@ -161,9 +174,12 @@ export function registerBatchCall(
|
|
|
161
174
|
if (existing) {
|
|
162
175
|
const member = existing.members.find((entry) => entry.toolCallId === context.toolCallId);
|
|
163
176
|
if (member) {
|
|
177
|
+
const changed =
|
|
178
|
+
member.detail !== detail || member.pattern !== opts.pattern || member.pathLabel !== opts.pathLabel;
|
|
164
179
|
member.detail = detail;
|
|
165
180
|
if (opts.pattern !== undefined) member.pattern = opts.pattern;
|
|
166
181
|
if (opts.pathLabel !== undefined) member.pathLabel = opts.pathLabel;
|
|
182
|
+
if (changed) bumpBatchRevision(existing);
|
|
167
183
|
}
|
|
168
184
|
return { batch: existing, isLeader: existing.leaderId === context.toolCallId };
|
|
169
185
|
}
|
|
@@ -182,6 +198,7 @@ export function registerBatchCall(
|
|
|
182
198
|
};
|
|
183
199
|
current.members.push(member);
|
|
184
200
|
batchByCallId.set(context.toolCallId, current);
|
|
201
|
+
bumpBatchRevision(current);
|
|
185
202
|
return { batch: current, isLeader: false };
|
|
186
203
|
}
|
|
187
204
|
|
|
@@ -208,14 +225,23 @@ export function registerBatchResult(
|
|
|
208
225
|
if (!batch || batch.meta.toolName !== meta.toolName) return { batch: undefined, isLeader: false };
|
|
209
226
|
const member = batch.members.find((entry) => entry.toolCallId === context.toolCallId);
|
|
210
227
|
if (member) {
|
|
211
|
-
|
|
212
|
-
|
|
228
|
+
const nextStatus = data.isPartial ? "running" : "done";
|
|
229
|
+
const nextIsError = !data.isPartial && data.isError;
|
|
230
|
+
const changed =
|
|
231
|
+
member.status !== nextStatus ||
|
|
232
|
+
member.isError !== nextIsError ||
|
|
233
|
+
member.errorText !== (nextIsError ? data.errorText : undefined) ||
|
|
234
|
+
member.outputEntries !== data.entries;
|
|
235
|
+
member.status = nextStatus;
|
|
236
|
+
member.isError = nextIsError;
|
|
213
237
|
if (member.isError && data.errorText !== undefined) member.errorText = data.errorText;
|
|
214
238
|
else delete member.errorText;
|
|
215
239
|
if (data.entries !== undefined) member.outputEntries = data.entries;
|
|
240
|
+
if (changed) bumpBatchRevision(batch);
|
|
216
241
|
}
|
|
217
242
|
if (batch.completedAt === undefined && batch.members.every((entry) => entry.status === "done")) {
|
|
218
243
|
batch.completedAt = performance.now();
|
|
244
|
+
bumpBatchRevision(batch);
|
|
219
245
|
}
|
|
220
246
|
return { batch, isLeader: batch.leaderId === context.toolCallId };
|
|
221
247
|
}
|
|
@@ -459,9 +485,17 @@ function renderBatchPanelLines(theme: BoxTheme, batch: BatchState, status: Batch
|
|
|
459
485
|
*/
|
|
460
486
|
export function renderBatchAwareCall(theme: BoxTheme, batch: BatchState): Component {
|
|
461
487
|
return {
|
|
462
|
-
invalidate() {
|
|
488
|
+
invalidate() {
|
|
489
|
+
delete batch.renderCache;
|
|
490
|
+
},
|
|
463
491
|
render(width: number): string[] {
|
|
464
|
-
|
|
492
|
+
const status = batchStatus(batch);
|
|
493
|
+
if (!status.allDone) return renderBatchPanelLines(theme, batch, status, width);
|
|
494
|
+
const cacheKey = [themeCacheKey(theme), getToolsRenderCacheSignature(), width, batch.revision].join("|");
|
|
495
|
+
if (batch.renderCache?.key === cacheKey) return batch.renderCache.lines;
|
|
496
|
+
const lines = renderBatchPanelLines(theme, batch, status, width);
|
|
497
|
+
batch.renderCache = { key: cacheKey, lines };
|
|
498
|
+
return lines;
|
|
465
499
|
},
|
|
466
500
|
};
|
|
467
501
|
}
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
type BoxedToolContext,
|
|
25
25
|
type BoxedToolDefinition,
|
|
26
26
|
displayPath,
|
|
27
|
+
getRenderCacheKey,
|
|
28
|
+
memoizedStateComponent,
|
|
27
29
|
noteBoxedCallState,
|
|
28
30
|
noteBoxedResultPhase,
|
|
29
31
|
noteExecutionStart,
|
|
@@ -133,21 +135,34 @@ export const editTool: BoxedToolDefinition = {
|
|
|
133
135
|
const diffView = new AdaptiveDiffComponent(theme, rows, maxRows, shouldHighlight ? language : undefined);
|
|
134
136
|
const expandHint = !expanded && diffView.hasCollapsed() ? "Ctrl+O more" : undefined;
|
|
135
137
|
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
return memoizedStateComponent(
|
|
139
|
+
context.state,
|
|
140
|
+
"__piStyleEditDiffResult",
|
|
141
|
+
getRenderCacheKey(
|
|
142
|
+
"edit-diff-result",
|
|
143
|
+
theme,
|
|
144
|
+
Boolean(expanded),
|
|
145
|
+
diff,
|
|
146
|
+
sourcePath ?? "",
|
|
147
|
+
editDiffFooter(theme, result, context, stats),
|
|
148
|
+
),
|
|
149
|
+
() =>
|
|
150
|
+
renderBoxedToolResult(
|
|
151
|
+
theme,
|
|
152
|
+
{
|
|
153
|
+
render(width: number): string[] {
|
|
154
|
+
return diffView.render(width);
|
|
155
|
+
},
|
|
156
|
+
invalidate(): void {
|
|
157
|
+
diffView.invalidate();
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
dividerLabel: diffDividerLabel(theme, stats),
|
|
162
|
+
...(expandHint ? { dividerRightLabel: expandHint } : {}),
|
|
163
|
+
footerLines: [editDiffFooter(theme, result, context, stats)],
|
|
164
|
+
},
|
|
165
|
+
),
|
|
151
166
|
);
|
|
152
167
|
},
|
|
153
168
|
};
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type BoxedToolContext,
|
|
17
17
|
type BoxedToolDefinition,
|
|
18
18
|
displayPath,
|
|
19
|
+
getRenderCacheKey,
|
|
20
|
+
memoizedStateComponent,
|
|
19
21
|
noteBoxedCallState,
|
|
20
22
|
noteBoxedResultPhase,
|
|
21
23
|
noteExecutionStart,
|
|
@@ -184,21 +186,35 @@ function renderQuickEditResult(
|
|
|
184
186
|
const diffView = new AdaptiveDiffComponent(theme, rows, maxRows, shouldHighlight ? language : undefined);
|
|
185
187
|
const expandHint = !expanded && diffView.hasCollapsed() ? "Ctrl+O more" : undefined;
|
|
186
188
|
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
189
|
+
return memoizedStateComponent(
|
|
190
|
+
context.state,
|
|
191
|
+
"__piStyleQuickEditDiffResult",
|
|
192
|
+
getRenderCacheKey(
|
|
193
|
+
"quick-edit-diff-result",
|
|
194
|
+
theme,
|
|
195
|
+
config.toolLabel,
|
|
196
|
+
Boolean(expanded),
|
|
197
|
+
diff,
|
|
198
|
+
argPath,
|
|
199
|
+
quickEditDiffFooter(theme, result, context, stats),
|
|
200
|
+
),
|
|
201
|
+
() =>
|
|
202
|
+
renderBoxedToolResult(
|
|
203
|
+
theme,
|
|
204
|
+
{
|
|
205
|
+
render(width: number): string[] {
|
|
206
|
+
return diffView.render(width);
|
|
207
|
+
},
|
|
208
|
+
invalidate(): void {
|
|
209
|
+
diffView.invalidate();
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
dividerLabel: quickEditDividerLabel(theme, stats),
|
|
214
|
+
...(expandHint ? { dividerRightLabel: expandHint } : {}),
|
|
215
|
+
footerLines: [quickEditDiffFooter(theme, result, context, stats)],
|
|
216
|
+
},
|
|
217
|
+
),
|
|
202
218
|
);
|
|
203
219
|
}
|
|
204
220
|
|
|
@@ -37,6 +37,19 @@ export function getToolsRenderConfig(): ToolsRenderConfig {
|
|
|
37
37
|
return sessionToolsConfig;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export function getToolsRenderCacheSignature(): string {
|
|
41
|
+
return [
|
|
42
|
+
sessionToolsConfig.maxCollapsedLines,
|
|
43
|
+
sessionToolsConfig.maxExpandedLines,
|
|
44
|
+
sessionToolsConfig.dimOutput ? 1 : 0,
|
|
45
|
+
sessionToolsConfig.showElapsed ? 1 : 0,
|
|
46
|
+
sessionToolsConfig.batchOpenGlyph,
|
|
47
|
+
sessionToolsConfig.nerdFonts ? 1 : 0,
|
|
48
|
+
sessionToolsConfig.collapseAfterTurn ? 1 : 0,
|
|
49
|
+
sessionToolsConfig.collapseMutatingTools ? 1 : 0,
|
|
50
|
+
].join("|");
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
// Wall-clock elapsed tracking through the renderer context state (no tool
|
|
41
54
|
// re-registration, so result.details has no execution timing).
|
|
42
55
|
//
|
|
@@ -83,8 +96,26 @@ export function markResultSeen(state: Record<string, unknown> | undefined): void
|
|
|
83
96
|
|
|
84
97
|
type TickerHandle = ReturnType<typeof setInterval>;
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
type ElapsedTickerEntry = {
|
|
100
|
+
invalidate: () => void;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** States currently subscribed to the shared elapsed-render ticker. */
|
|
104
|
+
const tickerEntries = new Map<Record<string, unknown>, ElapsedTickerEntry>();
|
|
105
|
+
let sharedTickerHandle: TickerHandle | undefined;
|
|
106
|
+
|
|
107
|
+
function ensureSharedTicker(): void {
|
|
108
|
+
if (sharedTickerHandle !== undefined || tickerEntries.size === 0) return;
|
|
109
|
+
sharedTickerHandle = setInterval(() => {
|
|
110
|
+
for (const { invalidate } of tickerEntries.values()) invalidate();
|
|
111
|
+
}, 1000) as unknown as TickerHandle;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stopSharedTickerIfIdle(): void {
|
|
115
|
+
if (sharedTickerHandle === undefined || tickerEntries.size > 0) return;
|
|
116
|
+
clearInterval(sharedTickerHandle);
|
|
117
|
+
sharedTickerHandle = undefined;
|
|
118
|
+
}
|
|
88
119
|
|
|
89
120
|
/**
|
|
90
121
|
* While a tool is running, re-render once per second so live elapsed labels
|
|
@@ -92,26 +123,26 @@ const tickerStates = new Set<Record<string, unknown>>();
|
|
|
92
123
|
*/
|
|
93
124
|
export function startElapsedTicker(state: Record<string, unknown> | undefined, invalidate: () => void): void {
|
|
94
125
|
if (!state || typeof state !== "object") return;
|
|
95
|
-
|
|
96
|
-
state
|
|
97
|
-
|
|
126
|
+
state[TICKER_KEY] = true;
|
|
127
|
+
tickerEntries.set(state, { invalidate });
|
|
128
|
+
ensureSharedTicker();
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
/** Stop a running tool's elapsed ticker (terminal result, error, session end). */
|
|
101
132
|
export function stopElapsedTicker(state: Record<string, unknown> | undefined): void {
|
|
102
133
|
if (!state || typeof state !== "object") return;
|
|
103
|
-
const handle = state[TICKER_KEY] as TickerHandle | undefined;
|
|
104
|
-
if (handle !== undefined) clearInterval(handle);
|
|
105
134
|
delete state[TICKER_KEY];
|
|
106
|
-
|
|
135
|
+
tickerEntries.delete(state);
|
|
136
|
+
stopSharedTickerIfIdle();
|
|
107
137
|
}
|
|
108
138
|
|
|
109
139
|
/** Stop every elapsed ticker (session start/shutdown). */
|
|
110
140
|
export function stopAllElapsedTickers(): void {
|
|
111
|
-
for (const state of
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
for (const state of tickerEntries.keys()) delete state[TICKER_KEY];
|
|
142
|
+
tickerEntries.clear();
|
|
143
|
+
stopSharedTickerIfIdle();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function __getElapsedTickerDebugState(): { trackedStates: number; hasSharedTicker: boolean } {
|
|
147
|
+
return { trackedStates: tickerEntries.size, hasSharedTicker: sharedTickerHandle !== undefined };
|
|
117
148
|
}
|
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
renderCompactBoxedToolCall,
|
|
13
13
|
resolveRelativePath,
|
|
14
14
|
shortenPath,
|
|
15
|
+
themeCacheKey,
|
|
15
16
|
} from "../../../shared/box.js";
|
|
16
17
|
import {
|
|
17
18
|
getStateElapsedMs,
|
|
19
|
+
getToolsRenderCacheSignature,
|
|
18
20
|
isResultSeen,
|
|
19
21
|
markResultSeen,
|
|
20
22
|
recordExecutionEnded,
|
|
@@ -174,6 +176,29 @@ export function resultFooterLines(
|
|
|
174
176
|
return [formatBoxedFooter(theme, result, extraParts, stateElapsedMs(context))];
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
type StateComponentCacheEntry = {
|
|
180
|
+
key: string;
|
|
181
|
+
component: Component;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export function getRenderCacheKey(prefix: string, theme: BoxTheme, ...parts: Array<string | number | boolean>): string {
|
|
185
|
+
return [prefix, themeCacheKey(theme), getToolsRenderCacheSignature(), ...parts].join("|");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function memoizedStateComponent(
|
|
189
|
+
state: Record<string, unknown> | undefined,
|
|
190
|
+
slot: string,
|
|
191
|
+
key: string,
|
|
192
|
+
build: () => Component,
|
|
193
|
+
): Component {
|
|
194
|
+
if (!state || typeof state !== "object") return build();
|
|
195
|
+
const cached = state[slot] as StateComponentCacheEntry | undefined;
|
|
196
|
+
if (cached && cached.key === key) return cached.component;
|
|
197
|
+
const component = build();
|
|
198
|
+
state[slot] = { key, component } satisfies StateComponentCacheEntry;
|
|
199
|
+
return component;
|
|
200
|
+
}
|
|
201
|
+
|
|
177
202
|
export function clearFooterState(context: BoxedToolContext): void {
|
|
178
203
|
clearCompactBoxedFooter(context.state);
|
|
179
204
|
}
|
|
@@ -145,7 +145,7 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
145
145
|
/** Primary (first-recorded) fingerprint per surface, for diagnostics and back-compat. */
|
|
146
146
|
export const TRUSTED_NATIVE_FINGERPRINTS: Readonly<Record<string, string>> = Object.freeze(
|
|
147
147
|
Object.fromEntries(
|
|
148
|
-
Object.entries(KNOWN_NATIVE_IDENTITIES).map(([key, identities]) => [key, identities[0]
|
|
148
|
+
Object.entries(KNOWN_NATIVE_IDENTITIES).map(([key, identities]) => [key, identities[0]?.fingerprint ?? ""]),
|
|
149
149
|
),
|
|
150
150
|
);
|
|
151
151
|
|
|
@@ -11,12 +11,11 @@ import {
|
|
|
11
11
|
import { requestToolPresentationRender } from "../features/tools/index.js";
|
|
12
12
|
import { registerPiStyleCommand } from "./commands.js";
|
|
13
13
|
import { type CompatibilityTestHooks, createPiStyleSessionCoordinator } from "./session-coordinator.js";
|
|
14
|
-
import { usageFromSession } from "./session-usage.js";
|
|
14
|
+
import { resetUsageFromSessionCache, usageFromSession } from "./session-usage.js";
|
|
15
15
|
|
|
16
|
-
/** Usage patch
|
|
16
|
+
/** Usage patch clears stale usage when the active branch/session has none. */
|
|
17
17
|
function usagePatch(ctx: ExtensionContext): StatusSnapshot {
|
|
18
|
-
|
|
19
|
-
return usage ? { usage } : {};
|
|
18
|
+
return { usage: usageFromSession(ctx.sessionManager) } as StatusSnapshot;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -65,6 +64,7 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
65
64
|
const coordinator = createPiStyleSessionCoordinator(pi, compatibilityTestHooks);
|
|
66
65
|
registerPiStyleCommand(pi, coordinator.app);
|
|
67
66
|
pi.on("session_start", async (event, ctx) => {
|
|
67
|
+
resetUsageFromSessionCache(ctx.sessionManager);
|
|
68
68
|
// Pi only activates read/bash/edit/write by default; grep/find/ls are
|
|
69
69
|
// registered but inactive (kept out of the model's tool list to keep the
|
|
70
70
|
// core small). Activate them so the TUI shows them and the model can call
|
|
@@ -110,7 +110,9 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
110
110
|
),
|
|
111
111
|
);
|
|
112
112
|
pi.on("thinking_level_select", (event) => coordinator.app.update({ thinkingLevel: event.level }, "immediate"));
|
|
113
|
-
pi.on("session_info_changed", (event) =>
|
|
113
|
+
pi.on("session_info_changed", (event) =>
|
|
114
|
+
coordinator.app.update({ sessionName: event.name }, "coalesced", { refreshExtensionStatuses: true }),
|
|
115
|
+
);
|
|
114
116
|
pi.on("message_start", () => {
|
|
115
117
|
// A new message is a batch boundary: quiet-tool (read/ls/find) calls of the
|
|
116
118
|
// new message start a fresh batch instead of joining the previous one.
|
|
@@ -123,11 +125,13 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
123
125
|
// Usage (tokens + cost) is aggregated from finalized session entries at
|
|
124
126
|
// message/turn boundaries, mirroring Pi's native footer; per-chunk updates
|
|
125
127
|
// stay usage-free to keep streaming cheap.
|
|
126
|
-
pi.on("message_end", (_event, ctx) =>
|
|
128
|
+
pi.on("message_end", (_event, ctx) =>
|
|
129
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "coalesced", { refreshContextUsage: true }),
|
|
130
|
+
);
|
|
127
131
|
pi.on("turn_end", (event, ctx) => {
|
|
128
132
|
// Append the finalized assistant message's tool batch to the current run.
|
|
129
133
|
registerTurnFromMessage(event.message, event.toolResults);
|
|
130
|
-
coordinator.app.update({ ...usagePatch(ctx) }, "deferred");
|
|
134
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "deferred", { refreshContextUsage: true });
|
|
131
135
|
});
|
|
132
136
|
pi.on("agent_end", () => {
|
|
133
137
|
// The run is complete: collapse its tool blocks into one summary line.
|
|
@@ -141,23 +145,32 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
141
145
|
requestToolPresentationRender();
|
|
142
146
|
}
|
|
143
147
|
});
|
|
144
|
-
pi.on("agent_settled", (_event, ctx) =>
|
|
148
|
+
pi.on("agent_settled", (_event, ctx) =>
|
|
149
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "coalesced", { refreshContextUsage: true }),
|
|
150
|
+
);
|
|
145
151
|
pi.on("session_tree", (_event, ctx) => {
|
|
152
|
+
resetUsageFromSessionCache(ctx.sessionManager);
|
|
146
153
|
// Rebuild the turn registry from session content so restored/branched
|
|
147
154
|
// history renders collapsed consistently (no in-process turn_end events).
|
|
148
155
|
rebuildTurnRegistryFromEntries(ctx.sessionManager.getEntries());
|
|
149
|
-
coordinator.app.update({ ...usagePatch(ctx) }, "deferred");
|
|
156
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "deferred", { refreshContextUsage: true });
|
|
157
|
+
});
|
|
158
|
+
pi.on("session_compact", (_event, ctx) => {
|
|
159
|
+
resetUsageFromSessionCache(ctx.sessionManager);
|
|
160
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "deferred", { refreshContextUsage: true });
|
|
150
161
|
});
|
|
151
|
-
pi.on("session_compact", (_event, ctx) => coordinator.app.update({ ...usagePatch(ctx) }, "deferred"));
|
|
152
162
|
pi.on("tool_result", (event, ctx) => {
|
|
153
163
|
if (["write", "edit", "bash"].includes(event.toolName)) {
|
|
154
164
|
coordinator.app.runtime.current?.invalidateGit();
|
|
155
|
-
coordinator.app.update({ ...usagePatch(ctx) }, "delayed-retry");
|
|
165
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "delayed-retry", { refreshContextUsage: true });
|
|
156
166
|
}
|
|
157
167
|
});
|
|
158
168
|
pi.on("user_bash", (_event, ctx) => {
|
|
159
169
|
coordinator.app.runtime.current?.invalidateGit();
|
|
160
|
-
coordinator.app.update({ ...usagePatch(ctx) }, "delayed-retry");
|
|
170
|
+
coordinator.app.update({ ...usagePatch(ctx) }, "delayed-retry", { refreshContextUsage: true });
|
|
171
|
+
});
|
|
172
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
173
|
+
resetUsageFromSessionCache(ctx.sessionManager);
|
|
174
|
+
coordinator.shutdown();
|
|
161
175
|
});
|
|
162
|
-
pi.on("session_shutdown", () => coordinator.shutdown());
|
|
163
176
|
}
|