pi-ui-extend 1.0.41 → 1.0.45

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 (236) hide show
  1. package/README.md +50 -13
  2. package/dist/app/app.d.ts +13 -0
  3. package/dist/app/app.js +158 -8
  4. package/dist/app/cli/install.js +2 -2
  5. package/dist/app/commands/command-controller.d.ts +4 -0
  6. package/dist/app/commands/command-controller.js +11 -0
  7. package/dist/app/commands/command-git-actions.d.ts +25 -0
  8. package/dist/app/commands/command-git-actions.js +381 -0
  9. package/dist/app/commands/command-host.d.ts +4 -0
  10. package/dist/app/commands/command-host.js +5 -2
  11. package/dist/app/commands/command-model-actions.d.ts +1 -0
  12. package/dist/app/commands/command-model-actions.js +57 -35
  13. package/dist/app/commands/command-navigation-actions.d.ts +1 -0
  14. package/dist/app/commands/command-navigation-actions.js +16 -3
  15. package/dist/app/commands/command-registry.d.ts +2 -0
  16. package/dist/app/commands/command-registry.js +15 -1
  17. package/dist/app/commands/command-session-actions.js +9 -3
  18. package/dist/app/commands/reload-context-inventory.d.ts +11 -0
  19. package/dist/app/commands/reload-context-inventory.js +70 -0
  20. package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
  21. package/dist/app/extensions/extension-actions-controller.js +6 -0
  22. package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
  23. package/dist/app/extensions/subagent-catalog-state.js +23 -0
  24. package/dist/app/input/autocomplete-controller.js +61 -34
  25. package/dist/app/input/input-action-controller.d.ts +2 -0
  26. package/dist/app/input/input-action-controller.js +8 -1
  27. package/dist/app/input/input-controller.d.ts +2 -1
  28. package/dist/app/input/input-controller.js +7 -2
  29. package/dist/app/input/prompt-enhancer-controller.js +53 -35
  30. package/dist/app/input/voice-controller.d.ts +41 -45
  31. package/dist/app/input/voice-controller.js +351 -419
  32. package/dist/app/model/model-usage-controller.js +17 -7
  33. package/dist/app/model/model-usage-status.d.ts +4 -1
  34. package/dist/app/model/model-usage-status.js +270 -46
  35. package/dist/app/popup/menu-items-controller.d.ts +13 -3
  36. package/dist/app/popup/menu-items-controller.js +37 -21
  37. package/dist/app/popup/popup-action-controller.d.ts +12 -2
  38. package/dist/app/popup/popup-action-controller.js +77 -24
  39. package/dist/app/popup/popup-menu-controller.d.ts +36 -14
  40. package/dist/app/popup/popup-menu-controller.js +239 -69
  41. package/dist/app/rendering/dcp-stats.d.ts +9 -4
  42. package/dist/app/rendering/dcp-stats.js +40 -425
  43. package/dist/app/rendering/editor-panels.js +10 -4
  44. package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
  45. package/dist/app/rendering/popup-menu-renderer.js +42 -37
  46. package/dist/app/rendering/render-controller.js +23 -2
  47. package/dist/app/rendering/status-line-renderer.d.ts +5 -0
  48. package/dist/app/rendering/status-line-renderer.js +41 -5
  49. package/dist/app/rendering/tab-line-renderer.js +26 -20
  50. package/dist/app/runtime.d.ts +12 -1
  51. package/dist/app/runtime.js +120 -12
  52. package/dist/app/screen/mouse-controller.d.ts +2 -0
  53. package/dist/app/screen/mouse-controller.js +17 -7
  54. package/dist/app/screen/status-controller.d.ts +4 -0
  55. package/dist/app/screen/status-controller.js +5 -0
  56. package/dist/app/session/lazy-session-manager.js +34 -0
  57. package/dist/app/session/session-event-controller.d.ts +1 -0
  58. package/dist/app/session/session-event-controller.js +10 -1
  59. package/dist/app/session/session-history.d.ts +1 -0
  60. package/dist/app/session/session-history.js +12 -1
  61. package/dist/app/session/session-lifecycle-controller.d.ts +7 -1
  62. package/dist/app/session/session-lifecycle-controller.js +16 -1
  63. package/dist/app/session/tabs-controller.d.ts +26 -1
  64. package/dist/app/session/tabs-controller.js +454 -147
  65. package/dist/app/subagents/subagents-files.js +60 -1
  66. package/dist/app/subagents/subagents-model.d.ts +1 -0
  67. package/dist/app/subagents/subagents-model.js +18 -2
  68. package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
  69. package/dist/app/subagents/subagents-widget-controller.js +6 -0
  70. package/dist/app/types.d.ts +16 -1
  71. package/dist/app/workspace/workspace-actions-controller.js +10 -2
  72. package/dist/app/workspace/workspace-undo.d.ts +1 -0
  73. package/dist/app/workspace/workspace-undo.js +1 -0
  74. package/dist/bundled-extensions/question/index.js +9 -1
  75. package/dist/bundled-extensions/question/remote.d.ts +4 -0
  76. package/dist/bundled-extensions/question/remote.js +33 -0
  77. package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
  78. package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
  79. package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
  80. package/dist/bundled-extensions/telegram-connector/config.js +87 -0
  81. package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
  82. package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
  83. package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
  84. package/dist/bundled-extensions/telegram-connector/index.js +195 -0
  85. package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
  86. package/dist/bundled-extensions/terminal-bell/index.js +0 -76
  87. package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
  88. package/dist/bundled-extensions/workspace-undo/index.js +191 -0
  89. package/dist/config.d.ts +21 -2
  90. package/dist/config.js +159 -32
  91. package/dist/default-pix-config.js +25 -5
  92. package/dist/schemas/index.d.ts +1 -0
  93. package/dist/schemas/index.js +1 -0
  94. package/dist/schemas/pi-tools-suite-schema.d.ts +88 -62
  95. package/dist/schemas/pi-tools-suite-schema.js +54 -83
  96. package/dist/schemas/pix-schema.d.ts +19 -2
  97. package/dist/schemas/pix-schema.js +47 -5
  98. package/dist/schemas/tasks-schema.d.ts +18 -0
  99. package/dist/schemas/tasks-schema.js +41 -0
  100. package/docs/concurrency.md +9 -1
  101. package/docs/desktop-mvp.md +22 -6
  102. package/docs/desktop-task-manager.md +148 -84
  103. package/docs/release.md +30 -6
  104. package/external/pi-tools-suite/README.md +197 -84
  105. package/external/pi-tools-suite/docs/evals.md +1 -1
  106. package/external/pi-tools-suite/docs/session-recovery.md +47 -14
  107. package/external/pi-tools-suite/docs/subagent-model-pools.md +48 -30
  108. package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
  109. package/external/pi-tools-suite/package.json +6 -1
  110. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
  111. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
  112. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
  113. package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
  114. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
  115. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
  116. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
  117. package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
  118. package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
  119. package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
  120. package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
  121. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
  122. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
  123. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
  124. package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
  125. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
  126. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
  127. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
  128. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
  129. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
  130. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
  131. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
  132. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
  133. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
  134. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
  135. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
  136. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
  137. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
  138. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
  139. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
  140. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
  141. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
  142. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
  143. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
  144. package/external/pi-tools-suite/src/async-subagents/commands.ts +15 -71
  145. package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
  146. package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
  147. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
  148. package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -5
  149. package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
  150. package/external/pi-tools-suite/src/async-subagents/core/config.ts +160 -327
  151. package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
  152. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
  153. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +21 -5
  154. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +40 -26
  155. package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
  156. package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
  157. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
  158. package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
  159. package/external/pi-tools-suite/src/async-subagents/lib.ts +16 -9
  160. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -5
  161. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +5 -4
  162. package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
  163. package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
  164. package/external/pi-tools-suite/src/config.ts +55 -1
  165. package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
  166. package/external/pi-tools-suite/src/context-gateway/config.ts +77 -3
  167. package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
  168. package/external/pi-tools-suite/src/context-gateway/enforcement.ts +222 -0
  169. package/external/pi-tools-suite/src/context-gateway/index.ts +206 -19
  170. package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +7 -7
  171. package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
  172. package/external/pi-tools-suite/src/context-gateway/types.ts +16 -3
  173. package/external/pi-tools-suite/src/context-inventory.ts +99 -0
  174. package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
  175. package/external/pi-tools-suite/src/dcp/auto-compress.ts +128 -49
  176. package/external/pi-tools-suite/src/dcp/commands.ts +32 -156
  177. package/external/pi-tools-suite/src/dcp/compress-tool.ts +177 -46
  178. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
  179. package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
  180. package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
  181. package/external/pi-tools-suite/src/dcp/config.ts +114 -17
  182. package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
  183. package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
  184. package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
  185. package/external/pi-tools-suite/src/dcp/index.ts +338 -66
  186. package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
  187. package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
  188. package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
  189. package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
  190. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +93 -3
  191. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
  192. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
  193. package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
  194. package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
  195. package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
  196. package/external/pi-tools-suite/src/dcp/state.ts +9 -0
  197. package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
  198. package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
  199. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +78 -22
  200. package/external/pi-tools-suite/src/index.ts +7 -1
  201. package/external/pi-tools-suite/src/lib/project.ts +36 -1
  202. package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
  203. package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
  204. package/external/pi-tools-suite/src/resource-registry/index.ts +2551 -0
  205. package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
  206. package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
  207. package/external/pi-tools-suite/src/todo/index.ts +21 -0
  208. package/external/pi-tools-suite/src/todo/todo.ts +19 -1
  209. package/external/pi-tools-suite/src/tool-descriptions.ts +47 -19
  210. package/package.json +10 -9
  211. package/schemas/pi-tools-suite.json +466 -287
  212. package/schemas/pix.json +129 -11
  213. package/schemas/tasks.json +131 -0
  214. package/skills/simplify/SKILL.md +33 -5
  215. package/docs/desktop-markdown-media.md +0 -77
  216. package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -177
  217. package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
  218. package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
  219. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
  220. package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
  221. package/skills/playwright-cli/SKILL.md +0 -420
  222. package/skills/playwright-cli/references/element-attributes.md +0 -23
  223. package/skills/playwright-cli/references/playwright-tests.md +0 -50
  224. package/skills/playwright-cli/references/request-mocking.md +0 -87
  225. package/skills/playwright-cli/references/running-code.md +0 -241
  226. package/skills/playwright-cli/references/session-management.md +0 -273
  227. package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
  228. package/skills/playwright-cli/references/storage-state.md +0 -290
  229. package/skills/playwright-cli/references/test-generation.md +0 -142
  230. package/skills/playwright-cli/references/tracing.md +0 -154
  231. package/skills/playwright-cli/references/video-recording.md +0 -147
  232. package/skills/spec-lite/SKILL.md +0 -140
  233. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
  234. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
  235. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
  236. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
@@ -27,6 +27,28 @@ export function outstandingCompressionTokens(state: DcpState, input: Compression
27
27
  return Math.max(0, previous.remainingTokens + growth + stricterTarget)
28
28
  }
29
29
 
30
+ /**
31
+ * Bound one automatic recovery plan to what the *current* budget actually
32
+ * requires. `compressionProgress.remainingTokens` is deliberately cumulative:
33
+ * it follows content growth and stricter historical targets across callbacks.
34
+ * That debt is useful for progress accounting, but feeding the whole stale debt
35
+ * back into candidate sizing can make a later, much smaller capacity gap select
36
+ * an uneconomically huge range (and repeatedly fail with non-positive gain).
37
+ */
38
+ export function compressionPlanningTokens(
39
+ state: DcpState,
40
+ input: CompressionProgressInput,
41
+ remainingTokens: number | undefined = outstandingCompressionTokens(state, input),
42
+ ): number {
43
+ const requested = Math.max(1, Math.ceil(input.requiredTokens))
44
+ if (
45
+ remainingTokens === undefined ||
46
+ remainingTokens <= 0 ||
47
+ state.compressionProgress?.kind !== input.kind
48
+ ) return requested
49
+ return Math.min(remainingTokens, requested)
50
+ }
51
+
30
52
  /** Follow actual projection growth, not context callback/retry count. */
31
53
  export function trackCompressionProgress(
32
54
  state: DcpState,
@@ -10,6 +10,8 @@ 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
@@ -55,15 +57,21 @@ export interface DcpConfig {
55
57
  * session — disabled by default; opt in via config.
56
58
  */
57
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. */
58
62
  enabled: boolean
59
63
  /** Auto fallback is eligible after more than this many completed,
60
64
  * correlated opportunities with a delivered actionable reminder.
61
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.
68
+ * The first entry is the primary summarizer. Additional entries remain
69
+ * supported for backward compatibility with the old inline fallback list.
64
70
  * Empty array → deterministic programmatic digest (no model call). */
65
71
  summarizerModel: string[]
66
- /** Hard ceiling in ms for a single summarizer model call. */
72
+ /** Explicit fallback summarizers tried after `summarizerModel`, in order. */
73
+ summarizerFallbackModels: string[]
74
+ /** Hard ceiling in ms for a summarizer model call on either path. */
67
75
  timeoutMs: number
68
76
  }
69
77
  }
@@ -90,7 +98,7 @@ export interface DcpConfig {
90
98
  modelOverrides: Record<string, DcpConfigOverride>
91
99
  }
92
100
 
93
- export type DcpConfigOverride = DeepPartial<Omit<DcpConfig, "modelOverrides">>
101
+ export type DcpConfigOverride = DeepPartial<Omit<DcpConfig, "modelOverrides" | "issues">>
94
102
 
95
103
  type DeepPartial<T> = T extends Array<infer U>
96
104
  ? Array<DeepPartial<U>>
@@ -105,6 +113,7 @@ type DeepPartial<T> = T extends Array<infer U>
105
113
  const DEFAULT_CONFIG: DcpConfig = {
106
114
  enabled: true,
107
115
  debug: false,
116
+ issues: [],
108
117
  manualMode: {
109
118
  enabled: false,
110
119
  },
@@ -139,6 +148,7 @@ const DEFAULT_CONFIG: DcpConfig = {
139
148
  enabled: false,
140
149
  patience: 2,
141
150
  summarizerModel: [],
151
+ summarizerFallbackModels: [],
142
152
  timeoutMs: 20000,
143
153
  },
144
154
  },
@@ -228,43 +238,75 @@ function readJsoncFile(filePath: string): Record<string, unknown> {
228
238
  /**
229
239
  * Return the nested DCP config from a shared pi-tools-suite config file.
230
240
  */
231
- function readDcpFromSuiteConfig(filePath: string): Record<string, unknown> {
241
+ function readDcpFromSuiteConfig(filePath: string, issues: string[]): Record<string, unknown> {
232
242
  const raw = readJsoncFile(filePath)
233
243
  const dcp = raw["dcp"]
234
244
  if (dcp === null || typeof dcp !== "object" || Array.isArray(dcp)) return {}
235
- return stripRemovedDcpKeys(dcp as Record<string, unknown>)
245
+ return stripRemovedDcpKeys(dcp as Record<string, unknown>, issues)
236
246
  }
237
247
 
238
- function stripRemovedDcpKeys(raw: Record<string, unknown>): Record<string, unknown> {
248
+ const REMOVED_DCP_GUIDANCE: Record<string, string> = {
249
+ pruneNotification: "removed; pruning notifications are no longer configurable",
250
+ "manualMode.automaticStrategies": "removed; manual mode now has one explicit enabled flag",
251
+ "strategies.deduplication": "removed; no automatic replacement policy is currently mapped to this setting",
252
+ "strategies.purgeErrors": "removed; no automatic replacement policy is currently mapped to this setting",
253
+ "strategies.autoToolPruning": "removed; use explicit /dcp sweep or DCP compression/emergency policies instead",
254
+ }
255
+
256
+ function reportRemovedKey(issues: string[], prefix: string, key: string): void {
257
+ const fullKey = prefix ? `${prefix}.${key}` : key
258
+ const canonical = fullKey.replace(/^modelOverrides\.[^.]+\./, "")
259
+ const guidance = REMOVED_DCP_GUIDANCE[canonical] ?? "removed and ignored"
260
+ issues.push(`dcp.${fullKey} is ${guidance}.`)
261
+ }
262
+
263
+ function stripRemovedDcpKeys(
264
+ raw: Record<string, unknown>,
265
+ issues: string[],
266
+ prefix = "",
267
+ ): Record<string, unknown> {
239
268
  const cleaned = structuredClone(raw)
269
+ if (Object.prototype.hasOwnProperty.call(cleaned, "pruneNotification")) {
270
+ reportRemovedKey(issues, prefix, "pruneNotification")
271
+ }
240
272
  delete cleaned.pruneNotification
241
273
 
242
274
  const manualMode = cleaned.manualMode
243
275
  if (manualMode && typeof manualMode === "object" && !Array.isArray(manualMode)) {
276
+ if (Object.prototype.hasOwnProperty.call(manualMode, "automaticStrategies")) {
277
+ reportRemovedKey(issues, prefix, "manualMode.automaticStrategies")
278
+ }
244
279
  delete (manualMode as Record<string, unknown>).automaticStrategies
245
280
  }
246
281
 
247
282
  const strategies = cleaned.strategies
248
283
  if (strategies && typeof strategies === "object" && !Array.isArray(strategies)) {
249
284
  const record = strategies as Record<string, unknown>
250
- delete record.deduplication
251
- delete record.purgeErrors
252
- delete record.autoToolPruning
285
+ for (const key of ["deduplication", "purgeErrors", "autoToolPruning"] as const) {
286
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
287
+ reportRemovedKey(issues, prefix, `strategies.${key}`)
288
+ }
289
+ delete record[key]
290
+ }
253
291
  }
254
292
 
255
293
  const modelOverrides = cleaned.modelOverrides
256
294
  if (modelOverrides && typeof modelOverrides === "object" && !Array.isArray(modelOverrides)) {
257
295
  for (const [key, override] of Object.entries(modelOverrides as Record<string, unknown>)) {
258
296
  if (!override || typeof override !== "object" || Array.isArray(override)) continue
259
- ;(modelOverrides as Record<string, unknown>)[key] = stripRemovedDcpKeys(override as Record<string, unknown>)
297
+ ;(modelOverrides as Record<string, unknown>)[key] = stripRemovedDcpKeys(
298
+ override as Record<string, unknown>,
299
+ issues,
300
+ prefix ? `${prefix}.modelOverrides.${key}` : `modelOverrides.${key}`,
301
+ )
260
302
  }
261
303
  }
262
304
 
263
305
  return cleaned
264
306
  }
265
307
 
266
- function mergeSuiteDcpConfig(config: DcpConfig, filePath: string): DcpConfig {
267
- const raw = readDcpFromSuiteConfig(filePath)
308
+ function mergeSuiteDcpConfig(config: DcpConfig, filePath: string, issues: string[]): DcpConfig {
309
+ const raw = readDcpFromSuiteConfig(filePath, issues)
268
310
  if (Object.keys(raw).length === 0) return config
269
311
  return deepMerge(config, raw as Partial<DcpConfig>)
270
312
  }
@@ -275,6 +317,55 @@ function normalizeModelKey(key: string | undefined): string | undefined {
275
317
  return trimmed.length > 0 ? trimmed : undefined
276
318
  }
277
319
 
320
+ export function summarizerModelRefs(
321
+ settings: DcpConfig["compress"]["autoCompress"],
322
+ ): string[] {
323
+ const refs = [
324
+ ...(Array.isArray(settings.summarizerModel) ? settings.summarizerModel : []),
325
+ ...(Array.isArray(settings.summarizerFallbackModels) ? settings.summarizerFallbackModels : []),
326
+ ]
327
+ const seen = new Set<string>()
328
+ const result: string[] = []
329
+ for (const ref of refs) {
330
+ if (typeof ref !== "string") continue
331
+ const normalized = ref.trim()
332
+ if (!normalized || seen.has(normalized)) continue
333
+ seen.add(normalized)
334
+ result.push(normalized)
335
+ }
336
+ return result
337
+ }
338
+
339
+ function normalizeModelRefArray(value: unknown): string[] {
340
+ if (!Array.isArray(value)) return []
341
+ const seen = new Set<string>()
342
+ const result: string[] = []
343
+ for (const ref of value) {
344
+ if (typeof ref !== "string") continue
345
+ const normalized = ref.trim()
346
+ if (!normalized || seen.has(normalized)) continue
347
+ seen.add(normalized)
348
+ result.push(normalized)
349
+ }
350
+ return result
351
+ }
352
+
353
+ function normalizeResolvedModelArrays(config: DcpConfig): DcpConfig {
354
+ const autoCompress = config.compress?.autoCompress
355
+ if (!autoCompress || typeof autoCompress !== "object") return config
356
+ return {
357
+ ...config,
358
+ compress: {
359
+ ...config.compress,
360
+ autoCompress: {
361
+ ...autoCompress,
362
+ summarizerModel: normalizeModelRefArray(autoCompress.summarizerModel),
363
+ summarizerFallbackModels: normalizeModelRefArray(autoCompress.summarizerFallbackModels),
364
+ },
365
+ },
366
+ }
367
+ }
368
+
278
369
  function escapeRegExp(text: string): string {
279
370
  return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&")
280
371
  }
@@ -378,17 +469,17 @@ export function resolveModelConfig(
378
469
  modelKeys: Array<string | undefined> = [],
379
470
  ): DcpConfig {
380
471
  const overrides = config.modelOverrides
381
- if (!overrides || Object.keys(overrides).length === 0) return config
472
+ if (!overrides || Object.keys(overrides).length === 0) return normalizeResolvedModelArrays(config)
382
473
 
383
474
  const matches = matchingModelEntries(overrides, modelKeys)
384
- if (matches.length === 0) return config
475
+ if (matches.length === 0) return normalizeResolvedModelArrays(config)
385
476
 
386
477
  let resolved = deepMerge(config, {})
387
478
  for (const [, override] of matches) {
388
479
  resolved = deepMerge(resolved, override as Partial<DcpConfig>)
389
480
  }
390
481
 
391
- return resolved
482
+ return normalizeResolvedModelArrays(resolved)
392
483
  }
393
484
 
394
485
  // ---------------------------------------------------------------------------
@@ -407,9 +498,15 @@ export interface LoadConfigOptions {
407
498
  export function loadConfig(options: LoadConfigOptions = {}): DcpConfig {
408
499
  // Layer 1: defaults (deep clone so we never mutate the constant)
409
500
  let config: DcpConfig = structuredClone(DEFAULT_CONFIG)
501
+ const issues: string[] = []
410
502
 
411
503
  const homeDir = options.homeDir ?? os.homedir()
412
- config = mergeSuiteDcpConfig(config, path.join(homeDir, ".config", "pi", "pi-tools-suite.jsonc"))
504
+ config = mergeSuiteDcpConfig(
505
+ config,
506
+ path.join(homeDir, ".config", "pi", "pi-tools-suite.jsonc"),
507
+ issues,
508
+ )
509
+ config.issues = issues
413
510
 
414
- return config
511
+ return normalizeResolvedModelArrays(config)
415
512
  }
@@ -76,19 +76,43 @@ function stripDcpCarrierText(text: string): string {
76
76
  return stripStaleDcpMetadataLines(text)
77
77
  }
78
78
 
79
- function canonicalHashValue(value: unknown, seen = new WeakSet<object>()): unknown {
79
+ const OMIT_JSON_OBJECT_VALUE = Symbol("omit-json-object-value")
80
+
81
+ function canonicalHashValue(
82
+ value: unknown,
83
+ seen = new WeakSet<object>(),
84
+ arrayElement = false,
85
+ ): unknown | typeof OMIT_JSON_OBJECT_VALUE {
80
86
  if (value === null || typeof value === "string" || typeof value === "boolean") return value
81
- if (typeof value === "number") return Number.isFinite(value) ? value : String(value)
87
+ // Exact membership is persisted in JSONL, so hash the value that can actually
88
+ // survive that persistence boundary. JSON.stringify serializes non-finite
89
+ // numbers as null, drops undefined/function/symbol object properties, and
90
+ // converts those values to null when they occupy an array slot. Encoding an
91
+ // in-memory-only sentinel here made otherwise unchanged toolResult.details
92
+ // fail exact replay after restart.
93
+ if (typeof value === "number") return Number.isFinite(value) ? value : null
82
94
  if (typeof value === "bigint") return `${value}n`
83
- if (typeof value === "undefined") return "[undefined]"
84
- if (typeof value === "function" || typeof value === "symbol") return String(value)
85
- if (Array.isArray(value)) return value.map((item) => canonicalHashValue(item, seen))
95
+ if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
96
+ return arrayElement ? null : OMIT_JSON_OBJECT_VALUE
97
+ }
98
+ if (Array.isArray(value)) {
99
+ return value.map((item) => {
100
+ const canonical = canonicalHashValue(item, seen, true)
101
+ return canonical === OMIT_JSON_OBJECT_VALUE ? null : canonical
102
+ })
103
+ }
86
104
  if (typeof value === "object") {
87
105
  if (seen.has(value as object)) return "[cycle]"
106
+ const toJSON = (value as { toJSON?: unknown }).toJSON
107
+ if (typeof toJSON === "function") {
108
+ const serialized = (toJSON as () => unknown).call(value)
109
+ return canonicalHashValue(serialized, seen, arrayElement)
110
+ }
88
111
  seen.add(value as object)
89
112
  const output: Record<string, unknown> = {}
90
113
  for (const key of Object.keys(value as Record<string, unknown>).sort()) {
91
- output[key] = canonicalHashValue((value as Record<string, unknown>)[key], seen)
114
+ const canonical = canonicalHashValue((value as Record<string, unknown>)[key], seen, false)
115
+ if (canonical !== OMIT_JSON_OBJECT_VALUE) output[key] = canonical
92
116
  }
93
117
  seen.delete(value as object)
94
118
  return output
@@ -106,14 +130,17 @@ export function canonicalMessageHash(message: any): string {
106
130
  if (message && typeof message === "object") {
107
131
  for (const key of Object.keys(message).sort()) {
108
132
  if (IDENTITY_ONLY_MESSAGE_KEYS.has(key)) continue
109
- canonical[key] = key === "content"
133
+ const value = key === "content"
110
134
  ? canonicalContentForMessageHash(message)
111
135
  : canonicalHashValue(message[key])
136
+ if (value !== OMIT_JSON_OBJECT_VALUE) canonical[key] = value
112
137
  }
113
138
  } else {
114
- canonical.value = canonicalHashValue(message)
139
+ const value = canonicalHashValue(message)
140
+ if (value !== OMIT_JSON_OBJECT_VALUE) canonical.value = value
115
141
  }
116
- return createHash("sha256").update(JSON.stringify(canonicalHashValue(canonical))).digest("hex")
142
+ const normalized = canonicalHashValue(canonical)
143
+ return createHash("sha256").update(JSON.stringify(normalized === OMIT_JSON_OBJECT_VALUE ? {} : normalized)).digest("hex")
117
144
  }
118
145
 
119
146
  const CANONICAL_HASH_RE = /^[a-f0-9]{64}$/i
@@ -0,0 +1,41 @@
1
+ import type { DcpConfig } from "./config.js";
2
+ import { modelKeysFromContext } from "./config.js";
3
+ import type { DcpState } from "./state.js";
4
+ import { estimateMessageTokens, getActiveSummaryTokenEstimate, resolveContextThresholds } from "./pruner-metadata.js";
5
+ import { planDcpBudget } from "./progress-controller.js";
6
+
7
+ /** Scalar-only diagnostic snapshot; never stores raw messages or credentials.
8
+ * These records are observational, never journal/recovery authority. */
9
+ export function dcpRequestSnapshot(config: DcpConfig, state: DcpState, model: any,
10
+ usage: { tokens?: number | null; contextWindow?: number } | undefined,
11
+ raw: any[], projected: any[], reason: string) {
12
+ const contextWindow = Number.isFinite(model?.contextWindow) && model.contextWindow > 0
13
+ ? model.contextWindow : usage?.contextWindow;
14
+ const rawTokens = raw.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
15
+ const projectedTokens = projected.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
16
+ const thresholds = resolveContextThresholds(config, modelKeysFromContext({ model } as any), contextWindow);
17
+ const budget = contextWindow ? planDcpBudget({
18
+ providerUsageTokens: usage?.tokens, repoProjectedTokens: projectedTokens, contextWindow,
19
+ reservedOutputTokens: model?.maxTokens ?? 0, reservedToolTokens: 0,
20
+ maxContextPercent: thresholds.maxContextPercent,
21
+ hardContextPercent: config.strategies.emergencyCurrentTurnPruning.hardContextPercent,
22
+ targetContextPercent: Math.min(config.strategies.emergencyCurrentTurnPruning.targetContextPercent, thresholds.maxContextPercent * 0.9),
23
+ summaryBufferEnabled: config.compress.summaryBuffer,
24
+ activeSummaryTokens: getActiveSummaryTokenEstimate(state), summaryBufferMaxBonusRatio: 0.05,
25
+ estimatorMarginTokens: Math.max(256, Math.ceil(contextWindow * 0.0025)),
26
+ }) : undefined;
27
+ const routineTokens = contextWindow ? thresholds.minContextPercent * contextWindow : undefined;
28
+ let pressure = "unknown";
29
+ if (budget && routineTokens !== undefined) {
30
+ pressure = budget.capacityExceeded ? "over capacity" : budget.hardPressure ? "hard" : budget.pressured ? "strong"
31
+ : budget.projectedBeforeTokens > routineTokens ? "routine" : "below routine threshold";
32
+ }
33
+ return { model: model?.provider && model?.id ? `${model.provider}/${model.id}` : undefined,
34
+ contextWindow, rawTokens, projectedTokens, inputCapacityTokens: budget?.inputCapacityTokens,
35
+ reservedOutputTokens: budget?.reservedOutputTokens, routineTokens, strongTokens: budget?.softHeadroomTokens,
36
+ hardTokens: budget?.hardHeadroomTokens, enabled: config.enabled, manualMode: state.manualMode,
37
+ autoEnabled: config.compress.autoCompress.enabled, ignored: state.consecutiveIgnoredNudges,
38
+ routineProjectedTokens: budget?.projectedBeforeTokens ?? projectedTokens,
39
+ routineUsageAdjustmentTokens: 0,
40
+ pressure, reason, createdAt: Date.now() };
41
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Proof that a tool result was produced locally in this epoch and has never
3
+ * belonged to an attempted provider prefix. This is NOT provider-seen evidence:
4
+ * failed/ambiguous sends consume freshness too. Never persist or rehydrate it.
5
+ */
6
+ export class FreshToolResultTracker {
7
+ private ready = false;
8
+ private readonly pending = new Map<string, string>();
9
+ private readonly fresh = new Set<string>();
10
+
11
+ reset(): void {
12
+ this.ready = false;
13
+ this.pending.clear();
14
+ this.fresh.clear();
15
+ }
16
+
17
+ /** Call before every send, not after successful completion. */
18
+ beforeProviderRequest(): void {
19
+ this.reset();
20
+ this.ready = true;
21
+ }
22
+
23
+ toolCall(id: string, name: string, alreadyKnown: boolean): void {
24
+ // Resumed/in-flight old calls and reused IDs cannot create a freshness grant.
25
+ if (!this.ready || alreadyKnown) return;
26
+ this.pending.set(id, name);
27
+ }
28
+
29
+ toolResult(id: string, name: string): void {
30
+ const expectedName = this.pending.get(id);
31
+ this.pending.delete(id);
32
+ if (expectedName === name) this.fresh.add(id);
33
+ }
34
+
35
+ get eligibleIds(): ReadonlySet<string> {
36
+ return this.fresh;
37
+ }
38
+ }