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
@@ -1,6 +1,6 @@
1
1
  export type ContextGatewayMode = "off" | "observe" | "enforce";
2
2
 
3
- export type ContextGatewayEffectiveMode = Exclude<ContextGatewayMode, "enforce">;
3
+ export type ContextGatewayEffectiveMode = ContextGatewayMode;
4
4
 
5
5
  export interface ContextGatewayBudgets {
6
6
  maxInlineBytes: number;
@@ -10,10 +10,17 @@ export interface ContextGatewayBudgets {
10
10
  maxSearchMatches: number;
11
11
  }
12
12
 
13
- /** Parsed layered config. `enforce` remains a valid requested value so doctor can refuse it explicitly. */
13
+ export interface ContextGatewayAccountingLogConfig {
14
+ enabled: boolean;
15
+ maxBytes: number;
16
+ maxBackups: number;
17
+ }
18
+
19
+ /** Parsed layered config. `enforce` is selective and fail-open for unsupported result classes. */
14
20
  export interface ContextGatewayResolvedConfig {
15
21
  mode: ContextGatewayMode;
16
22
  budgets: ContextGatewayBudgets;
23
+ accountingLog: ContextGatewayAccountingLogConfig;
17
24
  issues: string[];
18
25
  }
19
26
 
@@ -48,7 +55,7 @@ export interface ContextGatewayObservedView {
48
55
  }
49
56
 
50
57
  export interface ContextGatewayObservedDelivery {
51
- representation: "passthrough";
58
+ representation: "passthrough" | "test-build-compact" | "web-recoverable-compact";
52
59
  contentBytes: number;
53
60
  textBytes: number;
54
61
  }
@@ -81,11 +88,13 @@ export interface ContextGatewayObservation {
81
88
  version: 1;
82
89
  toolClass: ContextGatewayToolClass;
83
90
  outcome: ContextGatewayExecutionOutcome;
91
+ budgetBytes: number;
84
92
  source: ContextGatewayObservedSource;
85
93
  view: ContextGatewayObservedView;
86
94
  delivery: ContextGatewayObservedDelivery;
87
95
  overBudget: boolean;
88
96
  potentialBytesOverBudget: number;
97
+ actualBytesSaved: number;
89
98
  testOutput?: ContextGatewayTestOutputObservation;
90
99
  }
91
100
 
@@ -93,12 +102,15 @@ export interface ContextGatewayClassTelemetry {
93
102
  results: number;
94
103
  errors: number;
95
104
  contentBytes: number;
105
+ deliveredContentBytes: number;
96
106
  textBytes: number;
97
107
  imageBytes: number;
98
108
  detailsBytes: number;
99
109
  upstreamTruncatedResults: number;
100
110
  overBudgetResults: number;
101
111
  potentialBytesOverBudget: number;
112
+ enforcedResults: number;
113
+ actualBytesSaved: number;
102
114
  }
103
115
 
104
116
  export type ContextGatewayNativePolicyReason =
@@ -134,6 +146,7 @@ export interface ContextGatewayTelemetrySnapshot extends ContextGatewayClassTele
134
146
  pendingCalls: number;
135
147
  unboundResults: number;
136
148
  repeatCandidateCount: number;
149
+ sameSourceDifferentRangeCount: number;
137
150
  retrievalCalls: number;
138
151
  nativePolicy: ContextGatewayNativePolicyTelemetry;
139
152
  testOutput: ContextGatewayTestOutputTelemetry;
@@ -0,0 +1,99 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ currentModelRef,
4
+ filterSubagentConfigForParentModel,
5
+ loadSubagentConfig,
6
+ } from "./async-subagents/lib.js";
7
+ import { publishRpcSessionState } from "./lib/rpc-session-state.js";
8
+
9
+ export const CONTEXT_INVENTORY_EVENT = "pi-tools-suite:context-inventory";
10
+
11
+ export interface ContextInventoryState {
12
+ version: 1;
13
+ reason?: "startup" | "reload" | "new" | "resume" | "fork" | "model_select";
14
+ sessionId?: string;
15
+ sessionFile?: string;
16
+ model?: string;
17
+ thinking?: string;
18
+ tools: string[];
19
+ skills: string[];
20
+ agents?: string[];
21
+ }
22
+
23
+ export function createContextInventoryState(
24
+ pi: Pick<ExtensionAPI, "getActiveTools" | "getCommands">,
25
+ ctx: Pick<ExtensionContext, "cwd" | "model" | "sessionManager">,
26
+ reason?: ContextInventoryState["reason"],
27
+ ): ContextInventoryState {
28
+ const tools = unique(pi.getActiveTools());
29
+ const skillsReadable = tools.includes("read") || tools.includes("bash");
30
+ const skills = skillsReadable
31
+ ? unique(pi.getCommands()
32
+ .filter((command) => command.source === "skill")
33
+ .map((command) => command.name.replace(/^skill:/u, "")))
34
+ .sort(compareText)
35
+ : [];
36
+ const model = currentModelRef(ctx.model);
37
+ const thinking = typeof (ctx as { thinkingLevel?: unknown }).thinkingLevel === "string"
38
+ ? (ctx as { thinkingLevel?: string }).thinkingLevel?.trim() || undefined
39
+ : undefined;
40
+ const agents = tools.includes("subagents") ? effectiveAgentTypes(ctx.cwd, model) : [];
41
+ const sessionId = safeSessionId(ctx.sessionManager);
42
+ const sessionFile = safeSessionFile(ctx.sessionManager);
43
+ return {
44
+ version: 1,
45
+ ...(reason ? { reason } : {}),
46
+ ...(sessionId ? { sessionId } : {}),
47
+ ...(sessionFile ? { sessionFile } : {}),
48
+ ...(model ? { model } : {}),
49
+ ...(thinking ? { thinking } : {}),
50
+ tools,
51
+ skills,
52
+ ...(agents === undefined ? {} : { agents }),
53
+ };
54
+ }
55
+
56
+ export function publishContextInventoryState(
57
+ pi: ExtensionAPI,
58
+ ctx: ExtensionContext,
59
+ reason?: ContextInventoryState["reason"],
60
+ ): void {
61
+ const state = createContextInventoryState(pi, ctx, reason);
62
+ pi.events?.emit?.(CONTEXT_INVENTORY_EVENT, state);
63
+ publishRpcSessionState(ctx, CONTEXT_INVENTORY_EVENT, state);
64
+ }
65
+
66
+ function effectiveAgentTypes(cwd: string, model: string | undefined): string[] | undefined {
67
+ try {
68
+ const config = filterSubagentConfigForParentModel(loadSubagentConfig(cwd), model);
69
+ return Object.keys(config.types).sort(compareText);
70
+ } catch {
71
+ return undefined;
72
+ }
73
+ }
74
+
75
+ function safeSessionId(sessionManager: ExtensionContext["sessionManager"]): string | undefined {
76
+ try {
77
+ const sessionId = sessionManager.getSessionId();
78
+ return sessionId?.trim() || undefined;
79
+ } catch {
80
+ return undefined;
81
+ }
82
+ }
83
+
84
+ function safeSessionFile(sessionManager: ExtensionContext["sessionManager"]): string | undefined {
85
+ try {
86
+ const sessionFile = sessionManager.getSessionFile();
87
+ return sessionFile?.trim() || undefined;
88
+ } catch {
89
+ return undefined;
90
+ }
91
+ }
92
+
93
+ function unique(values: readonly string[]): string[] {
94
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
95
+ }
96
+
97
+ function compareText(left: string, right: string): number {
98
+ return left.localeCompare(right);
99
+ }
@@ -5,13 +5,32 @@ import {
5
5
  type AutoCompressionResult,
6
6
  type CreateAutoCompressionBlockOptions,
7
7
  } from "./auto-compress.js";
8
- import { canonicalMessageHash } from "./conversation-index.js";
8
+ import { buildExactRangeMembership, canonicalMessageHash } from "./conversation-index.js";
9
9
  import { stableMessageKeys } from "./pruner-message-ids.js";
10
10
  import type { CompressionCandidate } from "./pruner-types.js";
11
11
  import type { DcpState } from "./state.js";
12
12
 
13
13
  const rejectedSources = new WeakMap<DcpState, { key: string; error: AutoCompressionBlockedError }>();
14
14
  const FINALIZATION_GRACE_MS = 1_000;
15
+ const SUMMARY_DEADLINE_FRACTION = 0.75;
16
+
17
+ function exactCandidateFingerprint(
18
+ options: CreateAutoCompressionBlockOptions,
19
+ candidate: CompressionCandidate,
20
+ ): { startId: string; endId: string; sourceMembers: Array<{ stableId: string; hash: string }> } | undefined {
21
+ const membership = buildExactRangeMembership(
22
+ options.state.conversationIndexSnapshot,
23
+ candidate.startId,
24
+ candidate.endId,
25
+ options.state,
26
+ );
27
+ if (!membership) return undefined;
28
+ return {
29
+ startId: candidate.startId,
30
+ endId: candidate.endId,
31
+ sourceMembers: membership.sourceMembers,
32
+ };
33
+ }
15
34
 
16
35
  /**
17
36
  * A source-size estimate is not a net-savings estimate. Try the economical
@@ -25,14 +44,28 @@ export async function createBudgetedAutoCompressionBlock(
25
44
  ): Promise<AutoCompressionResult> {
26
45
  options.signal?.throwIfAborted();
27
46
  const epoch = options.state.sessionEpoch;
47
+ const primaryFingerprint = exactCandidateFingerprint(options, options.candidate);
48
+ const largestFingerprint = largestSafeCandidate
49
+ ? exactCandidateFingerprint(options, largestSafeCandidate)
50
+ : undefined;
51
+ // Rejection memoization belongs to the exact source that was rejected, not
52
+ // to the whole provider tail. A long-running agent can append unrelated
53
+ // messages while the oldest safe candidate stays byte-identical; hashing the
54
+ // whole tail made that same impossible candidate run again after every tool
55
+ // result. Fall back to the full-context identity only when exact membership
56
+ // is unavailable, so memoization never guesses source equivalence.
57
+ const sourceIdentity = primaryFingerprint && (!largestSafeCandidate || largestFingerprint)
58
+ ? { primary: primaryFingerprint, largest: largestFingerprint }
59
+ : {
60
+ source: options.messages.map(canonicalMessageHash),
61
+ ids: stableMessageKeys(options.messages),
62
+ };
28
63
  const key = createHash("sha256").update(JSON.stringify({
29
64
  epoch,
30
- source: options.messages.map(canonicalMessageHash),
31
- ids: stableMessageKeys(options.messages),
65
+ sourceIdentity,
32
66
  config: options.config,
33
67
  budget: options.requiredGainTokens ?? 0,
34
68
  allowPartialGain: options.allowPartialGain === true,
35
- largest: largestSafeCandidate,
36
69
  })).digest("hex");
37
70
  const rejected = rejectedSources.get(options.state);
38
71
  if (rejected?.key === key) throw rejected.error;
@@ -41,6 +74,7 @@ export async function createBudgetedAutoCompressionBlock(
41
74
  const onAbort = () => controller.abort(options.signal?.reason);
42
75
  options.signal?.addEventListener("abort", onAbort, { once: true });
43
76
  const timeout = Math.max(1, options.config.compress.autoCompress.timeoutMs);
77
+ const summaryTimeoutMs = Math.max(1, Math.floor(timeout * SUMMARY_DEADLINE_FRACTION));
44
78
  // generateModelSummary owns the configured summarizer deadline and can
45
79
  // deliberately recover from it with the deterministic extractive fallback.
46
80
  // Keep a small bounded tail for that fallback and durable publication;
@@ -84,6 +118,7 @@ export async function createBudgetedAutoCompressionBlock(
84
118
  ...options,
85
119
  candidate: candidates[attempt]!,
86
120
  signal: controller.signal,
121
+ summaryTimeoutMs,
87
122
  });
88
123
  rejectedSources.delete(options.state);
89
124
  return result;
@@ -16,7 +16,7 @@ import { createHash } from "node:crypto"
16
16
  import type { Model, Api, ProviderHeaders } from "@earendil-works/pi-ai"
17
17
  import { completeWithModelRegistry, type ModelCompletionRegistry } from "../model-completion.js"
18
18
  import type { DcpState } from "./state.js"
19
- import type { DcpConfig } from "./config.js"
19
+ import { summarizerModelRefs, type DcpConfig } from "./config.js"
20
20
  import type { CompressionCandidate } from "./pruner-types.js"
21
21
  import { estimateMessageTokens, estimateTokens, stripStaleDcpMetadataLines } from "./pruner-metadata.js"
22
22
  import {
@@ -37,11 +37,19 @@ import { settleCompressionProgress } from "./compression-progress.js"
37
37
 
38
38
  export class AutoCompressionBlockedError extends Error {
39
39
  readonly blockedReason: DcpBlockedReason
40
+ readonly summarizerFallbackUsed: boolean
41
+ readonly summarizerAttempts?: ModelSummaryAttempt[]
40
42
 
41
- constructor(blockedReason: DcpBlockedReason, message: string) {
43
+ constructor(
44
+ blockedReason: DcpBlockedReason,
45
+ message: string,
46
+ details: { summarizerFallbackUsed?: boolean; summarizerAttempts?: ModelSummaryAttempt[] } = {},
47
+ ) {
42
48
  super(message)
43
49
  this.name = "AutoCompressionBlockedError"
44
50
  this.blockedReason = blockedReason
51
+ this.summarizerFallbackUsed = details.summarizerFallbackUsed === true
52
+ this.summarizerAttempts = details.summarizerAttempts
45
53
  }
46
54
  }
47
55
 
@@ -775,6 +783,80 @@ export async function generateModelSummary(
775
783
  return { attempts }
776
784
  }
777
785
 
786
+ export interface PreparedCompressionSummary {
787
+ text: string
788
+ representation: "model" | "extractive" | "extractive-fallback"
789
+ sourceManifest: SummarySourceItem[]
790
+ sourceHash: string
791
+ sourceCoverage: SummarySourceCoverage
792
+ summarizerModelRef?: string
793
+ summarizerAttempts?: ModelSummaryAttempt[]
794
+ }
795
+
796
+ /**
797
+ * Shared summary preparation for automatic and explicit compression. The
798
+ * configured model chain is attempted first; if it is absent/unavailable the
799
+ * deterministic extractive continuation record is the safety floor.
800
+ */
801
+ export async function prepareCompressionSummary(options: {
802
+ topic: string
803
+ messages: any[]
804
+ candidate?: CompressionCandidate
805
+ modelRefs: string[]
806
+ timeoutMs: number
807
+ modelRegistry?: ModelSummaryRegistry
808
+ signal?: AbortSignal
809
+ sourceManifest?: SummarySourceItem[]
810
+ }): Promise<PreparedCompressionSummary> {
811
+ options.signal?.throwIfAborted()
812
+ const sourceManifest = options.sourceManifest ?? buildSummarySourceManifest(options.messages)
813
+ let modelResult: ModelSummaryResult | undefined
814
+ if (options.modelRefs.length > 0) {
815
+ modelResult = await generateModelSummary(
816
+ options.modelRefs,
817
+ options.modelRegistry,
818
+ options.signal,
819
+ options.topic,
820
+ options.messages,
821
+ options.timeoutMs,
822
+ sourceManifest,
823
+ )
824
+ options.signal?.throwIfAborted()
825
+ }
826
+ const fallbackCandidate = options.candidate ?? {
827
+ startId: "generated",
828
+ endId: "generated",
829
+ messageCount: sourceManifest.length,
830
+ estimatedTokens: options.messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0),
831
+ includedBlockIds: [],
832
+ reason: "explicit/generated summary source",
833
+ }
834
+ const text = modelResult?.text ?? buildExtractiveSummary(
835
+ options.topic,
836
+ fallbackCandidate,
837
+ sourceManifest,
838
+ )
839
+ // Do not impose a second, fixed output ceiling on the deterministic safety
840
+ // floor. A large source can legitimately require more than 8192 tokens to
841
+ // preserve every explicit continuity checkpoint. The auto-compression path
842
+ // already measures the actual replacement against the exact source and
843
+ // rejects non-positive/full-projection gain below, so compression economics
844
+ // — not an unrelated constant — decide whether this fallback is usable.
845
+ return {
846
+ text,
847
+ representation: modelResult?.text
848
+ ? "model"
849
+ : options.modelRefs.length > 0
850
+ ? "extractive-fallback"
851
+ : "extractive",
852
+ sourceManifest,
853
+ sourceHash: hashSummarySourceManifest(sourceManifest),
854
+ sourceCoverage: summarySourceCoverage(sourceManifest),
855
+ summarizerModelRef: modelResult?.usedModelRef,
856
+ summarizerAttempts: modelResult?.attempts.length ? modelResult.attempts : undefined,
857
+ }
858
+ }
859
+
778
860
  function extractAssistantText(result: any): string | undefined {
779
861
  const content = result?.content
780
862
  if (!Array.isArray(content)) return undefined
@@ -807,6 +889,8 @@ export interface CreateAutoCompressionBlockOptions {
807
889
  requiredGainTokens?: number
808
890
  /** Allow a positive safe commit that reduces, but does not fully settle, the current recovery debt. */
809
891
  allowPartialGain?: boolean
892
+ /** Optional model-summary sub-deadline. The outer budgeted operation may reserve time for deterministic fallback/publication. */
893
+ summaryTimeoutMs?: number
810
894
  /** Optional durable publication hook. Live state is not changed unless it succeeds. */
811
895
  persistState?: (preparedState: DcpState, publication?: DcpJournalPublicationOptions) => Promise<void>
812
896
  /** Optional pure projection preparation; included in the same durable generation. */
@@ -845,8 +929,9 @@ function createAutoCompressionWorkingState(state: DcpState): DcpState {
845
929
 
846
930
  /**
847
931
  * Create the auto-compression block. Selects the summary source based on
848
- * `config.compress.autoCompress.summarizerModel`: empty → programmatic digest;
849
- * non-empty → model summary with programmatic fallback. Then delegates block
932
+ * `config.compress.autoCompress.summarizerModel` plus explicit
933
+ * `summarizerFallbackModels`: empty → programmatic digest; non-empty → model
934
+ * summary with ordered model fallbacks, then programmatic fallback. Delegates block
850
935
  * creation to the shared `createRangeCompressionBlock` path so protected
851
936
  * content (user messages, tool outputs, prompt info) is handled identically to
852
937
  * a model-initiated compress.
@@ -913,48 +998,26 @@ export async function createAutoCompressionBlock(
913
998
  }
914
999
  }
915
1000
 
916
- // Summary source selection. `summaryMode` distinguishes three cases so the
917
- // DCP debug log can tell a real model summary from a programmatic fallback
918
- // caused by summarizer failure:
919
- // - "model": a configured model produced the summary.
920
- // - "programmatic": no summarizer models configured (floor by design).
921
- // - "programmatic_fallback": models were configured but all failed/empty.
922
- const sourceManifest = buildSummarySourceManifest(messagesInRange)
923
- let summary = ""
924
- let summaryMode: "programmatic" | "model" | "programmatic_fallback" = "programmatic"
925
- let summarizerModelRef: string | undefined
926
- let summarizerAttempts: ModelSummaryAttempt[] | undefined
927
-
928
- const modelRefs = settings.summarizerModel
929
- if (modelRefs.length > 0) {
930
- const modelResult = await generateModelSummary(
931
- modelRefs,
932
- modelRegistry,
933
- signal,
934
- topic,
935
- messagesInRange,
936
- settings.timeoutMs,
937
- sourceManifest,
938
- )
939
- assertCurrent()
940
- summarizerAttempts = modelResult.attempts.length > 0 ? modelResult.attempts : undefined
941
- if (modelResult.text) {
942
- summary = modelResult.text
943
- summaryMode = "model"
944
- summarizerModelRef = modelResult.usedModelRef
945
- } else {
946
- // All configured models failed or returned empty — fall back to the
947
- // programmatic digest, but mark the mode distinctly so the fallback
948
- // is visible in DCP debug logs.
949
- summaryMode = "programmatic_fallback"
950
- }
951
- }
952
- if (!summary) {
953
- summary = buildExtractiveSummary(topic, effectiveCandidate, sourceManifest)
954
- if (estimateTokens(summary) > 8192) {
955
- throw new AutoCompressionBlockedError("budget-exhausted", "Extractive continuity minimum exceeds its 8192-token budget; no checkpoints were silently dropped")
956
- }
957
- }
1001
+ const preparedSummary = await prepareCompressionSummary({
1002
+ topic,
1003
+ messages: messagesInRange,
1004
+ candidate: effectiveCandidate,
1005
+ modelRefs: summarizerModelRefs(settings),
1006
+ timeoutMs: options.summaryTimeoutMs ?? settings.timeoutMs,
1007
+ modelRegistry,
1008
+ signal,
1009
+ })
1010
+ assertCurrent()
1011
+ const summary = preparedSummary.text
1012
+ const sourceManifest = preparedSummary.sourceManifest
1013
+ const summaryMode: "programmatic" | "model" | "programmatic_fallback" =
1014
+ preparedSummary.representation === "model"
1015
+ ? "model"
1016
+ : preparedSummary.representation === "extractive-fallback"
1017
+ ? "programmatic_fallback"
1018
+ : "programmatic"
1019
+ const summarizerModelRef = preparedSummary.summarizerModelRef
1020
+ const summarizerAttempts = preparedSummary.summarizerAttempts
958
1021
 
959
1022
  const workingState = createAutoCompressionWorkingState(state)
960
1023
  const anchor = resolveAnchorBoundary(endTimestamp, workingState, endBoundary.stableId)
@@ -1004,8 +1067,8 @@ export async function createAutoCompressionBlock(
1004
1067
  : summaryMode === "programmatic_fallback"
1005
1068
  ? "extractive-fallback"
1006
1069
  : "extractive"
1007
- const sourceHash = hashSummarySourceManifest(sourceManifest)
1008
- const sourceCoverage = summarySourceCoverage(sourceManifest)
1070
+ const sourceHash = preparedSummary.sourceHash
1071
+ const sourceCoverage = preparedSummary.sourceCoverage
1009
1072
  created.block.autoSummaryRepresentation = summaryRepresentation
1010
1073
  created.block.sourceHash = sourceHash
1011
1074
  created.block.sourceCoverage = sourceCoverage
@@ -1013,11 +1076,16 @@ export async function createAutoCompressionBlock(
1013
1076
  const sourceExactEstimate = messagesInRange.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
1014
1077
  const replacementExactEstimate = estimateCompressionBlockReplacementTokens(created.block)
1015
1078
  const projectedGain = sourceExactEstimate - replacementExactEstimate
1079
+ const blockedSummaryDetails = {
1080
+ summarizerFallbackUsed: preparedSummary.representation === "extractive-fallback",
1081
+ summarizerAttempts: preparedSummary.summarizerAttempts,
1082
+ }
1016
1083
  if (projectedGain <= 0) {
1017
1084
  throw new AutoCompressionBlockedError(
1018
1085
  "non-positive-gain",
1019
1086
  `Auto-compress rejected non-positive full-projection gain for ${effectiveCandidate.startId}..${effectiveCandidate.endId}: ` +
1020
1087
  `source ${sourceExactEstimate} tokens, replacement ${replacementExactEstimate} tokens`,
1088
+ blockedSummaryDetails,
1021
1089
  )
1022
1090
  }
1023
1091
  const requiredGainTokens = Math.max(0, Math.floor(options.requiredGainTokens ?? 0))
@@ -1026,6 +1094,7 @@ export async function createAutoCompressionBlock(
1026
1094
  "budget-exhausted",
1027
1095
  `Auto-compress projected gain for ${effectiveCandidate.startId}..${effectiveCandidate.endId} is below required budget recovery: ` +
1028
1096
  `${projectedGain} < ${requiredGainTokens} tokens`,
1097
+ blockedSummaryDetails,
1029
1098
  )
1030
1099
  }
1031
1100
 
@@ -1037,12 +1106,22 @@ export async function createAutoCompressionBlock(
1037
1106
  fullProjectedAfterTokens = finalProjection.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
1038
1107
  fullProjectionGain = fullBefore - fullProjectedAfterTokens
1039
1108
  if (fullProjectionGain <= 0 || (fullProjectionGain < requiredGainTokens && !options.allowPartialGain)) {
1040
- throw new AutoCompressionBlockedError("budget-exhausted", `Final provider projection saves ${fullProjectionGain}, below required ${requiredGainTokens}; no state published`)
1109
+ throw new AutoCompressionBlockedError(
1110
+ "budget-exhausted",
1111
+ `Final provider projection saves ${fullProjectionGain}, below required ${requiredGainTokens}; no state published`,
1112
+ blockedSummaryDetails,
1113
+ )
1041
1114
  }
1042
1115
  }
1043
1116
  const pressureRelieved = workingState.compressionProgress
1044
1117
  ? settleCompressionProgress(workingState, fullProjectionGain, fullProjectedAfterTokens)
1045
1118
  : true
1119
+ created.block.commitMetrics = {
1120
+ operationId: `auto:${created.block.id}`,
1121
+ kind: effectiveCandidate.includedBlockIds.length >= 2 && sourceCoverage.itemCount === effectiveCandidate.includedBlockIds.length ? "consolidation" : "auto",
1122
+ beforeTokens: fullProjectedAfterTokens + fullProjectionGain,
1123
+ afterTokens: fullProjectedAfterTokens, netGainTokens: fullProjectionGain,
1124
+ }
1046
1125
  assertCurrent()
1047
1126
  let published = false
1048
1127
  if (options.persistState) await options.persistState(workingState, {