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
@@ -0,0 +1,175 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { planProspectiveTestOutputDelivery, parseTestBuildOutput } from "../context-gateway/test-output-parser.js";
4
+ import { classifyShellCommand, shellCommandText } from "../shell-command-policy.js";
5
+ import type { DcpConfig } from "./config.js";
6
+ import {
7
+ isToolRecordPruningProtected,
8
+ isToolRecordProtectedByFilePattern,
9
+ } from "./pruner-tools.js";
10
+ import type { ToolRecord } from "./state.js";
11
+
12
+ export type ToolContinuityMode = "none" | "digest" | "verbatim";
13
+
14
+ export interface ToolContinuityDecision {
15
+ mode: ToolContinuityMode;
16
+ text?: string;
17
+ reason: string;
18
+ }
19
+
20
+ const SHELL_TOOLS = new Set(["bash", "shell", "powershell", "exec", "execute"]);
21
+ const MAX_COMMAND_CHARS = 2_048;
22
+ const MAX_ERROR_EXCERPT_CHARS = 2_048;
23
+ const TEST_DIGEST_MAX_BYTES = 8_192;
24
+
25
+ function normalizeToolName(name: string): string {
26
+ return name.trim().toLowerCase().replace(/[\s-]+/g, "_");
27
+ }
28
+
29
+ function stableDigest(text: string): string {
30
+ return createHash("sha256").update(text).digest("hex");
31
+ }
32
+
33
+ function renderedCommand(record: ToolRecord): string {
34
+ const command = shellCommandText(record.inputArgs);
35
+ if (!command) return "unavailable";
36
+ if (command.length <= MAX_COMMAND_CHARS) return command;
37
+ return `[command omitted: ${command.length} chars, sha256:${stableDigest(command)}]`;
38
+ }
39
+
40
+ function outputIdentity(output: string): string {
41
+ return `${Buffer.byteLength(output, "utf8")} bytes, sha256:${stableDigest(output)}`;
42
+ }
43
+
44
+ function boundedErrorExcerpt(output: string): string | undefined {
45
+ const lines = output.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim().length > 0);
46
+ const selected: string[] = [];
47
+ const seen = new Set<string>();
48
+ const add = (line: string) => {
49
+ if (seen.has(line)) return;
50
+ seen.add(line);
51
+ selected.push(line);
52
+ };
53
+ for (const line of lines) {
54
+ if (/\b(error|failed?|failure|exception|denied|not found|cannot|unable)\b/i.test(line)) add(line);
55
+ }
56
+ for (const line of lines.slice(-6)) add(line);
57
+ if (selected.length === 0) return undefined;
58
+ let text = selected.join("\n");
59
+ if (text.length > MAX_ERROR_EXCERPT_CHARS) text = text.slice(0, MAX_ERROR_EXCERPT_CHARS) + "\n[excerpt truncated]";
60
+ return text;
61
+ }
62
+
63
+ function shellDigest(record: ToolRecord, kind: "inspection" | "test-build", body?: string): string {
64
+ const output = record.outputText ?? "";
65
+ const lines = [
66
+ `### Tool continuity: ${record.toolName}`,
67
+ `Command: ${renderedCommand(record)}`,
68
+ `Classification: ${kind}`,
69
+ `Outcome: ${record.isError ? "error" : "success"}`,
70
+ `Raw output identity: ${outputIdentity(output)}`,
71
+ ];
72
+ if (body) lines.push(body);
73
+ else if (record.isError) {
74
+ const excerpt = boundedErrorExcerpt(output);
75
+ if (excerpt) lines.push("Actionable error excerpt:", excerpt);
76
+ }
77
+ lines.push("Exact raw output is not retained in protected continuity; rerun the command only if exact text is needed.");
78
+ return lines.join("\n");
79
+ }
80
+
81
+ function verbatimToolText(record: ToolRecord): string | undefined {
82
+ const output = (record.outputText ?? "").trim();
83
+ return output ? `### Tool: ${record.toolName}\n${output}` : undefined;
84
+ }
85
+
86
+ function preferSmallerDigest(
87
+ record: ToolRecord,
88
+ reason: string,
89
+ digest: string,
90
+ ): ToolContinuityDecision {
91
+ const verbatim = verbatimToolText(record);
92
+ if (verbatim && digest.length >= verbatim.length) {
93
+ return { mode: "verbatim", reason: `${reason}-not-smaller`, text: verbatim };
94
+ }
95
+ return { mode: "digest", reason, text: digest };
96
+ }
97
+
98
+ function gatewayCompactRepresentation(record: ToolRecord): string | undefined {
99
+ const details = record.outputDetails;
100
+ if (!details || typeof details !== "object" || Array.isArray(details)) return undefined;
101
+ const marker = (details as Record<string, unknown>).contextGateway;
102
+ if (!marker || typeof marker !== "object" || Array.isArray(marker)) return undefined;
103
+ const value = marker as Record<string, unknown>;
104
+ if (value.version !== 1 || value.representation !== "test-build-compact") return undefined;
105
+ return record.outputText?.trim() || undefined;
106
+ }
107
+
108
+ /**
109
+ * Decide how much of a pruning-protected tool result must survive a compression
110
+ * roll-up. Pruning safety and continuity retention are deliberately separate:
111
+ * shell results remain protected from generic pruning, while proven read-only
112
+ * inspection and confidently parsed test/build output can keep only a bounded
113
+ * continuation digest.
114
+ */
115
+ export function toolRecordContinuity(record: ToolRecord, config: DcpConfig): ToolContinuityDecision {
116
+ if (!isToolRecordPruningProtected(record, config)) return { mode: "none", reason: "not-pruning-protected" };
117
+ const normalized = normalizeToolName(record.toolName);
118
+ const output = record.outputText ?? "";
119
+ if (!SHELL_TOOLS.has(normalized)) {
120
+ return {
121
+ mode: "verbatim",
122
+ reason: "non-shell-protected-tool",
123
+ text: verbatimToolText(record),
124
+ };
125
+ }
126
+ if (isToolRecordProtectedByFilePattern(record, config)) {
127
+ return {
128
+ mode: "verbatim",
129
+ reason: "protected-file-pattern",
130
+ text: verbatimToolText(record),
131
+ };
132
+ }
133
+ const gatewayCompact = gatewayCompactRepresentation(record);
134
+ if (gatewayCompact) {
135
+ return {
136
+ mode: "verbatim",
137
+ reason: "gateway-compacted",
138
+ text: `### Tool: ${record.toolName}\n${gatewayCompact}`,
139
+ };
140
+ }
141
+
142
+ const classification = classifyShellCommand(record.inputArgs);
143
+ if (classification.kind === "inspection") {
144
+ return preferSmallerDigest(record, "read-only-inspection", shellDigest(record, "inspection"));
145
+ }
146
+ if (classification.kind === "test-build" && classification.scope === "simple") {
147
+ const parsed = parseTestBuildOutput({
148
+ text: output,
149
+ hostOutcome: record.isError ? "error" : "success",
150
+ });
151
+ const compact = planProspectiveTestOutputDelivery(parsed, TEST_DIGEST_MAX_BYTES, { commandScope: "simple" });
152
+ if (compact.decision === "compact-candidate" && compact.text) {
153
+ return preferSmallerDigest(
154
+ record,
155
+ "recognised-test-build",
156
+ shellDigest(record, "test-build", compact.text),
157
+ );
158
+ }
159
+ return {
160
+ mode: "verbatim",
161
+ reason: `test-build-${compact.reason}`,
162
+ text: verbatimToolText(record),
163
+ };
164
+ }
165
+
166
+ return {
167
+ mode: "verbatim",
168
+ reason: classification.kind === "mutation"
169
+ ? "shell-mutation"
170
+ : classification.scope === "compound"
171
+ ? "shell-compound"
172
+ : "shell-unknown",
173
+ text: verbatimToolText(record),
174
+ };
175
+ }
@@ -102,8 +102,17 @@ export interface CompressionCandidateSelectionOptions {
102
102
  requiredSavingsTokens?: number;
103
103
  /** Conservative estimator margin added to the required source size. */
104
104
  estimatorMarginTokens?: number;
105
+ /**
106
+ * Whether an automatic candidate may fold already-compressed block
107
+ * placeholders into a new block. Capacity-recovery plans disable this so an
108
+ * economical existing summary is not expanded again while rescuing raw tail
109
+ * growth. Manual/non-pressure consolidation keeps the historical default.
110
+ */
111
+ allowCompressionBlocks?: boolean;
105
112
  }
106
113
 
114
+ const MIN_AUTOMATIC_BLOCK_CONSOLIDATION = 8;
115
+
107
116
  function selectOldestSafePrefix(
108
117
  boundaries: CandidateBoundary[],
109
118
  state: DcpState,
@@ -176,7 +185,9 @@ export function detectCompressionCandidate(
176
185
  if (!settings.enabled) return null;
177
186
  if (contextPercent < settings.minContextPercent) return null;
178
187
 
179
- const boundaries = buildCandidateBoundaries(messages, _state, { allowBlocks: true });
188
+ const boundaries = buildCandidateBoundaries(messages, _state, {
189
+ allowBlocks: options?.allowCompressionBlocks ?? true,
190
+ });
180
191
 
181
192
  if (boundaries.length < settings.minMessages) return null;
182
193
 
@@ -224,6 +235,83 @@ export function detectCompressionCandidate(
224
235
  };
225
236
  }
226
237
 
238
+ /**
239
+ * Periodically fold a contiguous prefix of already-compressed summaries into
240
+ * one summary. Raw-tail recovery deliberately excludes bN blocks so it cannot
241
+ * recreate the incident's b1 -> b2 -> ... ladder; without a separate bounded
242
+ * consolidation step, however, a marathon turn can accumulate dozens of small
243
+ * active summaries until summary overhead itself fills the provider window.
244
+ *
245
+ * This detector is intentionally block-only: every selected boundary must be a
246
+ * synthetic block message and physically adjacent in the provider projection.
247
+ * Requiring a batch of blocks amortizes the intentional prefix rewrite and
248
+ * prevents one-old-summary-plus-a-tiny-tail recompression on every turn.
249
+ */
250
+ export function detectCompressionBlockConsolidationCandidate(
251
+ messages: any[],
252
+ state: DcpState,
253
+ config: DcpConfig,
254
+ options?: CompressionCandidateSelectionOptions,
255
+ ): CompressionCandidate | null {
256
+ const settings = config.compress.autoCandidates;
257
+ const boundaries = buildCandidateBoundaries(messages, state, { allowBlocks: true });
258
+ const minimumBlocks = MIN_AUTOMATIC_BLOCK_CONSOLIDATION;
259
+ const requiredSavings = Math.max(0, Math.floor(options?.requiredSavingsTokens ?? 0));
260
+ const estimatorMargin = Math.max(0, Math.floor(options?.estimatorMarginTokens ?? 0));
261
+ const targetSourceTokens = Math.max(settings.minTokens, requiredSavings + estimatorMargin);
262
+
263
+ let run: CandidateBoundary[] = [];
264
+ const chooseRun = (candidateRun: CandidateBoundary[]): CandidateBoundary[] | null => {
265
+ if (candidateRun.length < minimumBlocks) return null;
266
+ let selectedTokens = 0;
267
+ let selectedEnd = minimumBlocks - 1;
268
+ for (let index = 0; index < candidateRun.length; index++) {
269
+ selectedTokens += candidateRun[index]!.tokenEstimate;
270
+ if (index + 1 >= minimumBlocks && selectedTokens >= targetSourceTokens) {
271
+ selectedEnd = index;
272
+ break;
273
+ }
274
+ selectedEnd = index;
275
+ }
276
+ return candidateRun.slice(0, Math.max(minimumBlocks, selectedEnd + 1));
277
+ };
278
+
279
+ for (const boundary of boundaries) {
280
+ const previous = run.at(-1);
281
+ const continuesBlockRun = boundary.blockId !== undefined &&
282
+ (!previous || boundary.messageIndex === previous.messageIndex + 1);
283
+ if (continuesBlockRun) {
284
+ run.push(boundary);
285
+ continue;
286
+ }
287
+ const selected = chooseRun(run);
288
+ if (selected) {
289
+ const estimatedTokens = selected.reduce((sum, item) => sum + item.tokenEstimate, 0);
290
+ return {
291
+ startId: selected[0]!.id,
292
+ endId: selected[selected.length - 1]!.id,
293
+ messageCount: selected.length,
294
+ estimatedTokens,
295
+ includedBlockIds: selected.map((item) => item.blockId!),
296
+ reason: `automatic block-only consolidation of ${selected.length} adjacent summaries`,
297
+ };
298
+ }
299
+ run = boundary.blockId !== undefined ? [boundary] : [];
300
+ }
301
+
302
+ const selected = chooseRun(run);
303
+ if (!selected) return null;
304
+ const estimatedTokens = selected.reduce((sum, item) => sum + item.tokenEstimate, 0);
305
+ return {
306
+ startId: selected[0]!.id,
307
+ endId: selected[selected.length - 1]!.id,
308
+ messageCount: selected.length,
309
+ estimatedTokens,
310
+ includedBlockIds: selected.map((item) => item.blockId!),
311
+ reason: `automatic block-only consolidation of ${selected.length} adjacent summaries`,
312
+ };
313
+ }
314
+
227
315
  /**
228
316
  * Emergency-only range candidate for marathon turns.
229
317
  *
@@ -257,7 +345,9 @@ export function detectEmergencyCompressionCandidate(
257
345
  if (!emergencySettings.enabled) return null;
258
346
  if (contextPercent <= maxContextPercent) return null;
259
347
 
260
- const boundaries = buildCandidateBoundaries(messages, state, { allowBlocks: true });
348
+ const boundaries = buildCandidateBoundaries(messages, state, {
349
+ allowBlocks: options?.allowCompressionBlocks ?? true,
350
+ });
261
351
  if (boundaries.length < settings.minMessages) return null;
262
352
 
263
353
  let latestUserIndex = -1;
@@ -453,5 +543,5 @@ export function formatMessageCompressionCandidateHint(candidates: MessageCompres
453
543
  const entries = candidates
454
544
  .map((candidate) => `${candidate.messageId} (${candidate.priority}, ${candidate.role}, ~${candidate.estimatedTokens} tokens)`)
455
545
  .join(", ");
456
- return `\n\nSuggested individual message compression candidates: ${entries}. To compress individual messages, call \`compress\` with a \`messages\` array: { messageId, topic, summary }. Use this for large stale messages when a full range would be too broad.`;
546
+ return `\n\nSuggested individual message compression candidates: ${entries}. To compress individual messages, call \`compress\` with a \`messages\` array: { messageId, topic }. Omit \`summary\` to use the configured DCP summarizer; provide it only when parent-authored wording is required. Use message mode when a full range would be too broad.`;
457
547
  }
@@ -113,9 +113,9 @@ function persistentMessageId(stableKey: string, state: DcpState): string {
113
113
  }
114
114
 
115
115
  function carrierSelfLabel(role: string): string {
116
- if (role === "toolResult") return "this tool result";
117
- if (role === "bashExecution") return "this bash result";
118
- return "this user message";
116
+ if (role === "toolResult") return "t";
117
+ if (role === "bashExecution") return "x";
118
+ return "u";
119
119
  }
120
120
 
121
121
  function buildCarrierControlText(
@@ -125,15 +125,11 @@ function buildCarrierControlText(
125
125
  blockId: number | undefined,
126
126
  ): string {
127
127
  const assignments = [
128
- ...precedingAssistantIds.map((id) => `${id}=preceding assistant message`),
128
+ ...precedingAssistantIds.map((id) => `${id}=a`),
129
129
  `${ownId}=${carrierSelfLabel(ownRole)}`,
130
130
  ];
131
- return [
132
- "<dcp-message-ids>",
133
- `Stable DCP IDs (use with compress; do not quote/output): ${assignments.join("; ")}`,
134
- ...(blockId === undefined ? [] : [`Active compressed block alias: b${blockId}`]),
135
- "</dcp-message-ids>",
136
- ].join("\n");
131
+ if (blockId !== undefined) assignments.push(`b${blockId}=b`);
132
+ return `<dcp-message-ids>${assignments.join(";")}</dcp-message-ids>`;
137
133
  }
138
134
 
139
135
  function appendControlToCarrier(message: any, controlText: string): void {
@@ -32,31 +32,51 @@ function nudgeTypeLabel(type: DcpNudgeType): string {
32
32
 
33
33
  function isRealAnchorCandidate(msg: any): boolean {
34
34
  const role = msg?.role ?? "";
35
- if (role !== "user" && role !== "assistant") return false;
35
+ if (role !== "user" && role !== "toolResult") return false;
36
36
  return msg?._dcpOrigin !== "block" && msg?._dcpOrigin !== "dcp-control";
37
37
  }
38
38
 
39
- function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId: string; timestamp: number; role: string } | null {
39
+ function findAnchorMessage(messages: any[], freshToolResultIds?: ReadonlySet<string>): { msg: any; index: number; stableId: string; timestamp: number; role: string } | null {
40
40
  const stableKeys = stableMessageKeys(messages);
41
- // A reminder may only be introduced on a fresh user tail. If assistant/tool
42
- // traffic already follows that user message then it has already belonged to
43
- // an earlier provider prefix; editing it now would invalidate that prefix.
41
+ // Only append to a fresh tail; never edit an earlier user/assistant prefix.
42
+ // Tool-result freshness requires a positive local lifecycle grant, not the
43
+ // absence of successful provider evidence (which is unknown after restart).
44
44
  for (let i = messages.length - 1; i >= 0; i--) {
45
45
  const msg = messages[i];
46
46
  if (msg?._dcpOrigin === "dcp-control") continue;
47
- if (msg?.role !== "user" || !isRealAnchorCandidate(msg) || !Number.isFinite(msg.timestamp)) return null;
47
+ if (!isRealAnchorCandidate(msg) || !Number.isFinite(msg.timestamp)) return null;
48
+ if (msg.role === "toolResult") {
49
+ if (!freshToolResultIds?.has(msg.toolCallId)) return null;
50
+ // A duplicate/reused call ID is not an unambiguous fresh carrier.
51
+ if (messages.filter((item) => item?.role === "toolResult" && item.toolCallId === msg.toolCallId).length !== 1) return null;
52
+ }
48
53
  return { msg, index: i, stableId: stableKeys[i]!, timestamp: msg.timestamp, role: msg.role };
49
54
  }
50
55
 
51
56
  return null;
52
57
  }
53
58
 
54
- export function hasCacheSafeNudgeCarrier(messages: any[]): boolean {
55
- return findAnchorMessage(messages) !== null;
59
+ export function hasCacheSafeNudgeCarrier(
60
+ messages: any[],
61
+ freshToolResultIds?: ReadonlySet<string>,
62
+ anchors: readonly DcpNudgeAnchor[] = [],
63
+ ): boolean {
64
+ if (findAnchorMessage(messages, freshToolResultIds) !== null) return true;
65
+ // A previously published reminder remains deliverable without editing its
66
+ // carrier. Losing the grant to create a NEW reminder on a provider attempt
67
+ // must not make retries bypass the completed-opportunity patience policy.
68
+ const stableKeys = stableMessageKeys(messages);
69
+ return anchors.some((anchor) => typeof anchor.renderedReminder === "string" &&
70
+ anchor.renderedReminder.length > 0 &&
71
+ messages.some((message, index) => anchorMatchesMessage(anchor, message, stableKeys[index]!)));
56
72
  }
57
73
 
58
74
  function anchorMatchesMessage(anchor: DcpNudgeAnchor, msg: any, stableKey: string): boolean {
59
- if (anchor.anchorStableId && stableKey === anchor.anchorStableId) return true;
75
+ if (!isRealAnchorCandidate(msg) || msg.role !== anchor.anchorRole) return false;
76
+ if (anchor.anchorStableId) return stableKey === anchor.anchorStableId;
77
+ // Legacy user anchors may be timestamp-only. Tool anchors must never rebind
78
+ // to another parallel result that happens to share their timestamp.
79
+ if (msg.role === "toolResult") return false;
60
80
  return msg?.timestamp === anchor.anchorTimestamp;
61
81
  }
62
82
 
@@ -78,24 +98,8 @@ function appendTextToMessage(msg: any, text: string): void {
78
98
  return;
79
99
  }
80
100
 
81
- const textBlock = { type: "text", text: suffix };
82
- if (msg.role !== "assistant") {
83
- msg.content = [...msg.content, textBlock];
84
- return;
85
- }
86
-
87
- // Keep provider ordering constraints: assistant text must appear before
88
- // toolCall blocks for models that enforce text/thinking/tool_use ordering.
89
- const firstToolCallIdx = msg.content.findIndex((block: any) => block?.type === "toolCall");
90
- if (firstToolCallIdx === -1) {
91
- msg.content = [...msg.content, textBlock];
92
- } else {
93
- msg.content = [
94
- ...msg.content.slice(0, firstToolCallIdx),
95
- textBlock,
96
- ...msg.content.slice(firstToolCallIdx),
97
- ];
98
- }
101
+ // Only user/tool-result carriers reach here. Assistant items are immutable.
102
+ msg.content = [...msg.content, { type: "text", text: suffix }];
99
103
  }
100
104
 
101
105
  function insertBeforeReminderClose(reminder: string, detail: string): string {
@@ -174,7 +178,7 @@ export function upsertNudgeAnchor(
174
178
  messages: any[],
175
179
  state: DcpState,
176
180
  type: DcpNudgeType,
177
- options: { contextPercent?: number; renderedReminder?: string } = {},
181
+ options: { contextPercent?: number; renderedReminder?: string; freshToolResultIds?: ReadonlySet<string> } = {},
178
182
  ): { anchor: DcpNudgeAnchor | null; created: boolean; updated: boolean } {
179
183
  const stableKeys = stableMessageKeys(messages);
180
184
  let existing: DcpNudgeAnchor | null = null;
@@ -198,11 +202,10 @@ export function upsertNudgeAnchor(
198
202
  state.lastNudge = undefined;
199
203
  }
200
204
 
201
- const target = findAnchorMessage(messages);
205
+ const target = findAnchorMessage(messages, options.freshToolResultIds);
202
206
  if (!target) {
203
- // Mid-turn reminder creation is deliberately deferred rather than
204
- // synthesizing an ephemeral tail message that would disappear on the next
205
- // provider continuation and break prefix equality.
207
+ // No positively fresh carrier: do not synthesize a disappearing tail or
208
+ // reinterpret unknown historical exposure as permission to edit history.
206
209
  return { anchor: null, created: false, updated: false };
207
210
  }
208
211
 
@@ -263,7 +266,7 @@ export function applyAnchoredNudges(
263
266
  const reminder = selected.anchor.renderedReminder ?? render(selected.anchor);
264
267
  if (materializedReminder) selected.anchor.renderedReminder = reminder;
265
268
  const anchorMessage = messages[selected.index];
266
- if (anchorMessage?.role !== "user") {
269
+ if (!isRealAnchorCandidate(anchorMessage)) {
267
270
  state.nudgeAnchors = [];
268
271
  state.lastNudge = undefined;
269
272
  return { rendered: false, stateChanged: true };
@@ -83,7 +83,7 @@ function patternMatchesValue(pattern: string, value: string): boolean {
83
83
  return globToRegExp(normalizedPattern).test(normalizedValue);
84
84
  }
85
85
 
86
- function isProtectedByFilePattern(record: ToolRecord | undefined, config: DcpConfig): boolean {
86
+ export function isToolRecordProtectedByFilePattern(record: ToolRecord | undefined, config: DcpConfig): boolean {
87
87
  if (!record || config.protectedFilePatterns.length === 0) return false;
88
88
  const values = collectStringValues(record.inputArgs);
89
89
  return values.some((value) =>
@@ -91,16 +91,19 @@ function isProtectedByFilePattern(record: ToolRecord | undefined, config: DcpCon
91
91
  );
92
92
  }
93
93
 
94
- export function isToolRecordProtected(
94
+ export function isToolRecordPruningProtected(
95
95
  record: ToolRecord,
96
96
  config: DcpConfig,
97
97
  extraProtectedTools: string[] = [],
98
98
  ): boolean {
99
99
  const protectedTools = buildProtectedTools(config, extraProtectedTools);
100
100
  if (protectedTools.has(normalizeToolName(record.toolName))) return true;
101
- return isProtectedByFilePattern(record, config);
101
+ return isToolRecordProtectedByFilePattern(record, config);
102
102
  }
103
103
 
104
+ /** Backward-compatible name for callers that mean pruning protection. */
105
+ export const isToolRecordProtected = isToolRecordPruningProtected;
106
+
104
107
  export function markToolPruned(
105
108
  state: DcpState,
106
109
  toolCallId: string,
@@ -30,6 +30,7 @@ export {
30
30
  markToolPruned,
31
31
  } from "./pruner-tools.js";
32
32
  export {
33
+ detectCompressionBlockConsolidationCandidate,
33
34
  detectCompressionCandidate,
34
35
  detectEmergencyCompressionCandidate,
35
36
  detectMessageCompressionCandidates,
@@ -0,0 +1,86 @@
1
+ import { canonicalMessageHash } from "./conversation-index.js";
2
+ import type { CompressionBlock } from "./state.js";
3
+
4
+ interface ProjectionFrame {
5
+ owner: string;
6
+ avoidedTokens: number;
7
+ blocks: string;
8
+ }
9
+
10
+ export interface RoutinePressureEstimate {
11
+ projectedTokens: number;
12
+ adjustmentTokens: number;
13
+ }
14
+
15
+ function usableUsage(message: any): boolean {
16
+ if (message?.role !== "assistant" || ["error", "aborted"].includes(message.stopReason)) return false;
17
+ const usage = message.usage;
18
+ if (!usage) return false;
19
+ const tokens = usage.totalTokens || [usage.input, usage.output, usage.cacheRead, usage.cacheWrite]
20
+ .reduce((sum, value) => sum + (typeof value === "number" && Number.isFinite(value) ? value : 0), 0);
21
+ return typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0;
22
+ }
23
+
24
+ function blockVersion(blocks: readonly CompressionBlock[]): string {
25
+ return blocks.filter((block) => block.active).map((block) => block.id).join(",");
26
+ }
27
+
28
+ /**
29
+ * Runtime-only calibration for ROUTINE nudges, never a provider-send budget.
30
+ * The SDK's last assistant usage still includes history removed by a later DCP
31
+ * rewrite. Subtract only the additional locally measured raw/projected delta
32
+ * since that assistant's correlated request. This keeps provider overhead and
33
+ * tail growth, and avoids counting older compression twice. Uncorrelated usage
34
+ * and lifecycle changes fall back to the existing conservative estimate.
35
+ */
36
+ export class RoutinePressureTracker {
37
+ private prepared: ProjectionFrame | undefined;
38
+ private pending: ProjectionFrame | undefined;
39
+ private sample: (ProjectionFrame & { hash: string; timestamp: unknown }) | undefined;
40
+
41
+ reset(): void {
42
+ this.prepared = undefined;
43
+ this.pending = undefined;
44
+ this.sample = undefined;
45
+ }
46
+
47
+ prepare(owner: string, rawTokens: number, projectedTokens: number, blocks: readonly CompressionBlock[]): void {
48
+ this.prepared = { owner, avoidedTokens: rawTokens - projectedTokens, blocks: blockVersion(blocks) };
49
+ }
50
+
51
+ beforeRequest(): void {
52
+ this.pending = this.prepared;
53
+ }
54
+
55
+ complete(message: any, correlated: boolean): void {
56
+ const pending = this.pending;
57
+ this.pending = undefined;
58
+ if (!usableUsage(message)) return;
59
+ this.sample = correlated && pending && typeof message.timestamp === "number" && Number.isFinite(message.timestamp)
60
+ ? { ...pending, hash: canonicalMessageHash(message), timestamp: message.timestamp }
61
+ : undefined;
62
+ }
63
+
64
+ estimate(owner: string, nativeTokens: number | null | undefined, raw: readonly any[],
65
+ rawTokens: number, projectedTokens: number, blocks: readonly CompressionBlock[]): RoutinePressureEstimate {
66
+ const fallback = Math.max(projectedTokens, nativeTokens ?? 0);
67
+ const unchanged = { projectedTokens: fallback, adjustmentTokens: 0 };
68
+ const sample = this.sample;
69
+ if (!sample || sample.owner !== owner || nativeTokens === null || nativeTokens === undefined) return unchanged;
70
+ const version = blockVersion(blocks);
71
+ if (!version || version === sample.blocks) return unchanged;
72
+ // Confirm the SDK is still based on this exact successful assistant, not
73
+ // an unrelated/new response, failed stream or a sample from another owner.
74
+ let latest: any;
75
+ for (let index = raw.length - 1; index >= 0; index--) {
76
+ if (usableUsage(raw[index])) {
77
+ latest = raw[index];
78
+ break;
79
+ }
80
+ }
81
+ if (!latest || latest.timestamp !== sample.timestamp || canonicalMessageHash(latest) !== sample.hash) return unchanged;
82
+ const newSavings = Math.max(0, rawTokens - projectedTokens - sample.avoidedTokens);
83
+ const adjusted = Math.max(projectedTokens, nativeTokens - newSavings);
84
+ return { projectedTokens: adjusted, adjustmentTokens: fallback - adjusted };
85
+ }
86
+ }
@@ -153,6 +153,15 @@ export interface CompressionBlock {
153
153
  active: boolean
154
154
  /** Token estimate for the summary text itself */
155
155
  summaryTokenEstimate: number
156
+ /** One record per successful transaction (on its first block), never on retries.
157
+ * Optional for older journal blocks; absence means unmeasured, not zero gain. */
158
+ commitMetrics?: {
159
+ operationId: string
160
+ kind: "manual" | "auto" | "consolidation"
161
+ beforeTokens: number
162
+ afterTokens: number
163
+ netGainTokens: number
164
+ }
156
165
  /** Wall-clock time the block was created (Date.now()) */
157
166
  createdAt: number
158
167
  /** Older compression blocks subsumed by this block during roll-up. */
@@ -0,0 +1,8 @@
1
+ export interface DcpStatisticsInput {
2
+ branch?: readonly any[];
3
+ model?: any;
4
+ usage?: any;
5
+ historyStatus?: "full" | "unavailable";
6
+ }
7
+ export function collectDcpStatistics(input: DcpStatisticsInput): any;
8
+ export function formatDcpStatistics(input: DcpStatisticsInput): string;