pi-ui-extend 0.1.3 → 0.1.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.
- package/dist/app/app.d.ts +1 -1
- package/dist/app/app.js +14 -4
- package/dist/app/clipboard.d.ts +1 -0
- package/dist/app/clipboard.js +16 -0
- package/dist/app/conversation-entry-renderer.d.ts +0 -1
- package/dist/app/conversation-entry-renderer.js +2 -6
- package/dist/app/conversation-tool-renderer.js +2 -3
- package/dist/app/conversation-viewport.d.ts +0 -1
- package/dist/app/conversation-viewport.js +0 -1
- package/dist/app/startup-info.d.ts +1 -1
- package/dist/app/startup-info.js +3 -2
- package/dist/app/update.d.ts +2 -0
- package/dist/app/update.js +16 -0
- package/dist/config.d.ts +0 -3
- package/dist/config.js +0 -79
- package/dist/default-pix-config.js +2 -2
- package/dist/markdown-format.js +18 -1
- package/external/pi-tools-suite/README.md +4 -4
- package/external/pi-tools-suite/licenses/opencode-dynamic-context-pruning-AGPL-3.0.txt +619 -0
- package/external/pi-tools-suite/package.json +1 -1
- package/external/pi-tools-suite/src/config.ts +5 -1
- package/external/pi-tools-suite/src/{compress → dcp}/config.ts +10 -70
- package/external/pi-tools-suite/src/{compress → dcp}/index.ts +16 -66
- package/external/pi-tools-suite/src/dcp/ui.ts +45 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -2
- package/external/pi-tools-suite/src/index.ts +1 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +1 -1
- package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +0 -498
- package/external/pi-tools-suite/src/compress/ui.ts +0 -308
- /package/external/pi-tools-suite/src/{compress → dcp}/commands.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/compress-tool.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/compression-blocks.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/prompts.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-candidates.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-compression-blocks.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-message-ids.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-metadata.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-nudge.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-tools.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner-types.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/pruner.ts +0 -0
- /package/external/pi-tools-suite/src/{compress → dcp}/state.ts +0 -0
|
@@ -220,48 +220,6 @@ function readDcpFromSuiteConfig(filePath: string): Record<string, unknown> {
|
|
|
220
220
|
return dcp as Record<string, unknown>
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
/**
|
|
224
|
-
* Walk up from `startDir` looking for `.pi/dcp.jsonc`.
|
|
225
|
-
* Returns the path if found, otherwise null.
|
|
226
|
-
*/
|
|
227
|
-
function findProjectConfig(startDir: string): string | null {
|
|
228
|
-
let dir = path.resolve(startDir)
|
|
229
|
-
const root = path.parse(dir).root
|
|
230
|
-
|
|
231
|
-
while (true) {
|
|
232
|
-
const candidate = path.join(dir, ".pi", "dcp.jsonc")
|
|
233
|
-
if (fs.existsSync(candidate)) return candidate
|
|
234
|
-
if (dir === root) return null
|
|
235
|
-
const parent = path.dirname(dir)
|
|
236
|
-
if (parent === dir) return null
|
|
237
|
-
dir = parent
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Walk up from `startDir` looking for `.pi/pi-tools-suite.jsonc`.
|
|
243
|
-
* Returns the path if found, otherwise null.
|
|
244
|
-
*/
|
|
245
|
-
function findProjectSuiteConfig(startDir: string): string | null {
|
|
246
|
-
let dir = path.resolve(startDir)
|
|
247
|
-
const root = path.parse(dir).root
|
|
248
|
-
|
|
249
|
-
while (true) {
|
|
250
|
-
const candidate = path.join(dir, ".pi", "pi-tools-suite.jsonc")
|
|
251
|
-
if (fs.existsSync(candidate)) return candidate
|
|
252
|
-
if (dir === root) return null
|
|
253
|
-
const parent = path.dirname(dir)
|
|
254
|
-
if (parent === dir) return null
|
|
255
|
-
dir = parent
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function mergeConfigFile(config: DcpConfig, filePath: string): DcpConfig {
|
|
260
|
-
const raw = readJsoncFile(filePath)
|
|
261
|
-
if (Object.keys(raw).length === 0) return config
|
|
262
|
-
return deepMerge(config, raw as Partial<DcpConfig>)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
223
|
function mergeSuiteDcpConfig(config: DcpConfig, filePath: string): DcpConfig {
|
|
266
224
|
const raw = readDcpFromSuiteConfig(filePath)
|
|
267
225
|
if (Object.keys(raw).length === 0) return config
|
|
@@ -272,39 +230,21 @@ function mergeSuiteDcpConfig(config: DcpConfig, filePath: string): DcpConfig {
|
|
|
272
230
|
// Public API
|
|
273
231
|
// ---------------------------------------------------------------------------
|
|
274
232
|
|
|
233
|
+
export interface LoadConfigOptions {
|
|
234
|
+
homeDir?: string
|
|
235
|
+
}
|
|
236
|
+
|
|
275
237
|
/**
|
|
276
|
-
* Load the DCP configuration by merging
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* 3. legacy $PI_CONFIG_DIR/dcp.jsonc, then dcp in $PI_CONFIG_DIR/pi-tools-suite.jsonc
|
|
280
|
-
* 4. legacy <project>/.pi/dcp.jsonc, then dcp in nearest <project>/.pi/pi-tools-suite.jsonc
|
|
281
|
-
*
|
|
282
|
-
* The shared pi-tools-suite config wins over legacy dcp.jsonc at each layer so
|
|
283
|
-
* DCP settings can live beside the rest of the suite config while preserving
|
|
284
|
-
* backwards compatibility with existing standalone files.
|
|
238
|
+
* Load the DCP configuration by merging built-in defaults with the `dcp` section
|
|
239
|
+
* from the user-level shared pi-tools-suite config only:
|
|
240
|
+
* `~/.config/pi/pi-tools-suite.jsonc`.
|
|
285
241
|
*/
|
|
286
|
-
export function loadConfig(
|
|
242
|
+
export function loadConfig(options: LoadConfigOptions = {}): DcpConfig {
|
|
287
243
|
// Layer 1: defaults (deep clone so we never mutate the constant)
|
|
288
244
|
let config: DcpConfig = deepMerge(DEFAULT_CONFIG, {})
|
|
289
245
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
config = mergeConfigFile(config, globalConfigPath)
|
|
293
|
-
config = mergeSuiteDcpConfig(config, path.join(os.homedir(), ".config", "pi", "pi-tools-suite.jsonc"))
|
|
294
|
-
|
|
295
|
-
// Layer 3: $PI_CONFIG_DIR/dcp.jsonc
|
|
296
|
-
const piConfigDir = process.env["PI_CONFIG_DIR"]
|
|
297
|
-
if (piConfigDir) {
|
|
298
|
-
config = mergeConfigFile(config, path.join(piConfigDir, "dcp.jsonc"))
|
|
299
|
-
config = mergeSuiteDcpConfig(config, path.join(piConfigDir, "pi-tools-suite.jsonc"))
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Layer 4: project-local config (walk up from projectDir)
|
|
303
|
-
const projectConfigPath = findProjectConfig(projectDir)
|
|
304
|
-
if (projectConfigPath) config = mergeConfigFile(config, projectConfigPath)
|
|
305
|
-
|
|
306
|
-
const projectSuiteConfigPath = findProjectSuiteConfig(projectDir)
|
|
307
|
-
if (projectSuiteConfigPath) config = mergeSuiteDcpConfig(config, projectSuiteConfigPath)
|
|
246
|
+
const homeDir = options.homeDir ?? os.homedir()
|
|
247
|
+
config = mergeSuiteDcpConfig(config, path.join(homeDir, ".config", "pi", "pi-tools-suite.jsonc"))
|
|
308
248
|
|
|
309
249
|
return config
|
|
310
250
|
}
|
|
@@ -37,11 +37,8 @@ import {
|
|
|
37
37
|
import type { DcpNudgeType } from "./pruner-types.js"
|
|
38
38
|
import { registerCompressTool } from "./compress-tool.js"
|
|
39
39
|
import { DCP_STATS_MESSAGE_TYPE, registerCommands } from "./commands.js"
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import { ignoreStaleExtensionContextError, safeGetContextUsage } from "../context-usage.js"
|
|
43
|
-
|
|
44
|
-
const DCP_CONTEXT_USAGE_EVENT = "pi-tools-suite:dcp-context-usage"
|
|
40
|
+
import { normalizeDcpContextUsage } from "./ui.js"
|
|
41
|
+
import { safeGetContextUsage } from "../context-usage.js"
|
|
45
42
|
|
|
46
43
|
// ---------------------------------------------------------------------------
|
|
47
44
|
// Helpers
|
|
@@ -101,31 +98,12 @@ function isUserVisibleOnlyMessage(message: any): boolean {
|
|
|
101
98
|
|
|
102
99
|
export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
103
100
|
// ── 1. Load config ────────────────────────────────────────────────────────
|
|
104
|
-
const config = loadConfig(
|
|
101
|
+
const config = loadConfig()
|
|
105
102
|
|
|
106
103
|
if (!config.enabled) return
|
|
107
104
|
|
|
108
105
|
// ── 2. Create state ───────────────────────────────────────────────────────
|
|
109
106
|
const state = createState()
|
|
110
|
-
const ui = new DcpUiController(state)
|
|
111
|
-
const updateUi = (ctx: ExtensionContext): void => {
|
|
112
|
-
try {
|
|
113
|
-
if (!ctx?.hasUI) return
|
|
114
|
-
ui.setUICtx(ctx.ui)
|
|
115
|
-
ui.update(ctx)
|
|
116
|
-
} catch (error) {
|
|
117
|
-
ignoreStaleExtensionContextError(error)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const emitContextUsage = (ctx: ExtensionContext): void => {
|
|
121
|
-
const usage = safeGetContextUsage(ctx)
|
|
122
|
-
if (!usage) return
|
|
123
|
-
try {
|
|
124
|
-
;(pi as { events?: { emit?: (name: string, data: unknown) => void } }).events?.emit?.(DCP_CONTEXT_USAGE_EVENT, usage)
|
|
125
|
-
} catch (error) {
|
|
126
|
-
ignoreStaleExtensionContextError(error)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
107
|
const appendNudgeTelemetry = (
|
|
130
108
|
event: "emitted" | "upgraded",
|
|
131
109
|
type: DcpNudgeType,
|
|
@@ -158,20 +136,13 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
158
136
|
state.manualMode = true
|
|
159
137
|
}
|
|
160
138
|
|
|
161
|
-
// ── 3. Register
|
|
162
|
-
registerTuiFilter(pi)
|
|
163
|
-
|
|
164
|
-
// ── 4. Register compress tool ─────────────────────────────────────────────
|
|
139
|
+
// ── 3. Register compress tool ─────────────────────────────────────────────
|
|
165
140
|
registerCompressTool(pi, state, config)
|
|
166
141
|
|
|
167
|
-
// ──
|
|
168
|
-
registerCommands(pi, state, config
|
|
169
|
-
onStateChanged(ctx) {
|
|
170
|
-
updateUi(ctx)
|
|
171
|
-
},
|
|
172
|
-
})
|
|
142
|
+
// ── 4. Register /dcp commands ─────────────────────────────────────────────
|
|
143
|
+
registerCommands(pi, state, config)
|
|
173
144
|
|
|
174
|
-
// ──
|
|
145
|
+
// ── 5. session_start: restore state from session entries ──────────────────
|
|
175
146
|
pi.on("session_start", async (_event, ctx) => {
|
|
176
147
|
// Reset to a clean slate first.
|
|
177
148
|
resetState(state)
|
|
@@ -191,17 +162,15 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
191
162
|
|
|
192
163
|
restoreState(state, latestDcpState)
|
|
193
164
|
|
|
194
|
-
//
|
|
195
|
-
updateUi(ctx)
|
|
165
|
+
// Headless by design: no extension status/footer/widgets are rendered.
|
|
196
166
|
})
|
|
197
167
|
|
|
198
|
-
// ──
|
|
168
|
+
// ── 6. session_shutdown: save state ───────────────────────────────────────
|
|
199
169
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
200
170
|
saveState(pi, state)
|
|
201
|
-
ui.dispose()
|
|
202
171
|
})
|
|
203
172
|
|
|
204
|
-
// ──
|
|
173
|
+
// ── 7. before_agent_start: inject system prompt ───────────────────────────
|
|
205
174
|
pi.on("before_agent_start", async (event, _ctx) => {
|
|
206
175
|
const promptAddition = state.manualMode
|
|
207
176
|
? MANUAL_MODE_SYSTEM_PROMPT
|
|
@@ -212,7 +181,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
212
181
|
}
|
|
213
182
|
})
|
|
214
183
|
|
|
215
|
-
// ──
|
|
184
|
+
// ── 8. tool_call: record input args for dedup / purge fingerprinting ───────
|
|
216
185
|
pi.on("tool_call", async (event, _ctx) => {
|
|
217
186
|
if (!state.toolCalls.has(event.toolCallId)) {
|
|
218
187
|
state.toolCalls.set(event.toolCallId, {
|
|
@@ -231,8 +200,8 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
231
200
|
}
|
|
232
201
|
})
|
|
233
202
|
|
|
234
|
-
// ──
|
|
235
|
-
pi.on("tool_result", async (event,
|
|
203
|
+
// ── 9. tool_result: finalise tool record with result info ─────────────────
|
|
204
|
+
pi.on("tool_result", async (event, _ctx) => {
|
|
236
205
|
const record = state.toolCalls.get(event.toolCallId)
|
|
237
206
|
|
|
238
207
|
const outputText = event.content
|
|
@@ -261,13 +230,9 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
261
230
|
})
|
|
262
231
|
}
|
|
263
232
|
|
|
264
|
-
if (event.toolName === "compress" && ctx.hasUI) {
|
|
265
|
-
updateUi(ctx)
|
|
266
|
-
emitContextUsage(ctx)
|
|
267
|
-
}
|
|
268
233
|
})
|
|
269
234
|
|
|
270
|
-
// ──
|
|
235
|
+
// ── 10. context: apply pruning and inject nudges ──────────────────────────
|
|
271
236
|
pi.on("context", async (event, ctx) => {
|
|
272
237
|
const contextMessages = event.messages.filter((message: any) => !isUserVisibleOnlyMessage(message))
|
|
273
238
|
annotateMessagesWithBranchEntryIds(contextMessages, ctx)
|
|
@@ -295,8 +260,6 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
295
260
|
applyAnchoredNudges(prunedMessages, state, (anchor) =>
|
|
296
261
|
appendConcreteNudgeGuidance(baseNudgeText(anchor.type), candidate, messageCandidates, state),
|
|
297
262
|
)
|
|
298
|
-
updateUi(ctx)
|
|
299
|
-
emitContextUsage(ctx)
|
|
300
263
|
return { messages: prunedMessages }
|
|
301
264
|
}
|
|
302
265
|
|
|
@@ -388,24 +351,11 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
388
351
|
appendConcreteNudgeGuidance(baseNudgeText(anchor.type), candidate, messageCandidates, state),
|
|
389
352
|
)
|
|
390
353
|
|
|
391
|
-
|
|
392
|
-
// Update footer status and floating widget after each context event.
|
|
393
|
-
updateUi(ctx)
|
|
394
|
-
emitContextUsage(ctx)
|
|
395
|
-
|
|
396
354
|
return { messages: prunedMessages }
|
|
397
355
|
})
|
|
398
356
|
|
|
399
|
-
// ──
|
|
400
|
-
pi.on("
|
|
401
|
-
updateUi(ctx)
|
|
402
|
-
emitContextUsage(ctx)
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
// ── 13. agent_end: persist state after each agent run ────────────────────
|
|
406
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
407
|
-
updateUi(ctx)
|
|
408
|
-
emitContextUsage(ctx)
|
|
357
|
+
// ── 11. agent_end: persist state after each agent run ────────────────────
|
|
358
|
+
pi.on("agent_end", async (_event, _ctx) => {
|
|
409
359
|
saveState(pi, state)
|
|
410
360
|
})
|
|
411
361
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface DcpCompressionVisualDetails {
|
|
2
|
+
topic: string
|
|
3
|
+
blockIds: number[]
|
|
4
|
+
ranges: number
|
|
5
|
+
messages: number
|
|
6
|
+
itemCount: number
|
|
7
|
+
totalSummaryTokens: number
|
|
8
|
+
activeBlocks: number
|
|
9
|
+
totalBlocks: number
|
|
10
|
+
prunedTools: number
|
|
11
|
+
tokensSaved: number
|
|
12
|
+
contextTokens?: number | null
|
|
13
|
+
contextWindow?: number
|
|
14
|
+
contextPercent?: number | null
|
|
15
|
+
skippedMessages?: number
|
|
16
|
+
skippedMessageIssues?: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DcpContextUsage {
|
|
20
|
+
tokens: number | null
|
|
21
|
+
contextWindow: number
|
|
22
|
+
percent?: number | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type RawDcpContextUsage = {
|
|
26
|
+
tokens?: number | null
|
|
27
|
+
contextWindow?: number
|
|
28
|
+
percent?: number | null
|
|
29
|
+
} | undefined
|
|
30
|
+
|
|
31
|
+
export function normalizeDcpContextUsage(usage: RawDcpContextUsage): DcpContextUsage | undefined {
|
|
32
|
+
if (!usage || typeof usage.contextWindow !== "number" || usage.contextWindow <= 0) return undefined
|
|
33
|
+
const contextWindow = usage.contextWindow
|
|
34
|
+
const percent = typeof usage.percent === "number" && Number.isFinite(usage.percent)
|
|
35
|
+
? Math.max(0, usage.percent)
|
|
36
|
+
: usage.percent === null
|
|
37
|
+
? null
|
|
38
|
+
: undefined
|
|
39
|
+
const tokens = typeof percent === "number"
|
|
40
|
+
? Math.round((contextWindow * percent) / 100)
|
|
41
|
+
: typeof usage.tokens === "number"
|
|
42
|
+
? usage.tokens
|
|
43
|
+
: null
|
|
44
|
+
return { tokens, contextWindow, percent }
|
|
45
|
+
}
|
|
@@ -13,10 +13,11 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = `{
|
|
|
13
13
|
// "model-tools",
|
|
14
14
|
// "usage",
|
|
15
15
|
// "web-search",
|
|
16
|
-
// "
|
|
16
|
+
// "dcp"
|
|
17
17
|
],
|
|
18
18
|
|
|
19
|
-
// Dynamic Context Pruning (DCP)
|
|
19
|
+
// Dynamic Context Pruning (DCP) module config.
|
|
20
|
+
// The DCP module owns the compress tool and /dcp commands.
|
|
20
21
|
"dcp": {
|
|
21
22
|
"enabled": true,
|
|
22
23
|
"manualMode": {
|
|
@@ -20,7 +20,7 @@ const MODULES: Array<{ name: string; load: () => Promise<ExtensionModule> }> = [
|
|
|
20
20
|
{ name: "model-tools", load: () => import("./model-tools/index") },
|
|
21
21
|
{ name: "usage", load: () => import("./usage/index") },
|
|
22
22
|
{ name: "web-search", load: () => import("./web-search/index") },
|
|
23
|
-
{ name: "
|
|
23
|
+
{ name: "dcp", load: () => import("./dcp/index") },
|
|
24
24
|
{ name: "prompt-commands", load: () => import("./prompt-commands/index") },
|
|
25
25
|
];
|
|
26
26
|
|