pi-ui-extend 1.0.41 → 1.0.42
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 +10 -2
- package/dist/app/app.d.ts +3 -0
- package/dist/app/app.js +26 -0
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +6 -1
- package/dist/app/commands/command-registry.js +1 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +59 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +19 -1
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/types.d.ts +5 -0
- package/dist/default-pix-config.js +10 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +80 -58
- package/dist/schemas/pi-tools-suite-schema.js +43 -78
- package/dist/schemas/pix-schema.d.ts +7 -0
- package/dist/schemas/pix-schema.js +14 -0
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +6 -0
- package/docs/desktop-mvp.md +12 -1
- package/docs/desktop-task-manager.md +93 -82
- package/external/pi-tools-suite/README.md +37 -50
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +19 -0
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +40 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +38 -27
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +14 -70
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +122 -312
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +15 -2
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +7 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -4
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/config.ts +41 -1
- package/external/pi-tools-suite/src/context-gateway/config.ts +11 -2
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +73 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +3 -3
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +9 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +96 -44
- package/external/pi-tools-suite/src/dcp/commands.ts +18 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +170 -45
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/config.ts +56 -13
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +1 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +13 -9
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -4
- package/package.json +4 -2
- package/schemas/pi-tools-suite.json +427 -280
- package/schemas/pix.json +24 -0
- package/schemas/tasks.json +132 -0
- package/skills/spec-wiki/SKILL.md +55 -0
- package/skills/spec-wiki/evals/evals.json +132 -0
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
- package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
- package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
- package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
- package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
- package/skills/spec-wiki/evals/trigger-evals.json +94 -0
- package/skills/spec-wiki/references/bootstrap.md +100 -0
- package/skills/spec-wiki/references/focused-spec.md +117 -0
- package/skills/spec-wiki/references/index-schema.md +330 -0
- package/skills/spec-wiki/references/maintenance.md +134 -0
- package/skills/spec-wiki/references/query.md +74 -0
- package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/spec-lite/SKILL.md +0 -140
|
@@ -775,6 +775,80 @@ export async function generateModelSummary(
|
|
|
775
775
|
return { attempts }
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
+
export interface PreparedCompressionSummary {
|
|
779
|
+
text: string
|
|
780
|
+
representation: "model" | "extractive" | "extractive-fallback"
|
|
781
|
+
sourceManifest: SummarySourceItem[]
|
|
782
|
+
sourceHash: string
|
|
783
|
+
sourceCoverage: SummarySourceCoverage
|
|
784
|
+
summarizerModelRef?: string
|
|
785
|
+
summarizerAttempts?: ModelSummaryAttempt[]
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Shared summary preparation for automatic and explicit compression. The
|
|
790
|
+
* configured model chain is attempted first; if it is absent/unavailable the
|
|
791
|
+
* deterministic extractive continuation record is the safety floor.
|
|
792
|
+
*/
|
|
793
|
+
export async function prepareCompressionSummary(options: {
|
|
794
|
+
topic: string
|
|
795
|
+
messages: any[]
|
|
796
|
+
candidate?: CompressionCandidate
|
|
797
|
+
modelRefs: string[]
|
|
798
|
+
timeoutMs: number
|
|
799
|
+
modelRegistry?: ModelSummaryRegistry
|
|
800
|
+
signal?: AbortSignal
|
|
801
|
+
sourceManifest?: SummarySourceItem[]
|
|
802
|
+
}): Promise<PreparedCompressionSummary> {
|
|
803
|
+
options.signal?.throwIfAborted()
|
|
804
|
+
const sourceManifest = options.sourceManifest ?? buildSummarySourceManifest(options.messages)
|
|
805
|
+
let modelResult: ModelSummaryResult | undefined
|
|
806
|
+
if (options.modelRefs.length > 0) {
|
|
807
|
+
modelResult = await generateModelSummary(
|
|
808
|
+
options.modelRefs,
|
|
809
|
+
options.modelRegistry,
|
|
810
|
+
options.signal,
|
|
811
|
+
options.topic,
|
|
812
|
+
options.messages,
|
|
813
|
+
options.timeoutMs,
|
|
814
|
+
sourceManifest,
|
|
815
|
+
)
|
|
816
|
+
options.signal?.throwIfAborted()
|
|
817
|
+
}
|
|
818
|
+
const fallbackCandidate = options.candidate ?? {
|
|
819
|
+
startId: "generated",
|
|
820
|
+
endId: "generated",
|
|
821
|
+
messageCount: sourceManifest.length,
|
|
822
|
+
estimatedTokens: options.messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0),
|
|
823
|
+
includedBlockIds: [],
|
|
824
|
+
reason: "explicit/generated summary source",
|
|
825
|
+
}
|
|
826
|
+
const text = modelResult?.text ?? buildExtractiveSummary(
|
|
827
|
+
options.topic,
|
|
828
|
+
fallbackCandidate,
|
|
829
|
+
sourceManifest,
|
|
830
|
+
)
|
|
831
|
+
if (!modelResult?.text && estimateTokens(text) > 8192) {
|
|
832
|
+
throw new AutoCompressionBlockedError(
|
|
833
|
+
"budget-exhausted",
|
|
834
|
+
"Extractive continuity minimum exceeds its 8192-token budget; no checkpoints were silently dropped",
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
text,
|
|
839
|
+
representation: modelResult?.text
|
|
840
|
+
? "model"
|
|
841
|
+
: options.modelRefs.length > 0
|
|
842
|
+
? "extractive-fallback"
|
|
843
|
+
: "extractive",
|
|
844
|
+
sourceManifest,
|
|
845
|
+
sourceHash: hashSummarySourceManifest(sourceManifest),
|
|
846
|
+
sourceCoverage: summarySourceCoverage(sourceManifest),
|
|
847
|
+
summarizerModelRef: modelResult?.usedModelRef,
|
|
848
|
+
summarizerAttempts: modelResult?.attempts.length ? modelResult.attempts : undefined,
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
778
852
|
function extractAssistantText(result: any): string | undefined {
|
|
779
853
|
const content = result?.content
|
|
780
854
|
if (!Array.isArray(content)) return undefined
|
|
@@ -913,48 +987,26 @@ export async function createAutoCompressionBlock(
|
|
|
913
987
|
}
|
|
914
988
|
}
|
|
915
989
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
settings.timeoutMs,
|
|
937
|
-
sourceManifest,
|
|
938
|
-
)
|
|
939
|
-
assertCurrent()
|
|
940
|
-
summarizerAttempts = modelResult.attempts.length > 0 ? modelResult.attempts : undefined
|
|
941
|
-
if (modelResult.text) {
|
|
942
|
-
summary = modelResult.text
|
|
943
|
-
summaryMode = "model"
|
|
944
|
-
summarizerModelRef = modelResult.usedModelRef
|
|
945
|
-
} else {
|
|
946
|
-
// All configured models failed or returned empty — fall back to the
|
|
947
|
-
// programmatic digest, but mark the mode distinctly so the fallback
|
|
948
|
-
// is visible in DCP debug logs.
|
|
949
|
-
summaryMode = "programmatic_fallback"
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
if (!summary) {
|
|
953
|
-
summary = buildExtractiveSummary(topic, effectiveCandidate, sourceManifest)
|
|
954
|
-
if (estimateTokens(summary) > 8192) {
|
|
955
|
-
throw new AutoCompressionBlockedError("budget-exhausted", "Extractive continuity minimum exceeds its 8192-token budget; no checkpoints were silently dropped")
|
|
956
|
-
}
|
|
957
|
-
}
|
|
990
|
+
const preparedSummary = await prepareCompressionSummary({
|
|
991
|
+
topic,
|
|
992
|
+
messages: messagesInRange,
|
|
993
|
+
candidate: effectiveCandidate,
|
|
994
|
+
modelRefs: settings.summarizerModel,
|
|
995
|
+
timeoutMs: settings.timeoutMs,
|
|
996
|
+
modelRegistry,
|
|
997
|
+
signal,
|
|
998
|
+
})
|
|
999
|
+
assertCurrent()
|
|
1000
|
+
const summary = preparedSummary.text
|
|
1001
|
+
const sourceManifest = preparedSummary.sourceManifest
|
|
1002
|
+
const summaryMode: "programmatic" | "model" | "programmatic_fallback" =
|
|
1003
|
+
preparedSummary.representation === "model"
|
|
1004
|
+
? "model"
|
|
1005
|
+
: preparedSummary.representation === "extractive-fallback"
|
|
1006
|
+
? "programmatic_fallback"
|
|
1007
|
+
: "programmatic"
|
|
1008
|
+
const summarizerModelRef = preparedSummary.summarizerModelRef
|
|
1009
|
+
const summarizerAttempts = preparedSummary.summarizerAttempts
|
|
958
1010
|
|
|
959
1011
|
const workingState = createAutoCompressionWorkingState(state)
|
|
960
1012
|
const anchor = resolveAnchorBoundary(endTimestamp, workingState, endBoundary.stableId)
|
|
@@ -1004,8 +1056,8 @@ export async function createAutoCompressionBlock(
|
|
|
1004
1056
|
: summaryMode === "programmatic_fallback"
|
|
1005
1057
|
? "extractive-fallback"
|
|
1006
1058
|
: "extractive"
|
|
1007
|
-
const sourceHash =
|
|
1008
|
-
const sourceCoverage =
|
|
1059
|
+
const sourceHash = preparedSummary.sourceHash
|
|
1060
|
+
const sourceCoverage = preparedSummary.sourceCoverage
|
|
1009
1061
|
created.block.autoSummaryRepresentation = summaryRepresentation
|
|
1010
1062
|
created.block.sourceHash = sourceHash
|
|
1011
1063
|
created.block.sourceCoverage = sourceCoverage
|
|
@@ -179,6 +179,7 @@ const HELP_TEXT = `DCP — Dynamic Context Pruning
|
|
|
179
179
|
Commands:
|
|
180
180
|
/dcp context — Show context window usage breakdown
|
|
181
181
|
/dcp stats — Show pruning statistics for this session
|
|
182
|
+
/dcp doctor — Show config compatibility diagnostics
|
|
182
183
|
/dcp sweep [N] — Prune last N tool outputs (default: all since last user msg)
|
|
183
184
|
/dcp manual — Show manual mode status
|
|
184
185
|
/dcp manual on — Enable manual mode (disable autonomous compression nudges)
|
|
@@ -189,6 +190,18 @@ function handleHelp(ctx: ExtensionCommandContext): void {
|
|
|
189
190
|
ctx.ui.notify(HELP_TEXT, "info")
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
function handleDoctor(ctx: ExtensionCommandContext, config: DcpConfig): void {
|
|
194
|
+
const issues = config.issues ?? []
|
|
195
|
+
const lines = ["DCP Doctor", ""]
|
|
196
|
+
if (issues.length === 0) {
|
|
197
|
+
lines.push("Config: no removed or ignored DCP settings detected.")
|
|
198
|
+
} else {
|
|
199
|
+
lines.push(`Config: ${issues.length} removed/ignored setting${issues.length === 1 ? "" : "s"}:`)
|
|
200
|
+
for (const issue of issues) lines.push(` - ${issue}`)
|
|
201
|
+
}
|
|
202
|
+
ctx.ui.notify(lines.join("\n"), issues.length > 0 ? "warning" : "info")
|
|
203
|
+
}
|
|
204
|
+
|
|
192
205
|
// ---------------------------------------------------------------------------
|
|
193
206
|
// Context usage
|
|
194
207
|
// ---------------------------------------------------------------------------
|
|
@@ -434,6 +447,7 @@ export function registerCommands(
|
|
|
434
447
|
const subcommands: AutocompleteItem[] = [
|
|
435
448
|
{ value: "context", label: "context", description: "Show context window usage breakdown" },
|
|
436
449
|
{ value: "stats", label: "stats", description: "Show pruning statistics" },
|
|
450
|
+
{ value: "doctor", label: "doctor", description: "Show config compatibility diagnostics" },
|
|
437
451
|
{ value: "sweep", label: "sweep", description: "Prune tool outputs" },
|
|
438
452
|
{ value: "manual", label: "manual", description: "Toggle manual mode" },
|
|
439
453
|
{ value: "compress", label: "compress", description: "Trigger LLM compression" },
|
|
@@ -469,6 +483,10 @@ export function registerCommands(
|
|
|
469
483
|
handleStats(pi, ctx, state)
|
|
470
484
|
break
|
|
471
485
|
|
|
486
|
+
case "doctor":
|
|
487
|
+
handleDoctor(ctx, config)
|
|
488
|
+
break
|
|
489
|
+
|
|
472
490
|
case "sweep": {
|
|
473
491
|
const rawN = parts[1] !== undefined ? parseInt(parts[1], 10) : 0
|
|
474
492
|
const n = isNaN(rawN) || rawN < 0 ? 0 : rawN
|
|
@@ -30,8 +30,55 @@ import {
|
|
|
30
30
|
detectToolGroupSpans,
|
|
31
31
|
findConversationIndexEntry,
|
|
32
32
|
} from "./conversation-index.js"
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
previewManualCompressionProjection,
|
|
35
|
+
verifiedManualCompressionMessages,
|
|
36
|
+
} from "./compression-preview.js"
|
|
34
37
|
import { settleCompressionProgress } from "./compression-progress.js"
|
|
38
|
+
import { estimateMessageTokens } from "./pruner-metadata.js"
|
|
39
|
+
import {
|
|
40
|
+
buildSummarySourceManifest,
|
|
41
|
+
hashSummarySourceManifest,
|
|
42
|
+
prepareCompressionSummary,
|
|
43
|
+
type ModelSummaryAttempt,
|
|
44
|
+
} from "./auto-compress.js"
|
|
45
|
+
|
|
46
|
+
export const COMPRESS_TOOL_PARAMETERS = Type.Object({
|
|
47
|
+
topic: Type.String({
|
|
48
|
+
description:
|
|
49
|
+
"Short label (3-5 words) for display - e.g., 'Auth System Exploration'",
|
|
50
|
+
}),
|
|
51
|
+
ranges: Type.Optional(Type.Array(
|
|
52
|
+
Type.Object({
|
|
53
|
+
startId: Type.String({
|
|
54
|
+
description:
|
|
55
|
+
"First ID (mNNN/bN); never start inside a tool group—include its calling assistant.",
|
|
56
|
+
}),
|
|
57
|
+
endId: Type.String({
|
|
58
|
+
description:
|
|
59
|
+
"Last ID (mNNN/bN); include all results of the final tool group, including parallel calls.",
|
|
60
|
+
}),
|
|
61
|
+
summary: Type.Optional(Type.String({
|
|
62
|
+
description: "Optional; omit to use the configured DCP summarizer.",
|
|
63
|
+
})),
|
|
64
|
+
}),
|
|
65
|
+
{ description: "One or more ranges to compress" },
|
|
66
|
+
)),
|
|
67
|
+
messages: Type.Optional(Type.Array(
|
|
68
|
+
Type.Object({
|
|
69
|
+
messageId: Type.String({
|
|
70
|
+
description: "Raw message ID to compress individually (e.g. m001)",
|
|
71
|
+
}),
|
|
72
|
+
topic: Type.Optional(Type.String({
|
|
73
|
+
description: "Short label for this one-message summary; defaults to top-level topic",
|
|
74
|
+
})),
|
|
75
|
+
summary: Type.Optional(Type.String({
|
|
76
|
+
description: "Optional; omit to use the configured DCP summarizer.",
|
|
77
|
+
})),
|
|
78
|
+
}),
|
|
79
|
+
{ description: "Individual raw messages to compress surgically" },
|
|
80
|
+
)),
|
|
81
|
+
})
|
|
35
82
|
|
|
36
83
|
type MessageSkipKind =
|
|
37
84
|
| "duplicate"
|
|
@@ -51,7 +98,7 @@ interface MessageSkipIssue {
|
|
|
51
98
|
interface ResolvedRangePlan {
|
|
52
99
|
startId: string
|
|
53
100
|
endId: string
|
|
54
|
-
summary
|
|
101
|
+
summary?: string
|
|
55
102
|
startTimestamp: number
|
|
56
103
|
endTimestamp: number
|
|
57
104
|
startMessageId?: string
|
|
@@ -63,7 +110,7 @@ interface ResolvedRangePlan {
|
|
|
63
110
|
interface ResolvedMessagePlan {
|
|
64
111
|
messageId: string
|
|
65
112
|
topic: string
|
|
66
|
-
summary
|
|
113
|
+
summary?: string
|
|
67
114
|
timestamp: number
|
|
68
115
|
stableId?: string
|
|
69
116
|
sourceMembers: CompressionMember[]
|
|
@@ -72,6 +119,33 @@ interface ResolvedMessagePlan {
|
|
|
72
119
|
|
|
73
120
|
type ResolvedRangeBoundaries = Omit<ResolvedRangePlan, "sourceMembers" | "mutationMembers">
|
|
74
121
|
|
|
122
|
+
type ManualSummaryMode = "explicit" | "model" | "extractive" | "extractive-fallback"
|
|
123
|
+
|
|
124
|
+
function verifiedSelectionMessages(
|
|
125
|
+
state: DcpState,
|
|
126
|
+
sourceMembers: CompressionMember[],
|
|
127
|
+
): any[] {
|
|
128
|
+
const projection = verifiedManualCompressionMessages(state)
|
|
129
|
+
const byStableId = new Map(
|
|
130
|
+
state.conversationIndexSnapshot.map((entry) => [entry.stableId, entry] as const),
|
|
131
|
+
)
|
|
132
|
+
return sourceMembers.map((member) => {
|
|
133
|
+
const entry = byStableId.get(member.stableId)
|
|
134
|
+
if (!entry || entry.contentHash !== member.hash || !Number.isSafeInteger(entry.index)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Manual compression summary source ${member.stableId} no longer matches the verified provider projection; refresh context and retry.`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
const message = projection[entry.index]
|
|
140
|
+
if (!message) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Manual compression summary source ${member.stableId} is missing from the verified provider projection; refresh context and retry.`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
return message
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
75
149
|
function validateNonOverlappingRanges(plans: ResolvedRangeBoundaries[], state: DcpState): void {
|
|
76
150
|
const sorted = [...plans].sort((a, b) =>
|
|
77
151
|
compareCompressionBoundaries(
|
|
@@ -246,43 +320,7 @@ export function registerCompressTool(
|
|
|
246
320
|
label: COMPRESS_TOOL_DESCRIPTION.label,
|
|
247
321
|
description: COMPRESS_TOOL_DESCRIPTION.description,
|
|
248
322
|
promptSnippet: COMPRESS_TOOL_DESCRIPTION.promptSnippet ?? "Compress ranges of conversation into summaries to manage context",
|
|
249
|
-
parameters:
|
|
250
|
-
topic: Type.String({
|
|
251
|
-
description:
|
|
252
|
-
"Short label (3-5 words) for display - e.g., 'Auth System Exploration'",
|
|
253
|
-
}),
|
|
254
|
-
ranges: Type.Optional(Type.Array(
|
|
255
|
-
Type.Object({
|
|
256
|
-
startId: Type.String({
|
|
257
|
-
description:
|
|
258
|
-
"First ID (mNNN/bN); never start inside a tool group—include its calling assistant.",
|
|
259
|
-
}),
|
|
260
|
-
endId: Type.String({
|
|
261
|
-
description:
|
|
262
|
-
"Last ID (mNNN/bN); include all results of the final tool group, including parallel calls.",
|
|
263
|
-
}),
|
|
264
|
-
summary: Type.String({
|
|
265
|
-
description:
|
|
266
|
-
"Continuation-focused technical summary; avoid raw JSON/code/diffs unless a short literal is required",
|
|
267
|
-
}),
|
|
268
|
-
}),
|
|
269
|
-
{ description: "One or more ranges to compress" },
|
|
270
|
-
)),
|
|
271
|
-
messages: Type.Optional(Type.Array(
|
|
272
|
-
Type.Object({
|
|
273
|
-
messageId: Type.String({
|
|
274
|
-
description: "Raw message ID to compress individually (e.g. m001)",
|
|
275
|
-
}),
|
|
276
|
-
topic: Type.Optional(Type.String({
|
|
277
|
-
description: "Short label for this one-message summary; defaults to top-level topic",
|
|
278
|
-
})),
|
|
279
|
-
summary: Type.String({
|
|
280
|
-
description: "Continuation-focused technical summary replacing this raw message; avoid raw JSON/code/diffs unless required",
|
|
281
|
-
}),
|
|
282
|
-
}),
|
|
283
|
-
{ description: "Individual raw messages to compress surgically" },
|
|
284
|
-
)),
|
|
285
|
-
}),
|
|
323
|
+
parameters: COMPRESS_TOOL_PARAMETERS,
|
|
286
324
|
|
|
287
325
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
288
326
|
_signal?.throwIfAborted()
|
|
@@ -462,6 +500,93 @@ export function registerCompressTool(
|
|
|
462
500
|
}
|
|
463
501
|
assertDisjointSelections(rangePlans, preflightMessages)
|
|
464
502
|
|
|
503
|
+
const summaryPreparations: Array<{
|
|
504
|
+
selection: string
|
|
505
|
+
mode: ManualSummaryMode
|
|
506
|
+
summarizerModelRef?: string
|
|
507
|
+
attempts?: Array<{ ref: string; outcome: ModelSummaryAttempt["outcome"] }>
|
|
508
|
+
}> = []
|
|
509
|
+
const resolveSummary = async (input: {
|
|
510
|
+
startId: string
|
|
511
|
+
endId: string
|
|
512
|
+
topic: string
|
|
513
|
+
sourceMembers: CompressionMember[]
|
|
514
|
+
explicitSummary?: string
|
|
515
|
+
}): Promise<string> => {
|
|
516
|
+
const selection = input.startId === input.endId
|
|
517
|
+
? input.startId
|
|
518
|
+
: `${input.startId}..${input.endId}`
|
|
519
|
+
if (typeof input.explicitSummary === "string") {
|
|
520
|
+
summaryPreparations.push({ selection, mode: "explicit" })
|
|
521
|
+
return input.explicitSummary
|
|
522
|
+
}
|
|
523
|
+
const sourceMessages = verifiedSelectionMessages(state, input.sourceMembers)
|
|
524
|
+
const sourceManifest = buildSummarySourceManifest(sourceMessages)
|
|
525
|
+
const sourceHash = hashSummarySourceManifest(sourceManifest)
|
|
526
|
+
const prepared = await prepareCompressionSummary({
|
|
527
|
+
topic: input.topic,
|
|
528
|
+
messages: sourceMessages,
|
|
529
|
+
candidate: {
|
|
530
|
+
startId: input.startId,
|
|
531
|
+
endId: input.endId,
|
|
532
|
+
messageCount: sourceMessages.length,
|
|
533
|
+
estimatedTokens: sourceMessages.reduce((sum, message) => sum + estimateMessageTokens(message), 0),
|
|
534
|
+
includedBlockIds: [],
|
|
535
|
+
reason: "manual delegated summary",
|
|
536
|
+
},
|
|
537
|
+
modelRefs: effectiveConfig.compress.autoCompress.summarizerModel,
|
|
538
|
+
timeoutMs: effectiveConfig.compress.autoCompress.timeoutMs,
|
|
539
|
+
modelRegistry: (ctx as any).modelRegistry,
|
|
540
|
+
signal: _signal,
|
|
541
|
+
sourceManifest,
|
|
542
|
+
})
|
|
543
|
+
assertCurrent()
|
|
544
|
+
const refreshedMessages = verifiedSelectionMessages(state, input.sourceMembers)
|
|
545
|
+
const refreshedHash = hashSummarySourceManifest(buildSummarySourceManifest(refreshedMessages))
|
|
546
|
+
if (refreshedHash !== sourceHash || prepared.sourceHash !== sourceHash) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`stale_plan: manual compression summary source changed while preparing ${selection}`,
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
const attempts = prepared.summarizerAttempts?.map(({ ref, outcome }) => ({ ref, outcome }))
|
|
552
|
+
summaryPreparations.push({
|
|
553
|
+
selection,
|
|
554
|
+
mode: prepared.representation,
|
|
555
|
+
summarizerModelRef: prepared.summarizerModelRef,
|
|
556
|
+
attempts,
|
|
557
|
+
})
|
|
558
|
+
log("compress.summary_prepared", {
|
|
559
|
+
toolCallId: _toolCallId,
|
|
560
|
+
selection,
|
|
561
|
+
mode: prepared.representation,
|
|
562
|
+
summarizerModelRef: prepared.summarizerModelRef,
|
|
563
|
+
summarizerAttempts: prepared.summarizerAttempts,
|
|
564
|
+
sourceHash: prepared.sourceHash,
|
|
565
|
+
sourceCoverage: prepared.sourceCoverage,
|
|
566
|
+
})
|
|
567
|
+
return prepared.text
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for (const range of rangePlans) {
|
|
571
|
+
range.summary = await resolveSummary({
|
|
572
|
+
startId: range.startId,
|
|
573
|
+
endId: range.endId,
|
|
574
|
+
topic: params.topic,
|
|
575
|
+
sourceMembers: range.sourceMembers,
|
|
576
|
+
explicitSummary: range.summary,
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
for (const plan of preflightMessages) {
|
|
580
|
+
plan.summary = await resolveSummary({
|
|
581
|
+
startId: plan.messageId,
|
|
582
|
+
endId: plan.messageId,
|
|
583
|
+
topic: plan.topic,
|
|
584
|
+
sourceMembers: plan.sourceMembers,
|
|
585
|
+
explicitSummary: plan.summary,
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
const preflightMessageById = new Map(preflightMessages.map((plan) => [plan.messageId, plan] as const))
|
|
589
|
+
|
|
465
590
|
for (const range of rangePlans) {
|
|
466
591
|
try {
|
|
467
592
|
const anchor = resolveAnchorBoundary(range.endTimestamp, workingState, range.endMessageId)
|
|
@@ -478,7 +603,7 @@ export function registerCompressTool(
|
|
|
478
603
|
|
|
479
604
|
const created = createRangeCompressionBlock({
|
|
480
605
|
topic: params.topic,
|
|
481
|
-
summary: range.summary
|
|
606
|
+
summary: range.summary!,
|
|
482
607
|
startTimestamp: range.startTimestamp,
|
|
483
608
|
endTimestamp: range.endTimestamp,
|
|
484
609
|
startMessageId: range.startMessageId,
|
|
@@ -521,7 +646,6 @@ export function registerCompressTool(
|
|
|
521
646
|
const seenMessageIds = new Set<string>()
|
|
522
647
|
|
|
523
648
|
for (const entry of messages) {
|
|
524
|
-
const { summary } = entry
|
|
525
649
|
const messageId = typeof entry.messageId === "string" ? entry.messageId.trim() : ""
|
|
526
650
|
if (seenMessageIds.has(messageId)) {
|
|
527
651
|
skippedMessageIssues.push({ kind: "duplicate", messageId })
|
|
@@ -604,7 +728,7 @@ export function registerCompressTool(
|
|
|
604
728
|
|
|
605
729
|
const created = createRangeCompressionBlock({
|
|
606
730
|
topic: entry.topic ?? params.topic,
|
|
607
|
-
summary
|
|
731
|
+
summary: preflightMessageById.get(messageId)?.summary!,
|
|
608
732
|
startTimestamp: meta.timestamp,
|
|
609
733
|
endTimestamp: meta.timestamp,
|
|
610
734
|
startMessageId: meta.stableId,
|
|
@@ -620,8 +744,8 @@ export function registerCompressTool(
|
|
|
620
744
|
validatePlaceholders: false,
|
|
621
745
|
expandPlaceholders: false,
|
|
622
746
|
preparedProtectedFragments,
|
|
623
|
-
sourceMembers:
|
|
624
|
-
mutationMembers:
|
|
747
|
+
sourceMembers: preflightMessageById.get(messageId)?.sourceMembers,
|
|
748
|
+
mutationMembers: preflightMessageById.get(messageId)?.mutationMembers,
|
|
625
749
|
})
|
|
626
750
|
const block = created.block
|
|
627
751
|
newBlockIds.push(block.id)
|
|
@@ -735,6 +859,7 @@ export function registerCompressTool(
|
|
|
735
859
|
}
|
|
736
860
|
const resultDetails = {
|
|
737
861
|
...visualDetails,
|
|
862
|
+
summaryPreparations,
|
|
738
863
|
projectedBeforeTokens: projection?.projectedBeforeTokens,
|
|
739
864
|
projectedAfterTokens: projection?.projectedAfterTokens,
|
|
740
865
|
netGain: projection?.netGain,
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import type { DcpConfig } from "./config.js"
|
|
9
9
|
import { estimateTokens } from "./pruner-metadata.js"
|
|
10
10
|
import { isToolRecordProtected } from "./pruner-tools.js"
|
|
11
|
+
import { toolRecordContinuity } from "./protected-continuity.js"
|
|
11
12
|
import { compareConversationStableIds, buildExactRangeMembership } from "./conversation-index.js"
|
|
12
13
|
import { createHash } from "node:crypto"
|
|
13
14
|
import { open, realpath } from "node:fs/promises"
|
|
@@ -570,10 +571,10 @@ function collectCurrentProtectedFragments(
|
|
|
570
571
|
if (!toolCallId) continue
|
|
571
572
|
const record = state.toolCalls.get(toolCallId)
|
|
572
573
|
if (!record || record.toolName === "compress" || !isToolRecordProtected(record, config)) continue
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
fragments.push(compressionProtectedFragment("tool", `tool:${toolCallId}`,
|
|
574
|
+
const continuity = toolRecordContinuity(record, config)
|
|
575
|
+
const text = continuity.text?.trim()
|
|
576
|
+
if (!text) continue
|
|
577
|
+
fragments.push(compressionProtectedFragment("tool", `tool:${toolCallId}`, text))
|
|
577
578
|
}
|
|
578
579
|
return mergeProtectedFragments(fragments)
|
|
579
580
|
}
|
|
@@ -657,51 +658,6 @@ function appendProtectedFragmentLedger(summary: string, fragments: CompressionPr
|
|
|
657
658
|
missing.map((fragment) => `\n\n${fragment.text}`).join("")
|
|
658
659
|
}
|
|
659
660
|
|
|
660
|
-
function appendProtectedToolOutputs(
|
|
661
|
-
summary: string,
|
|
662
|
-
startTimestamp: number,
|
|
663
|
-
endTimestamp: number,
|
|
664
|
-
state: DcpState,
|
|
665
|
-
config: DcpConfig,
|
|
666
|
-
ids: { startMessageId?: string; endMessageId?: string } = {},
|
|
667
|
-
): string {
|
|
668
|
-
const protectedOutputs: string[] = []
|
|
669
|
-
const seenToolCallIds = new Set<string>()
|
|
670
|
-
const boundaries = rangeBoundaries(startTimestamp, endTimestamp, ids)
|
|
671
|
-
|
|
672
|
-
for (const meta of state.messageMetaSnapshot.values()) {
|
|
673
|
-
if (meta.blockId !== undefined) continue
|
|
674
|
-
if (!Number.isFinite(meta.timestamp)) continue
|
|
675
|
-
if (!isCompressionBoundaryWithinRange(
|
|
676
|
-
{ timestamp: meta.timestamp, stableId: meta.stableId },
|
|
677
|
-
boundaries.start,
|
|
678
|
-
boundaries.end,
|
|
679
|
-
state,
|
|
680
|
-
)) continue
|
|
681
|
-
if (meta.role !== "toolResult" && meta.role !== "bashExecution") continue
|
|
682
|
-
|
|
683
|
-
const toolCallId = meta.toolCallId
|
|
684
|
-
if (!toolCallId || seenToolCallIds.has(toolCallId)) continue
|
|
685
|
-
const record = state.toolCalls.get(toolCallId)
|
|
686
|
-
if (!record) continue
|
|
687
|
-
|
|
688
|
-
// The compress tool's own JSON accounting output is control-plane data and
|
|
689
|
-
// rarely useful in future summaries; avoid recursively copying it.
|
|
690
|
-
if (record.toolName === "compress") continue
|
|
691
|
-
if (!isToolRecordProtected(record, config)) continue
|
|
692
|
-
|
|
693
|
-
const output = (record.outputText ?? meta.text ?? "").trim()
|
|
694
|
-
if (!output) continue
|
|
695
|
-
|
|
696
|
-
seenToolCallIds.add(toolCallId)
|
|
697
|
-
protectedOutputs.push(`\n### Tool: ${record.toolName}\n${output}`)
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if (protectedOutputs.length === 0) return summary
|
|
701
|
-
const heading = "\n\nThe following protected tool outputs were included in this conversation section:"
|
|
702
|
-
return summary + heading + protectedOutputs.join("")
|
|
703
|
-
}
|
|
704
|
-
|
|
705
661
|
export function estimateVisibleRangeTokens(
|
|
706
662
|
startTimestamp: number,
|
|
707
663
|
endTimestamp: number,
|
|
@@ -945,20 +901,12 @@ export function createRangeCompressionBlock(
|
|
|
945
901
|
ids,
|
|
946
902
|
)
|
|
947
903
|
|
|
948
|
-
const expandedSummary = appendProtectedToolOutputs(
|
|
949
|
-
promptPreservedSummary,
|
|
950
|
-
startTimestamp,
|
|
951
|
-
endTimestamp,
|
|
952
|
-
state,
|
|
953
|
-
config,
|
|
954
|
-
ids,
|
|
955
|
-
)
|
|
956
904
|
const protectedFragments = mergeProtectedFragments(
|
|
957
905
|
collectInheritedProtectedFragments(coveredBlocks),
|
|
958
906
|
collectCurrentProtectedFragments(startTimestamp, endTimestamp, state, config, mode, ids),
|
|
959
907
|
preparedProtectedFragments,
|
|
960
908
|
)
|
|
961
|
-
const ledgerPreservedSummary = appendProtectedFragmentLedger(
|
|
909
|
+
const ledgerPreservedSummary = appendProtectedFragmentLedger(promptPreservedSummary, protectedFragments)
|
|
962
910
|
|
|
963
911
|
const block: CompressionBlock = {
|
|
964
912
|
id: state.nextBlockId++,
|
|
@@ -58,6 +58,15 @@ function verifiedSnapshot(state: DcpState): any[] {
|
|
|
58
58
|
return snapshot
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Return a detached, verified copy of the exact provider projection used by
|
|
63
|
+
* manual compression. Generated summaries must use this source rather than
|
|
64
|
+
* reconstructing history from timestamps or persisted session entries.
|
|
65
|
+
*/
|
|
66
|
+
export function verifiedManualCompressionMessages(state: DcpState): any[] {
|
|
67
|
+
return cloneProjectionMessages(verifiedSnapshot(state))
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
function stripProjectionMetadata(message: any): any {
|
|
62
71
|
const stripped = stripStaleDcpMetadataFromMessage(message)
|
|
63
72
|
return copyDcpProvenance(message, stripped)
|