pi-ui-extend 0.1.34 → 0.1.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/README.md +20 -0
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +12 -2
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +32 -0
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-session-actions.js +2 -0
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +4 -0
- package/dist/app/input/input-controller.d.ts +5 -1
- package/dist/app/input/input-controller.js +122 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +21 -0
- package/dist/app/input/terminal-edit-shortcuts.js +92 -16
- package/dist/app/popup/popup-action-controller.d.ts +1 -0
- package/dist/app/popup/popup-action-controller.js +1 -0
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +7 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +4 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +1 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +5 -5
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
|
@@ -25,7 +25,7 @@ export interface DcpConfig {
|
|
|
25
25
|
modelMinContextLimits?: Record<string, number | string> // same formats as modelMinContextPercent; checked before modelMinContextPercent
|
|
26
26
|
summaryBuffer: boolean
|
|
27
27
|
nudgeFrequency: number // inject nudge every N context events (default: 2)
|
|
28
|
-
iterationNudgeThreshold: number // nudge after N tool calls since last user msg (default:
|
|
28
|
+
iterationNudgeThreshold: number // nudge after N tool calls since last user msg (default: 4)
|
|
29
29
|
nudgeForce: "strong" | "soft"
|
|
30
30
|
protectedTools: string[] // these tool outputs always protected from pruning
|
|
31
31
|
protectTags: boolean
|
|
@@ -67,8 +67,17 @@ export interface DcpConfig {
|
|
|
67
67
|
}
|
|
68
68
|
protectedFilePatterns: string[]
|
|
69
69
|
pruneNotification: "off" | "minimal" | "detailed"
|
|
70
|
+
modelOverrides: Record<string, DcpConfigOverride>
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
export type DcpConfigOverride = DeepPartial<Omit<DcpConfig, "modelOverrides">>
|
|
74
|
+
|
|
75
|
+
type DeepPartial<T> = T extends Array<infer U>
|
|
76
|
+
? Array<DeepPartial<U>>
|
|
77
|
+
: T extends object
|
|
78
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
79
|
+
: T
|
|
80
|
+
|
|
72
81
|
// ---------------------------------------------------------------------------
|
|
73
82
|
// Defaults
|
|
74
83
|
// ---------------------------------------------------------------------------
|
|
@@ -87,7 +96,7 @@ const DEFAULT_CONFIG: DcpConfig = {
|
|
|
87
96
|
modelMinContextPercent: {},
|
|
88
97
|
summaryBuffer: true,
|
|
89
98
|
nudgeFrequency: 1,
|
|
90
|
-
iterationNudgeThreshold:
|
|
99
|
+
iterationNudgeThreshold: 4,
|
|
91
100
|
nudgeForce: "soft",
|
|
92
101
|
protectedTools: ["compress", "write", "edit"],
|
|
93
102
|
protectTags: false,
|
|
@@ -95,14 +104,14 @@ const DEFAULT_CONFIG: DcpConfig = {
|
|
|
95
104
|
autoCandidates: {
|
|
96
105
|
enabled: true,
|
|
97
106
|
minContextPercent: 0.20,
|
|
98
|
-
keepRecentTurns:
|
|
107
|
+
keepRecentTurns: 1,
|
|
99
108
|
minMessages: 6,
|
|
100
109
|
minTokens: 1500,
|
|
101
110
|
},
|
|
102
111
|
messageMode: {
|
|
103
112
|
enabled: true,
|
|
104
113
|
minContextPercent: 0.20,
|
|
105
|
-
keepRecentTurns:
|
|
114
|
+
keepRecentTurns: 1,
|
|
106
115
|
mediumTokens: 500,
|
|
107
116
|
highTokens: 5000,
|
|
108
117
|
maxSuggestions: 5,
|
|
@@ -120,13 +129,17 @@ const DEFAULT_CONFIG: DcpConfig = {
|
|
|
120
129
|
},
|
|
121
130
|
autoToolPruning: {
|
|
122
131
|
enabled: true,
|
|
123
|
-
maxOutputTokens:
|
|
124
|
-
keepRecentTurns:
|
|
132
|
+
maxOutputTokens: 1200,
|
|
133
|
+
keepRecentTurns: 1,
|
|
125
134
|
readLikeTools: [
|
|
126
135
|
"read",
|
|
136
|
+
"shell",
|
|
137
|
+
"bash",
|
|
127
138
|
"grep",
|
|
128
139
|
"find",
|
|
129
140
|
"ls",
|
|
141
|
+
"web_search",
|
|
142
|
+
"web_fetch",
|
|
130
143
|
"repo_architecture",
|
|
131
144
|
"repo_structure",
|
|
132
145
|
"repo_ast",
|
|
@@ -141,6 +154,7 @@ const DEFAULT_CONFIG: DcpConfig = {
|
|
|
141
154
|
},
|
|
142
155
|
protectedFilePatterns: [],
|
|
143
156
|
pruneNotification: "detailed",
|
|
157
|
+
modelOverrides: {},
|
|
144
158
|
}
|
|
145
159
|
|
|
146
160
|
// ---------------------------------------------------------------------------
|
|
@@ -226,6 +240,128 @@ function mergeSuiteDcpConfig(config: DcpConfig, filePath: string): DcpConfig {
|
|
|
226
240
|
return deepMerge(config, raw as Partial<DcpConfig>)
|
|
227
241
|
}
|
|
228
242
|
|
|
243
|
+
function normalizeModelKey(key: string | undefined): string | undefined {
|
|
244
|
+
if (typeof key !== "string") return undefined
|
|
245
|
+
const trimmed = key.trim()
|
|
246
|
+
return trimmed.length > 0 ? trimmed : undefined
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function escapeRegExp(text: string): string {
|
|
250
|
+
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function globToRegExp(pattern: string): RegExp {
|
|
254
|
+
let source = "^"
|
|
255
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
256
|
+
const char = pattern[i]!
|
|
257
|
+
if (char === "*") {
|
|
258
|
+
source += ".*"
|
|
259
|
+
} else if (char === "?") {
|
|
260
|
+
source += "."
|
|
261
|
+
} else {
|
|
262
|
+
source += escapeRegExp(char)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
source += "$"
|
|
266
|
+
return new RegExp(source)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isWildcardPattern(pattern: string): boolean {
|
|
270
|
+
return pattern.includes("*") || pattern.includes("?")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function modelPatternMatches(pattern: string, candidate: string): boolean {
|
|
274
|
+
return globToRegExp(pattern).test(candidate)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function uniqueModelCandidates(modelKeys: Array<string | undefined>): string[] {
|
|
278
|
+
return modelKeys
|
|
279
|
+
.map((key) => normalizeModelKey(key))
|
|
280
|
+
.filter((key, index, array): key is string => typeof key === "string" && array.indexOf(key) === index)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function matchingModelEntries<T>(
|
|
284
|
+
record: Record<string, T> | undefined,
|
|
285
|
+
modelKeys: Array<string | undefined> = [],
|
|
286
|
+
): Array<[string, T]> {
|
|
287
|
+
if (!record || Object.keys(record).length === 0) return []
|
|
288
|
+
|
|
289
|
+
const candidates = uniqueModelCandidates(modelKeys)
|
|
290
|
+
if (candidates.length === 0) return []
|
|
291
|
+
|
|
292
|
+
const exactEntries = new Map<string, T>()
|
|
293
|
+
const wildcardEntries: Array<[string, T]> = []
|
|
294
|
+
|
|
295
|
+
for (const [rawKey, value] of Object.entries(record)) {
|
|
296
|
+
const key = normalizeModelKey(rawKey)
|
|
297
|
+
if (!key) continue
|
|
298
|
+
if (isWildcardPattern(key)) wildcardEntries.push([key, value])
|
|
299
|
+
else exactEntries.set(key, value)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const fullCandidates = candidates.filter((candidate) => candidate.includes("/"))
|
|
303
|
+
const bareCandidates = candidates.filter((candidate) => !candidate.includes("/"))
|
|
304
|
+
const matches: Array<[string, T]> = []
|
|
305
|
+
|
|
306
|
+
for (const candidate of bareCandidates) {
|
|
307
|
+
for (const entry of wildcardEntries) {
|
|
308
|
+
if (entry[0].includes("/")) continue
|
|
309
|
+
if (modelPatternMatches(entry[0], candidate)) matches.push(entry)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (const candidate of bareCandidates) {
|
|
314
|
+
const value = exactEntries.get(candidate)
|
|
315
|
+
if (value !== undefined) matches.push([candidate, value])
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const candidate of fullCandidates) {
|
|
319
|
+
for (const entry of wildcardEntries) {
|
|
320
|
+
if (!entry[0].includes("/")) continue
|
|
321
|
+
if (modelPatternMatches(entry[0], candidate)) matches.push(entry)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
for (const candidate of fullCandidates) {
|
|
326
|
+
const value = exactEntries.get(candidate)
|
|
327
|
+
if (value !== undefined) matches.push([candidate, value])
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return matches
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function modelKeysFromContext(ctx: unknown): string[] {
|
|
334
|
+
const ctxModel = (ctx as any)?.model
|
|
335
|
+
const provider = normalizeModelKey(
|
|
336
|
+
ctxModel?.provider ?? ctxModel?.providerId ?? ctxModel?.providerID,
|
|
337
|
+
)
|
|
338
|
+
const model = normalizeModelKey(
|
|
339
|
+
ctxModel?.id ?? ctxModel?.model ?? ctxModel?.modelId ?? ctxModel?.modelID,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return [provider && model ? `${provider}/${model}` : undefined, model].filter(
|
|
343
|
+
(key): key is string => typeof key === "string",
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function resolveModelConfig(
|
|
348
|
+
config: DcpConfig,
|
|
349
|
+
modelKeys: Array<string | undefined> = [],
|
|
350
|
+
): DcpConfig {
|
|
351
|
+
const overrides = config.modelOverrides
|
|
352
|
+
if (!overrides || Object.keys(overrides).length === 0) return config
|
|
353
|
+
|
|
354
|
+
const matches = matchingModelEntries(overrides, modelKeys)
|
|
355
|
+
if (matches.length === 0) return config
|
|
356
|
+
|
|
357
|
+
let resolved = deepMerge(config, {})
|
|
358
|
+
for (const [, override] of matches) {
|
|
359
|
+
resolved = deepMerge(resolved, override as Partial<DcpConfig>)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return resolved
|
|
363
|
+
}
|
|
364
|
+
|
|
229
365
|
// ---------------------------------------------------------------------------
|
|
230
366
|
// Public API
|
|
231
367
|
// ---------------------------------------------------------------------------
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
6
|
-
import { loadConfig } from "./config.js"
|
|
6
|
+
import { loadConfig, modelKeysFromContext, resolveModelConfig } from "./config.js"
|
|
7
7
|
import {
|
|
8
8
|
createState,
|
|
9
9
|
resetState,
|
|
@@ -102,8 +102,12 @@ function isDcpControlPlaneMessage(message: any): boolean {
|
|
|
102
102
|
export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
103
103
|
// ── 1. Load config ────────────────────────────────────────────────────────
|
|
104
104
|
const config = loadConfig()
|
|
105
|
+
const configForContext = (ctx: unknown) => resolveModelConfig(config, modelKeysFromContext(ctx))
|
|
106
|
+
const hasEnabledModelOverride = Object.values(config.modelOverrides).some(
|
|
107
|
+
(override) => override.enabled === true,
|
|
108
|
+
)
|
|
105
109
|
|
|
106
|
-
if (!config.enabled) return
|
|
110
|
+
if (!config.enabled && !hasEnabledModelOverride) return
|
|
107
111
|
|
|
108
112
|
// ── 2. Create state ───────────────────────────────────────────────────────
|
|
109
113
|
const state = createState()
|
|
@@ -175,6 +179,9 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
175
179
|
|
|
176
180
|
// ── 7. before_agent_start: inject system prompt ───────────────────────────
|
|
177
181
|
pi.on("before_agent_start", async (event, _ctx) => {
|
|
182
|
+
const effectiveConfig = configForContext(_ctx)
|
|
183
|
+
if (!effectiveConfig.enabled) return { systemPrompt: event.systemPrompt }
|
|
184
|
+
|
|
178
185
|
const promptAddition = state.manualMode
|
|
179
186
|
? MANUAL_MODE_SYSTEM_PROMPT
|
|
180
187
|
: SYSTEM_PROMPT
|
|
@@ -239,11 +246,15 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
239
246
|
|
|
240
247
|
// ── 10. context: apply pruning and inject nudges ──────────────────────────
|
|
241
248
|
pi.on("context", async (event, ctx) => {
|
|
249
|
+
const effectiveConfig = configForContext(ctx)
|
|
242
250
|
const contextMessages = event.messages.filter((message: any) =>
|
|
243
251
|
!isUserVisibleOnlyMessage(message) && !isDcpControlPlaneMessage(message)
|
|
244
252
|
)
|
|
253
|
+
if (!effectiveConfig.enabled) {
|
|
254
|
+
return { messages: contextMessages }
|
|
255
|
+
}
|
|
245
256
|
annotateMessagesWithBranchEntryIds(contextMessages, ctx)
|
|
246
|
-
let prunedMessages = applyPruning(contextMessages, state,
|
|
257
|
+
let prunedMessages = applyPruning(contextMessages, state, effectiveConfig)
|
|
247
258
|
let candidate = null as ReturnType<typeof detectCompressionCandidate>
|
|
248
259
|
let messageCandidates = [] as ReturnType<typeof detectMessageCompressionCandidates>
|
|
249
260
|
|
|
@@ -273,11 +284,11 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
273
284
|
const ctxModel = (ctx as any).model
|
|
274
285
|
const provider = ctxModel?.provider ?? ctxModel?.providerId ?? ctxModel?.providerID
|
|
275
286
|
const model = ctxModel?.id ?? ctxModel?.model ?? ctxModel?.modelId ?? ctxModel?.modelID
|
|
276
|
-
const thresholds = resolveContextThresholds(
|
|
287
|
+
const thresholds = resolveContextThresholds(effectiveConfig, [
|
|
277
288
|
provider && model ? `${provider}/${model}` : undefined,
|
|
278
289
|
model,
|
|
279
290
|
], usage.contextWindow)
|
|
280
|
-
if (
|
|
291
|
+
if (effectiveConfig.compress.summaryBuffer) {
|
|
281
292
|
const summaryBonus = getActiveSummaryTokenEstimate(state) / usage.contextWindow
|
|
282
293
|
thresholds.maxContextPercent += Math.min(summaryBonus, SUMMARY_BUFFER_MAX_CONTEXT_BONUS)
|
|
283
294
|
}
|
|
@@ -292,7 +303,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
292
303
|
const nudgeType = getNudgeType(
|
|
293
304
|
contextPercent,
|
|
294
305
|
state,
|
|
295
|
-
|
|
306
|
+
effectiveConfig,
|
|
296
307
|
toolCallsSinceLastUser,
|
|
297
308
|
thresholds,
|
|
298
309
|
)
|
|
@@ -304,12 +315,13 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
304
315
|
candidate = detectCompressionCandidate(
|
|
305
316
|
prunedMessages,
|
|
306
317
|
state,
|
|
307
|
-
|
|
318
|
+
effectiveConfig,
|
|
308
319
|
contextPercent,
|
|
309
320
|
)
|
|
310
321
|
messageCandidates = detectMessageCompressionCandidates(
|
|
311
322
|
prunedMessages,
|
|
312
|
-
|
|
323
|
+
state,
|
|
324
|
+
effectiveConfig,
|
|
313
325
|
contextPercent,
|
|
314
326
|
)
|
|
315
327
|
|
|
@@ -26,7 +26,9 @@ When multiple independent stale sections exist, prefer several focused compressi
|
|
|
26
26
|
When one older message is huge but the surrounding context is still useful, use message-mode compression for that single message instead of compressing a broad range.
|
|
27
27
|
Summaries should be proportional to future usefulness, not proportional to the amount of text being removed.
|
|
28
28
|
|
|
29
|
-
Use \`compress\` as steady housekeeping while you work.
|
|
29
|
+
Use \`compress\` as steady housekeeping while you work. Closed slice => compress now. Do not start a new search, file-read batch, test, verification run, or web lookup while older completed work remains raw.
|
|
30
|
+
|
|
31
|
+
A closed slice is any finished implementation, verification, config edit, answered exploration, dead-end debugging branch, or test/log inspection. Passing logs are summary-only: preserve command, pass/fail, key failures if any, and whether follow-up is needed; never keep a full passing log in live context. Treat large shell/read/repo/web outputs as disposable evidence once their facts are extracted.
|
|
30
32
|
|
|
31
33
|
Before compressing while work is unfinished, ensure one \`todo in_progress\` captures the active objective and next step.
|
|
32
34
|
|
|
@@ -38,6 +40,7 @@ CADENCE, SIGNALS, AND LATENCY
|
|
|
38
40
|
- Prioritize closedness and independence over raw size
|
|
39
41
|
- Prefer smaller, regular compressions over infrequent massive compressions for better latency and summary quality
|
|
40
42
|
- When multiple independent stale sections are ready, batch compressions in parallel
|
|
43
|
+
- Before more exploration, ask whether an older completed slice can become summary-only; if yes, compress first
|
|
41
44
|
|
|
42
45
|
COMPRESS WHEN
|
|
43
46
|
|
|
@@ -45,6 +48,8 @@ A section is genuinely closed and the raw conversation has served its purpose:
|
|
|
45
48
|
|
|
46
49
|
- Research concluded and findings are clear
|
|
47
50
|
- Implementation finished and verified
|
|
51
|
+
- Config/doc edit finished
|
|
52
|
+
- Test, lint, or CI output has been understood, especially passing logs
|
|
48
53
|
- Exploration exhausted and patterns understood
|
|
49
54
|
- Dead-end noise can be discarded without waiting for a whole chapter to close
|
|
50
55
|
|
|
@@ -77,7 +82,10 @@ It is your responsibility to keep a sharp, high-quality context window for optim
|
|
|
77
82
|
export const COMPRESS_RANGE_DESCRIPTION = `Collapse one or more ranges of the conversation into detailed summaries.
|
|
78
83
|
|
|
79
84
|
AUTONOMOUS HOUSEKEEPING
|
|
80
|
-
Do not wait for context emergencies.
|
|
85
|
+
Do not wait for context emergencies. Closed slice => compress now. A closed slice includes completed implementation, verification, config/doc edit, answered exploration, dead-end debugging, or finished test/log inspection. Compress before starting the next search/read/test/web lookup unless exact raw text is still needed.
|
|
86
|
+
|
|
87
|
+
PASSING LOGS AND LARGE OUTPUTS
|
|
88
|
+
Passing check/test/lint/tsc logs are summary-only after you know the result. Preserve command, pass/fail, key failures if any, and follow-up status; drop full passing output. Treat large shell/read/repo/web outputs as disposable evidence once important facts are extracted.
|
|
81
89
|
|
|
82
90
|
DCP REMINDERS
|
|
83
91
|
If a \`<dcp-system-reminder>\` is present in context, treat it as a direct instruction to evaluate compression immediately. If a safe closed range exists, call this tool before further exploration. Skipping compression is acceptable only when the newest raw context is still needed for the next concrete edit, test, or answer.
|
|
@@ -87,7 +95,7 @@ Your summary must be COMPLETE FOR CONTINUATION, not a transcript rewrite. Preser
|
|
|
87
95
|
|
|
88
96
|
If active unfinished work exists, start with \`Active objective\` and \`Next step\`.
|
|
89
97
|
|
|
90
|
-
Default to a compact structured summary (roughly
|
|
98
|
+
Default to a compact structured summary (roughly 4-10 bullets for a normal completed work slice). Grow beyond that only when the compressed range contains multiple independent decisions, unresolved blockers, or precise state that is genuinely required to continue.
|
|
91
99
|
|
|
92
100
|
Do not copy long raw code, JSON, diffs, logs, or tool output into summaries. Prefer semantic descriptions such as “updated foo.json so scene_assets_1.zai-svg has maxConcurrentRuns set to 5.” Include exact snippets only when the literal text is required for safe continuation, and keep them short and single-line.
|
|
93
101
|
|
|
@@ -98,7 +106,7 @@ USER INTENT FIDELITY
|
|
|
98
106
|
When the compressed range includes user messages, preserve the user's intent with extra care. Do not change scope, constraints, priorities, acceptance criteria, or requested outcomes.
|
|
99
107
|
Directly quote user messages when they are short enough to include safely. Direct quotes are preferred when they best preserve exact meaning.
|
|
100
108
|
|
|
101
|
-
Be LEAN. Strip away
|
|
109
|
+
Be LEAN. Strip away full logs, repeated search/read output, duplicate summaries, incidental failed attempts, and line-by-line edit history. What remains should be pure signal with enough detail to resume work confidently.
|
|
102
110
|
|
|
103
111
|
TWO COMPRESSION MODES
|
|
104
112
|
You may use either or both modes in one call:
|
|
@@ -174,7 +182,7 @@ You are at or beyond the configured max context threshold. This is an emergency
|
|
|
174
182
|
You MUST use the \`compress\` tool now. Do not continue normal exploration until compression is handled.
|
|
175
183
|
|
|
176
184
|
If you are in the middle of a critical atomic operation, finish that atomic step first, then compress immediately.
|
|
177
|
-
If
|
|
185
|
+
If any closed slice exists (finished implementation, verification, config/doc edit, answered exploration, dead end, or test/log inspection), compress it before replying or starting another task. Passing logs should become command + pass/fail + follow-up status only.
|
|
178
186
|
|
|
179
187
|
RANGE STRATEGY (MANDATORY)
|
|
180
188
|
Prioritize one large, closed, high-yield compression range first.
|
|
@@ -201,9 +209,9 @@ ACTION REQUIRED: Context usage is high.
|
|
|
201
209
|
|
|
202
210
|
Before doing more exploration, look for a closed, self-contained range that no longer needs to stay raw and compress it now.
|
|
203
211
|
|
|
204
|
-
Do not treat this as optional housekeeping. If any completed research, implementation, verification, CI-log inspection, or dead-end debugging slice is present, call the \`compress\` tool before continuing normal work.
|
|
212
|
+
Do not treat this as optional housekeeping. If any completed research, implementation, verification, config/doc edit, CI-log inspection, or dead-end debugging slice is present, call the \`compress\` tool before continuing normal work.
|
|
205
213
|
If a completed implementation+verification slice exists, compress it before replying or starting another task.
|
|
206
|
-
High-priority stale
|
|
214
|
+
High-priority stale shell/read/repo/web outputs must be compressed once no exact raw text is needed. Passing logs should not remain raw after they are understood.
|
|
207
215
|
|
|
208
216
|
RANGE SELECTION
|
|
209
217
|
Prefer older, resolved history. Avoid the newest active working slice unless it is clearly done.
|
|
@@ -225,7 +233,7 @@ If any range is cleanly closed and unlikely to be needed again, use the \`compre
|
|
|
225
233
|
If direction has shifted, compress earlier ranges that are now less relevant.
|
|
226
234
|
|
|
227
235
|
Do not defer this across another batch of searches, reads, CI log fetches, or tests. The next safe action should be compression whenever a closed slice exists.
|
|
228
|
-
High-priority stale
|
|
236
|
+
High-priority stale shell/read/repo/web outputs and understood passing logs must be compressed once no exact raw text is needed.
|
|
229
237
|
|
|
230
238
|
Prefer small, closed-range compressions over one broad compression.
|
|
231
239
|
Use message-mode compression for isolated large stale messages.
|
|
@@ -239,7 +247,7 @@ Keep active context uncompressed.
|
|
|
239
247
|
export const ITERATION_NUDGE = `<dcp-system-reminder>
|
|
240
248
|
ACTION REQUIRED: You've been iterating for a while after the last user message.
|
|
241
249
|
|
|
242
|
-
Pause before the next non-atomic tool call. If there is a closed portion that is unlikely to be referenced immediately (for example, finished research before implementation, completed CI-log triage, a verified fix, or a dead-end investigation), use the \`compress\` tool on it now.
|
|
250
|
+
Pause before the next non-atomic tool call. If there is a closed portion that is unlikely to be referenced immediately (for example, finished research before implementation, completed config edit, completed CI-log triage, a verified fix, or a dead-end investigation), use the \`compress\` tool on it now.
|
|
243
251
|
|
|
244
252
|
Do not keep accumulating tool outputs while a completed slice remains raw. If a range is closed, compression is the next safe action.
|
|
245
253
|
If a completed implementation+verification slice exists, compress it before replying or starting another task.
|
|
@@ -17,6 +17,55 @@ interface CandidateBoundary {
|
|
|
17
17
|
isSystemReminder: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function hasAddressableSnapshot(state: DcpState): boolean {
|
|
21
|
+
return state.messageMetaSnapshot.size > 0 || state.messageIdSnapshot.size > 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isActiveBlockId(blockId: number, state: DcpState): boolean {
|
|
25
|
+
return state.compressionBlocks.some((block) => block.id === blockId && block.active);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findCurrentMessageId(msg: any, state: DcpState): string | undefined {
|
|
29
|
+
const role = msg?.role ?? "";
|
|
30
|
+
const timestamp = msg?.timestamp;
|
|
31
|
+
if (!Number.isFinite(timestamp)) return undefined;
|
|
32
|
+
|
|
33
|
+
for (const [id, meta] of state.messageMetaSnapshot) {
|
|
34
|
+
if (meta.timestamp === timestamp && meta.role === role && meta.blockId === undefined) return id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const [id, ts] of state.messageIdSnapshot) {
|
|
38
|
+
if (ts === timestamp) return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveAddressableBoundaryId(
|
|
45
|
+
msg: any,
|
|
46
|
+
state: DcpState,
|
|
47
|
+
options: { allowBlocks: boolean },
|
|
48
|
+
): { id: string; blockId?: number; text: string } | null {
|
|
49
|
+
const text = messageText(msg);
|
|
50
|
+
const blockId = extractBlockId(text);
|
|
51
|
+
if (blockId !== undefined) {
|
|
52
|
+
if (options.allowBlocks && isActiveBlockId(blockId, state)) return { id: `b${blockId}`, blockId, text };
|
|
53
|
+
if (!hasAddressableSnapshot(state) && options.allowBlocks) return { id: `b${blockId}`, blockId, text };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const messageId = extractMessageId(text);
|
|
58
|
+
if (!messageId) return null;
|
|
59
|
+
if (!hasAddressableSnapshot(state)) return { id: messageId, text };
|
|
60
|
+
|
|
61
|
+
if (state.messageMetaSnapshot.has(messageId) || state.messageIdSnapshot.has(messageId)) {
|
|
62
|
+
return { id: messageId, text };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const currentId = findCurrentMessageId(msg, state);
|
|
66
|
+
return currentId ? { id: currentId, text } : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
20
69
|
export function detectCompressionCandidate(
|
|
21
70
|
messages: any[],
|
|
22
71
|
_state: DcpState,
|
|
@@ -29,19 +78,16 @@ export function detectCompressionCandidate(
|
|
|
29
78
|
|
|
30
79
|
const boundaries: CandidateBoundary[] = [];
|
|
31
80
|
for (const msg of messages) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const messageId = extractMessageId(text);
|
|
35
|
-
const id = blockId !== undefined ? `b${blockId}` : messageId;
|
|
36
|
-
if (!id) continue;
|
|
81
|
+
const boundary = resolveAddressableBoundaryId(msg, _state, { allowBlocks: true });
|
|
82
|
+
if (!boundary) continue;
|
|
37
83
|
if (!Number.isFinite(msg.timestamp)) continue;
|
|
38
84
|
boundaries.push({
|
|
39
|
-
id,
|
|
85
|
+
id: boundary.id,
|
|
40
86
|
role: msg.role ?? "",
|
|
41
87
|
timestamp: msg.timestamp,
|
|
42
88
|
tokenEstimate: estimateMessageTokens(msg),
|
|
43
|
-
blockId,
|
|
44
|
-
isSystemReminder: text.includes("<dcp-system-reminder>"),
|
|
89
|
+
blockId: boundary.blockId,
|
|
90
|
+
isSystemReminder: boundary.text.includes("<dcp-system-reminder>"),
|
|
45
91
|
});
|
|
46
92
|
}
|
|
47
93
|
|
|
@@ -102,6 +148,7 @@ export function formatCompressionCandidateHint(candidate: CompressionCandidate):
|
|
|
102
148
|
|
|
103
149
|
export function detectMessageCompressionCandidates(
|
|
104
150
|
messages: any[],
|
|
151
|
+
state: DcpState,
|
|
105
152
|
config: DcpConfig,
|
|
106
153
|
contextPercent: number,
|
|
107
154
|
): MessageCompressionCandidate[] {
|
|
@@ -111,18 +158,15 @@ export function detectMessageCompressionCandidates(
|
|
|
111
158
|
|
|
112
159
|
const boundaries: CandidateBoundary[] = [];
|
|
113
160
|
for (const msg of messages) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const messageId = extractMessageId(text);
|
|
117
|
-
if (!messageId || blockId !== undefined) continue;
|
|
161
|
+
const boundary = resolveAddressableBoundaryId(msg, state, { allowBlocks: false });
|
|
162
|
+
if (!boundary || boundary.blockId !== undefined) continue;
|
|
118
163
|
if (!Number.isFinite(msg.timestamp)) continue;
|
|
119
164
|
boundaries.push({
|
|
120
|
-
id:
|
|
165
|
+
id: boundary.id,
|
|
121
166
|
role: msg.role ?? "",
|
|
122
167
|
timestamp: msg.timestamp,
|
|
123
168
|
tokenEstimate: estimateMessageTokens(msg),
|
|
124
|
-
|
|
125
|
-
isSystemReminder: text.includes("<dcp-system-reminder>"),
|
|
169
|
+
isSystemReminder: boundary.text.includes("<dcp-system-reminder>"),
|
|
126
170
|
});
|
|
127
171
|
}
|
|
128
172
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { matchingModelEntries, type DcpConfig } from "./config.js";
|
|
2
2
|
import type { DcpState } from "./state.js";
|
|
3
3
|
import type { NudgeThresholds } from "./pruner-types.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
@@ -107,8 +107,6 @@ export function resolveContextThresholds(
|
|
|
107
107
|
modelKeys: Array<string | undefined> = [],
|
|
108
108
|
contextWindow?: number,
|
|
109
109
|
): Required<NudgeThresholds> {
|
|
110
|
-
const candidates = modelKeys.filter((key): key is string => typeof key === "string" && key.length > 0);
|
|
111
|
-
|
|
112
110
|
const resolveThresholdValue = (value: number | string | undefined): number | undefined => {
|
|
113
111
|
if (typeof value === "string") {
|
|
114
112
|
const trimmed = value.trim();
|
|
@@ -130,12 +128,12 @@ export function resolveContextThresholds(
|
|
|
130
128
|
};
|
|
131
129
|
|
|
132
130
|
const resolveOverride = (map: Record<string, number | string> | undefined): number | undefined => {
|
|
133
|
-
|
|
134
|
-
for (const
|
|
135
|
-
const value = resolveThresholdValue(
|
|
136
|
-
if (value !== undefined)
|
|
131
|
+
let resolved: number | undefined;
|
|
132
|
+
for (const [, rawValue] of matchingModelEntries(map, modelKeys)) {
|
|
133
|
+
const value = resolveThresholdValue(rawValue);
|
|
134
|
+
if (value !== undefined) resolved = value;
|
|
137
135
|
}
|
|
138
|
-
return
|
|
136
|
+
return resolved;
|
|
139
137
|
};
|
|
140
138
|
|
|
141
139
|
const min =
|
|
@@ -157,7 +157,7 @@ function formatCandidateActions(
|
|
|
157
157
|
|
|
158
158
|
if (candidate) {
|
|
159
159
|
parts.push(
|
|
160
|
-
`Recommended range candidate: ${candidate.startId}..${candidate.endId} (${candidate.messageCount} messages, ~${candidate.estimatedTokens} tokens, ${candidate.reason}). Compress this before the next search/read/test if it is closed.`,
|
|
160
|
+
`Recommended range candidate: ${candidate.startId}..${candidate.endId} (${candidate.messageCount} messages, ~${candidate.estimatedTokens} tokens, ${candidate.reason}). Compress this before the next search/read/test/web lookup if it is closed.`,
|
|
161
161
|
);
|
|
162
162
|
if (candidate.includedBlockIds.length > 0) {
|
|
163
163
|
parts.push(
|
|
@@ -172,7 +172,7 @@ function formatCandidateActions(
|
|
|
172
172
|
parts.push(
|
|
173
173
|
`Recommended message candidates: ${listedMessages
|
|
174
174
|
.map((item) => `${item.messageId} (${item.priority}, ${item.role}, ~${item.estimatedTokens} tokens)`)
|
|
175
|
-
.join(", ")}. High-priority stale messages MUST be compressed once their full text is no longer needed. Batch multiple messages in one compress call when possible.`,
|
|
175
|
+
.join(", ")}. High-priority stale messages MUST be compressed once their full text is no longer needed; passing logs should become command + pass/fail + follow-up status only. Batch multiple messages in one compress call when possible.`,
|
|
176
176
|
);
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -180,7 +180,7 @@ function formatCandidateActions(
|
|
|
180
180
|
if (activeBlocks) parts.push(activeBlocks);
|
|
181
181
|
|
|
182
182
|
if (parts.length === 0) {
|
|
183
|
-
parts.push("No automatic candidate is certain; scan the older closed context now and compress any completed research, implementation, verification, CI-log inspection, or dead-end debugging slice before accumulating more tool output.");
|
|
183
|
+
parts.push("No automatic candidate is certain; scan the older closed context now and compress any completed research, implementation, config/doc edit, verification, CI-log inspection, or dead-end debugging slice before accumulating more tool output.");
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
return [`CONCRETE NEXT ACTION`, ...parts].join("\n");
|
|
@@ -19,11 +19,61 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
|
|
|
19
19
|
"dcp": {
|
|
20
20
|
"enabled": true,
|
|
21
21
|
"manualMode": { "enabled": false, "automaticStrategies": true },
|
|
22
|
+
"modelOverrides": {
|
|
23
|
+
"openai-codex/gpt-5.5": {
|
|
24
|
+
"compress": {
|
|
25
|
+
"minContextPercent": "28%",
|
|
26
|
+
"maxContextPercent": "48%"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"openai-codex/gpt-5.4-mini": {
|
|
30
|
+
"compress": {
|
|
31
|
+
"minContextPercent": "20%",
|
|
32
|
+
"maxContextPercent": "38%"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"zai/*": {
|
|
36
|
+
"compress": {
|
|
37
|
+
"minContextPercent": "16%",
|
|
38
|
+
"maxContextPercent": "30%"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"antigravity/*sonnet*": {
|
|
42
|
+
"compress": {
|
|
43
|
+
"minContextPercent": "22%",
|
|
44
|
+
"maxContextPercent": "40%"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"antigravity/gemini-3.1-pro*": {
|
|
48
|
+
"compress": {
|
|
49
|
+
"minContextPercent": "24%",
|
|
50
|
+
"maxContextPercent": "42%"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"antigravity/gemini-3-flash*": {
|
|
54
|
+
"compress": {
|
|
55
|
+
"minContextPercent": "18%",
|
|
56
|
+
"maxContextPercent": "34%"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"antigravity/gemini-2.5-flash*": {
|
|
60
|
+
"compress": {
|
|
61
|
+
"minContextPercent": "18%",
|
|
62
|
+
"maxContextPercent": "32%"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"antigravity/antigravity-claude-opus-4-6-thinking": {
|
|
66
|
+
"compress": {
|
|
67
|
+
"minContextPercent": "26%",
|
|
68
|
+
"maxContextPercent": "44%"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
22
72
|
"compress": {
|
|
23
73
|
"minContextPercent": "20%",
|
|
24
74
|
"maxContextPercent": "55%",
|
|
25
75
|
"nudgeFrequency": 1,
|
|
26
|
-
"iterationNudgeThreshold":
|
|
76
|
+
"iterationNudgeThreshold": 4,
|
|
27
77
|
"autoCandidates": { "minContextPercent": 0.2 },
|
|
28
78
|
"messageMode": { "minContextPercent": 0.2 }
|
|
29
79
|
}
|