pi-ui-extend 1.0.40 → 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.
Files changed (186) hide show
  1. package/README.md +10 -2
  2. package/dist/app/app.d.ts +3 -0
  3. package/dist/app/app.js +26 -0
  4. package/dist/app/commands/command-host.d.ts +1 -0
  5. package/dist/app/commands/command-model-actions.js +6 -1
  6. package/dist/app/commands/command-registry.js +3 -3
  7. package/dist/app/commands/command-session-actions.d.ts +0 -1
  8. package/dist/app/commands/command-session-actions.js +30 -15
  9. package/dist/app/commands/reload-context-inventory.d.ts +11 -0
  10. package/dist/app/commands/reload-context-inventory.js +59 -0
  11. package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
  12. package/dist/app/extensions/extension-actions-controller.js +6 -0
  13. package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
  14. package/dist/app/extensions/subagent-catalog-state.js +23 -0
  15. package/dist/app/icons.d.ts +14 -0
  16. package/dist/app/icons.js +33 -0
  17. package/dist/app/rendering/conversation-tool-renderer.js +2 -2
  18. package/dist/app/rendering/dcp-stats.d.ts +6 -1
  19. package/dist/app/rendering/dcp-stats.js +214 -46
  20. package/dist/app/rendering/editor-panels.js +17 -8
  21. package/dist/app/session/lazy-session-manager.js +12 -1
  22. package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
  23. package/dist/app/session/session-lifecycle-controller.js +6 -0
  24. package/dist/app/session/tabs-controller.d.ts +3 -5
  25. package/dist/app/session/tabs-controller.js +31 -22
  26. package/dist/app/subagents/subagents-files.js +60 -1
  27. package/dist/app/subagents/subagents-model.d.ts +15 -1
  28. package/dist/app/subagents/subagents-model.js +52 -17
  29. package/dist/app/types.d.ts +7 -0
  30. package/dist/bundled-extensions/session-title/config.js +1 -1
  31. package/dist/default-pix-config.js +10 -0
  32. package/dist/markdown-format.js +27 -9
  33. package/dist/schemas/index.d.ts +1 -0
  34. package/dist/schemas/index.js +1 -0
  35. package/dist/schemas/pi-tools-suite-schema.d.ts +106 -71
  36. package/dist/schemas/pi-tools-suite-schema.js +68 -88
  37. package/dist/schemas/pix-schema.d.ts +7 -0
  38. package/dist/schemas/pix-schema.js +14 -0
  39. package/dist/schemas/tasks-schema.d.ts +18 -0
  40. package/dist/schemas/tasks-schema.js +41 -0
  41. package/docs/concurrency.md +6 -0
  42. package/docs/desktop-mvp.md +12 -1
  43. package/docs/desktop-task-manager.md +93 -82
  44. package/external/pi-tools-suite/README.md +192 -72
  45. package/external/pi-tools-suite/docs/browser-qa-subagent.md +50 -21
  46. package/external/pi-tools-suite/docs/context-gateway-p00-adr.md +216 -0
  47. package/external/pi-tools-suite/docs/context-gateway-p01n-gate-review.md +122 -0
  48. package/external/pi-tools-suite/docs/context-gateway-p01n-measurement.md +133 -0
  49. package/external/pi-tools-suite/docs/context-gateway-p01r-ra-evidence.md +111 -0
  50. package/external/pi-tools-suite/docs/context-gateway-p01r-rb-evidence.md +100 -0
  51. package/external/pi-tools-suite/docs/context-gateway-p01r-rc-evidence.md +69 -0
  52. package/external/pi-tools-suite/docs/context-gateway-p01r-rd-evidence.md +100 -0
  53. package/external/pi-tools-suite/docs/context-gateway-p01r-re-evidence.md +74 -0
  54. package/external/pi-tools-suite/docs/context-gateway-p01r-rf-evidence.md +153 -0
  55. package/external/pi-tools-suite/docs/context-gateway-p01r-rg-evidence.md +235 -0
  56. package/external/pi-tools-suite/docs/evals.md +1 -1
  57. package/external/pi-tools-suite/docs/session-recovery.md +40 -14
  58. package/external/pi-tools-suite/docs/subagent-model-pools.md +120 -0
  59. package/external/pi-tools-suite/package.json +3 -0
  60. package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/scripts/browser-qa-runner.mjs +82 -1
  61. package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/SKILL.md → agents/browser-qa.md} +261 -12
  62. package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
  63. package/external/pi-tools-suite/src/async-subagents/agents/implement.md +20 -0
  64. package/external/pi-tools-suite/src/async-subagents/agents/oracle.md +16 -0
  65. package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
  66. package/external/pi-tools-suite/src/async-subagents/agents/research.md +20 -0
  67. package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
  68. package/external/pi-tools-suite/src/async-subagents/commands.ts +20 -72
  69. package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
  70. package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +42 -0
  71. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +13 -93
  72. package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +499 -0
  73. package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +9 -0
  74. package/external/pi-tools-suite/src/async-subagents/core/config.ts +284 -417
  75. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +1 -1
  76. package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +54 -0
  77. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +7 -6
  78. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +65 -45
  79. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +18 -6
  80. package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
  81. package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
  82. package/external/pi-tools-suite/src/async-subagents/index.ts +77 -3
  83. package/external/pi-tools-suite/src/async-subagents/lib.ts +12 -9
  84. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +54 -22
  85. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +6 -4
  86. package/external/pi-tools-suite/src/async-subagents/types.ts +3 -0
  87. package/external/pi-tools-suite/src/config.ts +42 -2
  88. package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
  89. package/external/pi-tools-suite/src/context-gateway/config.ts +120 -0
  90. package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
  91. package/external/pi-tools-suite/src/context-gateway/index.ts +214 -0
  92. package/external/pi-tools-suite/src/context-gateway/metadata-normalization.ts +88 -0
  93. package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +89 -0
  94. package/external/pi-tools-suite/src/context-gateway/telemetry.ts +502 -0
  95. package/external/pi-tools-suite/src/context-gateway/test-output-parser.ts +326 -0
  96. package/external/pi-tools-suite/src/context-gateway/types.ts +158 -0
  97. package/external/pi-tools-suite/src/context-inventory.ts +99 -0
  98. package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
  99. package/external/pi-tools-suite/src/dcp/auto-compress.ts +892 -136
  100. package/external/pi-tools-suite/src/dcp/commands.ts +82 -139
  101. package/external/pi-tools-suite/src/dcp/compress-tool.ts +533 -74
  102. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +488 -94
  103. package/external/pi-tools-suite/src/dcp/compression-preview.ts +122 -0
  104. package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
  105. package/external/pi-tools-suite/src/dcp/config.ts +86 -68
  106. package/external/pi-tools-suite/src/dcp/conversation-index.ts +421 -0
  107. package/external/pi-tools-suite/src/dcp/debug-log.ts +7 -5
  108. package/external/pi-tools-suite/src/dcp/index.ts +617 -203
  109. package/external/pi-tools-suite/src/dcp/journal.ts +566 -0
  110. package/external/pi-tools-suite/src/dcp/progress-controller.ts +244 -0
  111. package/external/pi-tools-suite/src/dcp/prompts.ts +83 -189
  112. package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
  113. package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
  114. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +299 -79
  115. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +173 -281
  116. package/external/pi-tools-suite/src/dcp/pruner-emergency.ts +2 -4
  117. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +23 -15
  118. package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +11 -1
  119. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +30 -82
  120. package/external/pi-tools-suite/src/dcp/pruner-tools.ts +28 -136
  121. package/external/pi-tools-suite/src/dcp/pruner.ts +18 -33
  122. package/external/pi-tools-suite/src/dcp/recovery.ts +129 -0
  123. package/external/pi-tools-suite/src/dcp/shadow-plan.ts +127 -0
  124. package/external/pi-tools-suite/src/dcp/state-transaction.ts +102 -0
  125. package/external/pi-tools-suite/src/dcp/state.ts +158 -580
  126. package/external/pi-tools-suite/src/dcp/ui.ts +1 -0
  127. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +36 -214
  128. package/external/pi-tools-suite/src/index.ts +16 -1
  129. package/external/pi-tools-suite/src/model-tools/index.ts +76 -42
  130. package/external/pi-tools-suite/src/repo-discovery/index.ts +84 -18
  131. package/external/pi-tools-suite/src/repo-discovery/native-compact.ts +458 -0
  132. package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
  133. package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
  134. package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
  135. package/external/pi-tools-suite/src/tool-descriptions.ts +41 -38
  136. package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
  137. package/package.json +6 -3
  138. package/schemas/pi-tools-suite.json +540 -312
  139. package/schemas/pix.json +24 -0
  140. package/schemas/tasks.json +132 -0
  141. package/skills/spec-wiki/SKILL.md +55 -0
  142. package/skills/spec-wiki/evals/evals.json +132 -0
  143. package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
  144. package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
  145. package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
  146. package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
  147. package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
  148. package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
  149. package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
  150. package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
  151. package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
  152. package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
  153. package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
  154. package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
  155. package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
  156. package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
  157. package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
  158. package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
  159. package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
  160. package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
  161. package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
  162. package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
  163. package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
  164. package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
  165. package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
  166. package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
  167. package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
  168. package/skills/spec-wiki/evals/trigger-evals.json +94 -0
  169. package/skills/spec-wiki/references/bootstrap.md +100 -0
  170. package/skills/spec-wiki/references/focused-spec.md +117 -0
  171. package/skills/spec-wiki/references/index-schema.md +330 -0
  172. package/skills/spec-wiki/references/maintenance.md +134 -0
  173. package/skills/spec-wiki/references/query.md +74 -0
  174. package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
  175. package/docs/desktop-markdown-media.md +0 -77
  176. package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
  177. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -270
  178. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +0 -78
  179. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +0 -223
  180. package/external/pi-tools-suite/src/dcp/state-persistence.ts +0 -195
  181. package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
  182. package/skills/spec-lite/SKILL.md +0 -140
  183. /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-auth.example.jsonc +0 -0
  184. /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-flow.example.jsonc +0 -0
  185. /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.LICENSE +0 -0
  186. /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.mjs +0 -0
@@ -0,0 +1,122 @@
1
+ import type { DcpConfig } from "./config.js"
2
+ import { canonicalMessageHash, copyRawMutationHash } from "./conversation-index.js"
3
+ import { cloneDcpTransactionState } from "./state-transaction.js"
4
+ import type { CompressionBlock, ConversationIndexEntry, DcpState } from "./state.js"
5
+ import { stripStaleDcpMetadataFromMessage, estimateMessageTokens } from "./pruner-metadata.js"
6
+ import { injectMessageIds, stableMessageKeys } from "./pruner-message-ids.js"
7
+ import { applyCompressionBlocks } from "./pruner-compression-blocks.js"
8
+
9
+ /** The latest provider projection is runtime-only and intentionally never persisted. */
10
+ const projectionSnapshots = new WeakMap<ConversationIndexEntry[], any[]>()
11
+
12
+ const DCP_PROVENANCE_KEYS = ["_dcpOrigin", "_dcpStableId", "_dcpBlockId"] as const
13
+
14
+ function copyDcpProvenance(source: any, target: any): any {
15
+ if (!source || !target || typeof source !== "object" || typeof target !== "object") return target
16
+ for (const key of DCP_PROVENANCE_KEYS) {
17
+ const descriptor = Object.getOwnPropertyDescriptor(source, key)
18
+ if (descriptor) Object.defineProperty(target, key, descriptor)
19
+ }
20
+ copyRawMutationHash(source, target)
21
+ return target
22
+ }
23
+
24
+ /** Clone message objects while retaining the non-enumerable DCP provenance used by exact spans. */
25
+ export function cloneProjectionMessages(messages: readonly any[]): any[] {
26
+ return messages.map((message) => {
27
+ const clone = message && typeof message === "object" ? { ...message } : message
28
+ if (!clone || typeof clone !== "object") return clone
29
+ if (message.content && typeof message.content === "object") clone.content = structuredClone(message.content)
30
+ return copyDcpProvenance(message, clone)
31
+ })
32
+ }
33
+
34
+ /** Store a detached copy of the exact provider projection that produced this index. */
35
+ export function recordCompressionProjection(
36
+ entries: ConversationIndexEntry[],
37
+ messages: readonly any[],
38
+ ): void {
39
+ projectionSnapshots.set(entries, cloneProjectionMessages(messages))
40
+ }
41
+
42
+ function verifiedSnapshot(state: DcpState): any[] {
43
+ const snapshot = projectionSnapshots.get(state.conversationIndexSnapshot)
44
+ if (!snapshot || snapshot.length !== state.conversationIndexSnapshot.length) {
45
+ throw new Error(
46
+ "Manual compression requires a current verifiable provider projection; refresh the DCP context and retry.",
47
+ )
48
+ }
49
+ const stableKeys = stableMessageKeys(snapshot)
50
+ for (let index = 0; index < snapshot.length; index++) {
51
+ const entry = state.conversationIndexSnapshot[index]
52
+ if (!entry || entry.stableId !== stableKeys[index] || entry.contentHash !== canonicalMessageHash(snapshot[index])) {
53
+ throw new Error(
54
+ "Manual compression provider projection no longer matches the current DCP snapshot; refresh the context and retry.",
55
+ )
56
+ }
57
+ }
58
+ return snapshot
59
+ }
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
+
70
+ function stripProjectionMetadata(message: any): any {
71
+ const stripped = stripStaleDcpMetadataFromMessage(message)
72
+ return copyDcpProvenance(message, stripped)
73
+ }
74
+
75
+ export interface ManualCompressionProjection {
76
+ projectedBeforeTokens: number
77
+ projectedAfterTokens: number
78
+ netGain: number
79
+ }
80
+
81
+ /**
82
+ * Materialize only an operation's new blocks against the exact current
83
+ * provider projection. Existing blocks are already represented there as
84
+ * synthetic entries, so replaying them would double-count and hide a bad
85
+ * manual summary behind unrelated automatic pruning.
86
+ */
87
+ export function previewManualCompressionProjection(
88
+ state: DcpState,
89
+ config: DcpConfig,
90
+ newBlocks: CompressionBlock[],
91
+ ): ManualCompressionProjection {
92
+ const source = verifiedSnapshot(state)
93
+ const before = cloneProjectionMessages(source)
94
+ const after = cloneProjectionMessages(source).map(stripProjectionMetadata)
95
+ const previewState = cloneDcpTransactionState(state)
96
+ previewState.compressionBlocks = newBlocks.map((block) => structuredClone(block))
97
+ previewState.accountedCompressionBlockIds = new Set()
98
+ previewState.compressionTokenSavings = new Map()
99
+
100
+ applyCompressionBlocks(after, previewState)
101
+ const appliedBlockIds = new Set(
102
+ after.flatMap((message) => Number.isInteger(message?._dcpBlockId) ? [message._dcpBlockId as number] : []),
103
+ )
104
+ const missing = newBlocks.filter((block) => !appliedBlockIds.has(block.id))
105
+ if (missing.length > 0) {
106
+ throw new Error(
107
+ `Manual compression could not materialize ${missing.map((block) => `b${block.id}`).join(", ")} in the current provider projection; no state was published.`,
108
+ )
109
+ }
110
+
111
+ // Recreate carrier overhead on a detached state. This deliberately happens
112
+ // after applying the wrapper so the comparison includes the full delivered
113
+ // provider projection rather than just summary text.
114
+ injectMessageIds(after, previewState, { config })
115
+ const projectedBeforeTokens = before.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
116
+ const projectedAfterTokens = after.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
117
+ return {
118
+ projectedBeforeTokens,
119
+ projectedAfterTokens,
120
+ netGain: projectedBeforeTokens - projectedAfterTokens,
121
+ }
122
+ }
@@ -0,0 +1,70 @@
1
+ import type { DcpState } from "./state.js"
2
+
3
+ /** Routine recovery aims for up to five percentage points, never below min. */
4
+ export function routineRecoveryTokens(tokens: number, window: number, minPercent: number): number {
5
+ return Math.max(1, Math.ceil(Math.min(window * 0.05, Math.max(0, tokens - window * minPercent))))
6
+ }
7
+
8
+ export interface CompressionProgressInput {
9
+ projectedTokens: number
10
+ contextWindow: number
11
+ requiredTokens: number
12
+ kind: "routine" | "emergency"
13
+ observedTokens?: number
14
+ targetHeadroomTokens?: number
15
+ }
16
+
17
+ /** Remaining debt follows new content/usage, not a stale pre-compression sample. */
18
+ export function outstandingCompressionTokens(state: DcpState, input: CompressionProgressInput): number | undefined {
19
+ const previous = state.compressionProgress
20
+ if (!previous || previous.contextWindow !== input.contextWindow) return undefined
21
+ const projectionChange = input.projectedTokens - previous.projectedTokens
22
+ const observedGrowth = input.observedTokens !== undefined && previous.observedTokens !== undefined
23
+ ? Math.max(0, input.observedTokens - previous.observedTokens) : 0
24
+ const growth = observedGrowth > 0 ? Math.max(projectionChange, observedGrowth) : projectionChange
25
+ const stricterTarget = previous.kind === input.kind && previous.targetHeadroomTokens !== undefined && input.targetHeadroomTokens !== undefined
26
+ ? Math.max(0, previous.targetHeadroomTokens - input.targetHeadroomTokens) : 0
27
+ return Math.max(0, previous.remainingTokens + growth + stricterTarget)
28
+ }
29
+
30
+ /** Follow actual projection growth, not context callback/retry count. */
31
+ export function trackCompressionProgress(
32
+ state: DcpState,
33
+ input: CompressionProgressInput,
34
+ ): void {
35
+ const previous = state.compressionProgress
36
+ const remaining = outstandingCompressionTokens(state, input)
37
+ state.compressionProgress = {
38
+ projectedTokens: input.projectedTokens,
39
+ contextWindow: input.contextWindow,
40
+ // The unchanged native sample still describes the input before a manual
41
+ // commit. Do not charge the same already-recovered tokens on every callback.
42
+ remainingTokens: previous && previous.kind === input.kind && remaining !== undefined && remaining > 0
43
+ ? remaining : Math.max(1, input.requiredTokens, remaining ?? 0),
44
+ kind: input.kind,
45
+ observedTokens: input.observedTokens,
46
+ targetHeadroomTokens: previous?.kind === input.kind && previous.targetHeadroomTokens !== undefined && input.targetHeadroomTokens !== undefined
47
+ ? Math.min(previous.targetHeadroomTokens, input.targetHeadroomTokens) : input.targetHeadroomTokens,
48
+ }
49
+ }
50
+
51
+ export function resetCompressionProgress(state: DcpState): void {
52
+ state.consecutiveIgnoredStrongNudges = 0
53
+ state.consecutiveIgnoredNudges = 0
54
+ state.compressionProgress = undefined
55
+ }
56
+
57
+ /** Called on a detached state only after positive full-projection gain is proved. */
58
+ export function settleCompressionProgress(state: DcpState, gain: number, projectedAfterTokens: number): boolean {
59
+ const goal = state.compressionProgress
60
+ if (!goal || gain >= goal.remainingTokens) {
61
+ resetCompressionProgress(state)
62
+ return true
63
+ }
64
+ state.compressionProgress = {
65
+ ...goal,
66
+ remainingTokens: goal.remainingTokens - gain,
67
+ projectedTokens: projectedAfterTokens,
68
+ }
69
+ return false
70
+ }
@@ -10,13 +10,14 @@ import { parse as parseJsonc, type ParseError } from "jsonc-parser"
10
10
  export interface DcpConfig {
11
11
  enabled: boolean
12
12
  debug: boolean
13
+ /** Non-fatal loader diagnostics, e.g. removed settings that were ignored. */
14
+ issues?: string[]
13
15
  debugLog?: {
14
16
  maxBytes?: number
15
17
  maxBackups?: number
16
18
  }
17
19
  manualMode: {
18
20
  enabled: boolean
19
- automaticStrategies: boolean // run dedup/purge even in manual mode
20
21
  }
21
22
  compress: {
22
23
  maxContextPercent: number | string // accepts a 0-1 fraction, absolute tokens, or a percent string like "80%"
@@ -50,41 +51,27 @@ export interface DcpConfig {
50
51
  maxSuggestions: number
51
52
  }
52
53
  /**
53
- * Auto-compress fallback: when the model ignores repeated context-strong
54
- * nudges above the emergency threshold, DCP creates a compression block
55
- * itself (without waiting for the model). Lossy and irreversible within a
54
+ * Auto-compress fallback: after completed actionable provider opportunities
55
+ * fail to meet the recovery goal, DCP can create a safe compression block,
56
+ * including after routine-reminder escalation. Lossy within a
56
57
  * session — disabled by default; opt in via config.
57
58
  */
58
59
  autoCompress: {
60
+ /** Gates autonomous firing only. Explicit `compress` calls may still use
61
+ * the summarizer settings below when their per-selection summary is omitted. */
59
62
  enabled: boolean
60
- /** Number of context-strong nudges emitted (and ignored) before DCP
61
- * auto-compresses. The model gets `patience` genuine strong chances. */
63
+ /** Auto fallback is eligible after more than this many completed,
64
+ * correlated opportunities with a delivered actionable reminder.
65
+ * Failed calls and insufficient commits do not reset patience. */
62
66
  patience: number
63
- /** Models to try, in order, when producing a model-generated summary.
67
+ /** Models to try, in order, for auto-compress and explicit generated summaries.
64
68
  * Empty array → deterministic programmatic digest (no model call). */
65
69
  summarizerModel: string[]
66
- /** Hard ceiling in ms for a single summarizer model call. */
70
+ /** Hard ceiling in ms for a summarizer model call on either path. */
67
71
  timeoutMs: number
68
72
  }
69
73
  }
70
74
  strategies: {
71
- deduplication: {
72
- enabled: boolean
73
- protectedTools: string[]
74
- }
75
- purgeErrors: {
76
- enabled: boolean
77
- turns: number // prune error inputs after N user turns (default: 4)
78
- protectedTools: string[]
79
- }
80
- autoToolPruning: {
81
- enabled: boolean
82
- maxOutputTokens: number
83
- keepRecentTurns: number
84
- readLikeTools: string[]
85
- readLikeTurns: number
86
- protectedTools: string[]
87
- }
88
75
  emergencyCurrentTurnPruning: {
89
76
  /** Enable same-turn candidates and lossy fallback pruning; emergency reminders remain active. */
90
77
  enabled: boolean
@@ -104,11 +91,10 @@ export interface DcpConfig {
104
91
  }
105
92
  }
106
93
  protectedFilePatterns: string[]
107
- pruneNotification: "off" | "minimal" | "detailed"
108
94
  modelOverrides: Record<string, DcpConfigOverride>
109
95
  }
110
96
 
111
- export type DcpConfigOverride = DeepPartial<Omit<DcpConfig, "modelOverrides">>
97
+ export type DcpConfigOverride = DeepPartial<Omit<DcpConfig, "modelOverrides" | "issues">>
112
98
 
113
99
  type DeepPartial<T> = T extends Array<infer U>
114
100
  ? Array<DeepPartial<U>>
@@ -123,9 +109,9 @@ type DeepPartial<T> = T extends Array<infer U>
123
109
  const DEFAULT_CONFIG: DcpConfig = {
124
110
  enabled: true,
125
111
  debug: false,
112
+ issues: [],
126
113
  manualMode: {
127
114
  enabled: false,
128
- automaticStrategies: true,
129
115
  },
130
116
  compress: {
131
117
  maxContextPercent: 0.65,
@@ -162,39 +148,6 @@ const DEFAULT_CONFIG: DcpConfig = {
162
148
  },
163
149
  },
164
150
  strategies: {
165
- deduplication: {
166
- enabled: true,
167
- protectedTools: [],
168
- },
169
- purgeErrors: {
170
- enabled: true,
171
- turns: 4,
172
- protectedTools: [],
173
- },
174
- autoToolPruning: {
175
- enabled: true,
176
- maxOutputTokens: 1200,
177
- keepRecentTurns: 1,
178
- readLikeTools: [
179
- "read",
180
- "shell",
181
- "bash",
182
- "grep",
183
- "find",
184
- "ls",
185
- "web_search",
186
- "web_fetch",
187
- "repo_architecture",
188
- "repo_structure",
189
- "repo_ast",
190
- "repo_search",
191
- "repo_explain",
192
- "repo_deps",
193
- "ast_grep",
194
- ],
195
- readLikeTurns: 3,
196
- protectedTools: [],
197
- },
198
151
  emergencyCurrentTurnPruning: {
199
152
  enabled: true,
200
153
  hardContextPercent: 0.82,
@@ -207,7 +160,6 @@ const DEFAULT_CONFIG: DcpConfig = {
207
160
  },
208
161
  },
209
162
  protectedFilePatterns: [],
210
- pruneNotification: "detailed",
211
163
  modelOverrides: {},
212
164
  }
213
165
 
@@ -281,15 +233,75 @@ function readJsoncFile(filePath: string): Record<string, unknown> {
281
233
  /**
282
234
  * Return the nested DCP config from a shared pi-tools-suite config file.
283
235
  */
284
- function readDcpFromSuiteConfig(filePath: string): Record<string, unknown> {
236
+ function readDcpFromSuiteConfig(filePath: string, issues: string[]): Record<string, unknown> {
285
237
  const raw = readJsoncFile(filePath)
286
238
  const dcp = raw["dcp"]
287
239
  if (dcp === null || typeof dcp !== "object" || Array.isArray(dcp)) return {}
288
- return dcp as Record<string, unknown>
240
+ return stripRemovedDcpKeys(dcp as Record<string, unknown>, issues)
289
241
  }
290
242
 
291
- function mergeSuiteDcpConfig(config: DcpConfig, filePath: string): DcpConfig {
292
- const raw = readDcpFromSuiteConfig(filePath)
243
+ const REMOVED_DCP_GUIDANCE: Record<string, string> = {
244
+ pruneNotification: "removed; pruning notifications are no longer configurable",
245
+ "manualMode.automaticStrategies": "removed; manual mode now has one explicit enabled flag",
246
+ "strategies.deduplication": "removed; no automatic replacement policy is currently mapped to this setting",
247
+ "strategies.purgeErrors": "removed; no automatic replacement policy is currently mapped to this setting",
248
+ "strategies.autoToolPruning": "removed; use explicit /dcp sweep or DCP compression/emergency policies instead",
249
+ }
250
+
251
+ function reportRemovedKey(issues: string[], prefix: string, key: string): void {
252
+ const fullKey = prefix ? `${prefix}.${key}` : key
253
+ const canonical = fullKey.replace(/^modelOverrides\.[^.]+\./, "")
254
+ const guidance = REMOVED_DCP_GUIDANCE[canonical] ?? "removed and ignored"
255
+ issues.push(`dcp.${fullKey} is ${guidance}.`)
256
+ }
257
+
258
+ function stripRemovedDcpKeys(
259
+ raw: Record<string, unknown>,
260
+ issues: string[],
261
+ prefix = "",
262
+ ): Record<string, unknown> {
263
+ const cleaned = structuredClone(raw)
264
+ if (Object.prototype.hasOwnProperty.call(cleaned, "pruneNotification")) {
265
+ reportRemovedKey(issues, prefix, "pruneNotification")
266
+ }
267
+ delete cleaned.pruneNotification
268
+
269
+ const manualMode = cleaned.manualMode
270
+ if (manualMode && typeof manualMode === "object" && !Array.isArray(manualMode)) {
271
+ if (Object.prototype.hasOwnProperty.call(manualMode, "automaticStrategies")) {
272
+ reportRemovedKey(issues, prefix, "manualMode.automaticStrategies")
273
+ }
274
+ delete (manualMode as Record<string, unknown>).automaticStrategies
275
+ }
276
+
277
+ const strategies = cleaned.strategies
278
+ if (strategies && typeof strategies === "object" && !Array.isArray(strategies)) {
279
+ const record = strategies as Record<string, unknown>
280
+ for (const key of ["deduplication", "purgeErrors", "autoToolPruning"] as const) {
281
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
282
+ reportRemovedKey(issues, prefix, `strategies.${key}`)
283
+ }
284
+ delete record[key]
285
+ }
286
+ }
287
+
288
+ const modelOverrides = cleaned.modelOverrides
289
+ if (modelOverrides && typeof modelOverrides === "object" && !Array.isArray(modelOverrides)) {
290
+ for (const [key, override] of Object.entries(modelOverrides as Record<string, unknown>)) {
291
+ if (!override || typeof override !== "object" || Array.isArray(override)) continue
292
+ ;(modelOverrides as Record<string, unknown>)[key] = stripRemovedDcpKeys(
293
+ override as Record<string, unknown>,
294
+ issues,
295
+ prefix ? `${prefix}.modelOverrides.${key}` : `modelOverrides.${key}`,
296
+ )
297
+ }
298
+ }
299
+
300
+ return cleaned
301
+ }
302
+
303
+ function mergeSuiteDcpConfig(config: DcpConfig, filePath: string, issues: string[]): DcpConfig {
304
+ const raw = readDcpFromSuiteConfig(filePath, issues)
293
305
  if (Object.keys(raw).length === 0) return config
294
306
  return deepMerge(config, raw as Partial<DcpConfig>)
295
307
  }
@@ -431,10 +443,16 @@ export interface LoadConfigOptions {
431
443
  */
432
444
  export function loadConfig(options: LoadConfigOptions = {}): DcpConfig {
433
445
  // Layer 1: defaults (deep clone so we never mutate the constant)
434
- let config: DcpConfig = deepMerge(DEFAULT_CONFIG, {})
446
+ let config: DcpConfig = structuredClone(DEFAULT_CONFIG)
447
+ const issues: string[] = []
435
448
 
436
449
  const homeDir = options.homeDir ?? os.homedir()
437
- config = mergeSuiteDcpConfig(config, path.join(homeDir, ".config", "pi", "pi-tools-suite.jsonc"))
450
+ config = mergeSuiteDcpConfig(
451
+ config,
452
+ path.join(homeDir, ".config", "pi", "pi-tools-suite.jsonc"),
453
+ issues,
454
+ )
455
+ config.issues = issues
438
456
 
439
457
  return config
440
458
  }