pi-ui-extend 1.0.37 → 1.0.39

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 (44) hide show
  1. package/README.md +1 -0
  2. package/dist/app/extensions/extension-ui-controller.js +3 -0
  3. package/dist/app/rendering/extension-entry-renderer.js +2 -0
  4. package/dist/bundled-extensions/question/contract.d.ts +3 -0
  5. package/dist/bundled-extensions/question/contract.js +24 -0
  6. package/dist/bundled-extensions/question/desktop.d.ts +11 -0
  7. package/dist/bundled-extensions/question/desktop.js +143 -0
  8. package/dist/bundled-extensions/question/index.d.ts +1 -0
  9. package/dist/bundled-extensions/question/index.js +5 -1
  10. package/dist/bundled-extensions/question/render.js +6 -0
  11. package/dist/bundled-extensions/question/result.js +71 -9
  12. package/dist/bundled-extensions/question/tool-description.js +4 -3
  13. package/dist/bundled-extensions/question/tui.js +127 -12
  14. package/dist/bundled-extensions/question/types.d.ts +23 -2
  15. package/dist/tool-renderers/question.js +20 -1
  16. package/docs/desktop-markdown-media.md +77 -0
  17. package/docs/desktop-mvp.md +134 -0
  18. package/docs/desktop-task-manager.md +124 -0
  19. package/external/pi-tools-suite/package.json +3 -3
  20. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +9 -1
  21. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +41 -2
  22. package/external/pi-tools-suite/src/async-subagents/core/config.ts +13 -1
  23. package/external/pi-tools-suite/src/async-subagents/index.ts +6 -2
  24. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +65 -22
  25. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +23 -8
  26. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +180 -41
  27. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +1 -1
  28. package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
  29. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -51
  30. package/external/pi-tools-suite/src/dcp/debug-log.ts +6 -0
  31. package/external/pi-tools-suite/src/dcp/index.ts +27 -125
  32. package/external/pi-tools-suite/src/dcp/prompts.ts +2 -2
  33. package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +2 -1
  34. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +31 -10
  35. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +6 -7
  36. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +153 -111
  37. package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +28 -17
  38. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +61 -27
  39. package/external/pi-tools-suite/src/dcp/pruner.ts +21 -13
  40. package/external/pi-tools-suite/src/dcp/state.ts +59 -0
  41. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +4 -0
  42. package/external/pi-tools-suite/src/lib/rpc-session-state.ts +34 -0
  43. package/external/pi-tools-suite/src/todo/todo.ts +8 -3
  44. package/package.json +10 -4
@@ -119,6 +119,8 @@ export interface DcpNudgeAnchor {
119
119
  turnIndex: number
120
120
  /** Approximate context usage that triggered the reminder, as a 0-1 fraction. */
121
121
  contextPercent?: number
122
+ /** Frozen provider-visible reminder text for cache-stable reapplication. */
123
+ renderedReminder?: string
122
124
  /** Wall-clock creation time. */
123
125
  createdAt: number
124
126
  /** Wall-clock time of the latest re-application/update. */
@@ -166,6 +168,10 @@ export interface DcpState {
166
168
  messageIdSnapshot: Map<string, number>
167
169
  /** Extra metadata for the model-visible DCP message IDs in messageIdSnapshot. */
168
170
  messageMetaSnapshot: Map<string, MessageIdMeta>
171
+ /** Stable message identity → persistent model-visible mNNN assignment. */
172
+ messageIdsByStableId: Map<string, string>
173
+ /** Monotonic counter for persistent message IDs. */
174
+ nextMessageId: number
169
175
 
170
176
  // ── Turn tracking ──────────────────────────────────────────────────────────
171
177
  /**
@@ -174,6 +180,10 @@ export interface DcpState {
174
180
  * context array in the `context` event handler.
175
181
  */
176
182
  currentTurn: number
183
+ /** Last user-turn count at which automatic prune discovery ran. */
184
+ lastAutomaticPruneTurn: number
185
+ /** Highest created block ID at the last automatic-prune checkpoint. */
186
+ lastAutomaticPruneBlockId: number
177
187
 
178
188
  // ── Statistics ─────────────────────────────────────────────────────────────
179
189
  /** Running total of tokens estimated to have been saved by pruning/compression */
@@ -253,7 +263,11 @@ export function createState(): DcpState {
253
263
  nextBlockId: 1,
254
264
  messageIdSnapshot: new Map(),
255
265
  messageMetaSnapshot: new Map(),
266
+ messageIdsByStableId: new Map(),
267
+ nextMessageId: 1,
256
268
  currentTurn: 0,
269
+ lastAutomaticPruneTurn: -1,
270
+ lastAutomaticPruneBlockId: 0,
257
271
  tokensSaved: 0,
258
272
  totalPruneCount: 0,
259
273
  totalToolCallCount: 0,
@@ -285,7 +299,11 @@ export function resetState(state: DcpState): void {
285
299
  state.nextBlockId = 1
286
300
  state.messageIdSnapshot.clear()
287
301
  state.messageMetaSnapshot.clear()
302
+ state.messageIdsByStableId.clear()
303
+ state.nextMessageId = 1
288
304
  state.currentTurn = 0
305
+ state.lastAutomaticPruneTurn = -1
306
+ state.lastAutomaticPruneBlockId = 0
289
307
  state.tokensSaved = 0
290
308
  state.totalPruneCount = 0
291
309
  state.totalToolCallCount = 0
@@ -424,12 +442,18 @@ export interface SerializedDcpState {
424
442
  nudgeAnchors?: DcpNudgeAnchor[]
425
443
  nextNudgeAnchorId?: number
426
444
  lastNudge?: DcpLastNudge
445
+ /** Persistent stable-identity → mNNN assignments. */
446
+ messageIdsByStableId?: Array<[string, string]>
447
+ nextMessageId?: number
427
448
  /**
428
449
  * Persisted since v??. `context` events re-seed currentTurn from raw
429
450
  * messages, but keeping it across session restarts gives diagnostics and
430
451
  * telemetry a contiguous turn counter instead of resetting to 0.
431
452
  */
432
453
  currentTurn?: number
454
+ /** Automatic pruning runs only when one of these checkpoints changes. */
455
+ lastAutomaticPruneTurn?: number
456
+ lastAutomaticPruneBlockId?: number
433
457
  /**
434
458
  * Persisted since v?.?. Without persistence a pi process restart silently
435
459
  * reset the nudge cadence counter to 0, which could suppress the next
@@ -622,7 +646,11 @@ export function serializeState(state: DcpState): SerializedDcpState {
622
646
  nudgeAnchors: state.nudgeAnchors,
623
647
  nextNudgeAnchorId: state.nextNudgeAnchorId,
624
648
  lastNudge: state.lastNudge,
649
+ messageIdsByStableId: Array.from(state.messageIdsByStableId.entries()),
650
+ nextMessageId: state.nextMessageId,
625
651
  currentTurn: state.currentTurn,
652
+ lastAutomaticPruneTurn: state.lastAutomaticPruneTurn,
653
+ lastAutomaticPruneBlockId: state.lastAutomaticPruneBlockId,
626
654
  nudgeCounter: state.nudgeCounter,
627
655
  lastNudgeTurn: state.lastNudgeTurn,
628
656
  lastContextWindow: state.lastContextWindow,
@@ -776,6 +804,31 @@ export function restoreState(state: DcpState, data: unknown): void {
776
804
  state.lastNudge = saved.lastNudge
777
805
  }
778
806
 
807
+ if (Array.isArray(saved.messageIdsByStableId)) {
808
+ const usedIds = new Set<string>()
809
+ const usedStableKeys = new Set<string>()
810
+ state.messageIdsByStableId = new Map(
811
+ saved.messageIdsByStableId.filter(
812
+ (entry): entry is [string, string] => {
813
+ if (!Array.isArray(entry) || typeof entry[0] !== "string" || typeof entry[1] !== "string") return false
814
+ if (!entry[0] || !/^m\d+$/.test(entry[1]) || usedIds.has(entry[1]) || usedStableKeys.has(entry[0])) return false
815
+ usedIds.add(entry[1])
816
+ usedStableKeys.add(entry[0])
817
+ return true
818
+ },
819
+ ),
820
+ )
821
+ }
822
+ const highestRestoredMessageId = Math.max(
823
+ 0,
824
+ ...Array.from(state.messageIdsByStableId.values()).map((id) => Number.parseInt(id.slice(1), 10) || 0),
825
+ )
826
+ if (typeof saved.nextMessageId === "number" && Number.isFinite(saved.nextMessageId) && saved.nextMessageId > 0) {
827
+ state.nextMessageId = Math.max(Math.floor(saved.nextMessageId), highestRestoredMessageId + 1)
828
+ } else {
829
+ state.nextMessageId = highestRestoredMessageId + 1
830
+ }
831
+
779
832
  // nudgeCounter: clamp to a non-negative integer so a corrupted payload
780
833
  // cannot stall reminders by going negative. Default 0 keeps older sessions
781
834
  // behaving like a fresh cadence on first post-restart context event.
@@ -793,6 +846,12 @@ export function restoreState(state: DcpState, data: unknown): void {
793
846
  if (typeof saved.currentTurn === "number" && Number.isFinite(saved.currentTurn) && saved.currentTurn >= 0) {
794
847
  state.currentTurn = Math.floor(saved.currentTurn)
795
848
  }
849
+ if (typeof saved.lastAutomaticPruneTurn === "number" && Number.isFinite(saved.lastAutomaticPruneTurn)) {
850
+ state.lastAutomaticPruneTurn = Math.floor(saved.lastAutomaticPruneTurn)
851
+ }
852
+ if (typeof saved.lastAutomaticPruneBlockId === "number" && Number.isFinite(saved.lastAutomaticPruneBlockId) && saved.lastAutomaticPruneBlockId >= 0) {
853
+ state.lastAutomaticPruneBlockId = Math.floor(saved.lastAutomaticPruneBlockId)
854
+ }
796
855
  if (typeof saved.lastContextWindow === "number" && Number.isFinite(saved.lastContextWindow) && saved.lastContextWindow > 0) {
797
856
  state.lastContextWindow = saved.lastContextWindow
798
857
  }
@@ -329,6 +329,10 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
329
329
  "description": "Use when the sub-agent should make or plan code changes for a feature, bug fix, or refactor.",
330
330
  "model": "openai-codex/gpt-5.6-sol",
331
331
  "fallbackModels": ["zai/glm-5.3"],
332
+ "modelByParent": {
333
+ "openai-codex/gpt-5.6-luna*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] },
334
+ "openai-codex/gpt-5.6-sol*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] }
335
+ },
332
336
  "thinking": "high"
333
337
  },
334
338
  "deep": {
@@ -0,0 +1,34 @@
1
+ /** Reserved RPC widget key consumed by Pix ACP as a structured event envelope. */
2
+ export const RPC_SESSION_STATE_WIDGET_KEY = "pix.session-state";
3
+ export const RPC_SESSION_STATE_ENV = "PIX_ACP_SESSION_STATE_BRIDGE";
4
+
5
+ export interface RpcSessionStateContext {
6
+ readonly mode?: unknown;
7
+ readonly ui?: {
8
+ setWidget?: (key: string, lines: string[] | undefined) => void;
9
+ };
10
+ }
11
+
12
+ /**
13
+ * Publish structured extension state through pi's supported RPC UI protocol.
14
+ *
15
+ * TUI/print/json modes keep using their native event consumers. Serialization
16
+ * is best-effort so an optional external UI can never break the source tool.
17
+ */
18
+ export function publishRpcSessionState(
19
+ ctx: RpcSessionStateContext | undefined,
20
+ channel: string,
21
+ data: unknown,
22
+ ): void {
23
+ if (
24
+ process.env[RPC_SESSION_STATE_ENV] !== "1"
25
+ || ctx?.mode !== "rpc"
26
+ || !ctx.ui?.setWidget
27
+ || !channel.trim()
28
+ ) return;
29
+ try {
30
+ ctx.ui.setWidget(RPC_SESSION_STATE_WIDGET_KEY, [channel, JSON.stringify(data)]);
31
+ } catch {
32
+ // The event bus remains authoritative when RPC UI transport is absent.
33
+ }
34
+ }
@@ -42,6 +42,7 @@ import {
42
42
  } from "./tool/types.js";
43
43
  import { formatCommandTaskLine } from "./view/format.js";
44
44
  import { formatStatusLabel } from "./view/labels.js";
45
+ import { publishRpcSessionState, type RpcSessionStateContext } from "../lib/rpc-session-state.js";
45
46
 
46
47
  const SECTION_PENDING = "── Pending ──";
47
48
  const SECTION_IN_PROGRESS = "── In Progress ──";
@@ -96,7 +97,9 @@ interface TodoToolRegistrationOptions extends TodoToolHooks {
96
97
  parameters?: typeof TodoParamsSchema;
97
98
  }
98
99
 
99
- type TodoStateEventContext = { sessionManager?: { getSessionFile?: () => unknown; getSessionId?: () => unknown } };
100
+ type TodoStateEventContext = RpcSessionStateContext & {
101
+ sessionManager?: { getSessionFile?: () => unknown; getSessionId?: () => unknown };
102
+ };
100
103
  type TodoStateEventEmitter = { events?: { emit?: (channel: string, data: unknown) => void } };
101
104
  type TodoStateEntryWriter = { appendEntry<T = unknown>(customType: string, data?: T): void };
102
105
 
@@ -225,7 +228,7 @@ export function publishTodoState(
225
228
  const state = getState();
226
229
  const sessionFile = sessionFileFromContext(ctx);
227
230
  const sessionId = sessionIdFromContext(ctx);
228
- pi.events?.emit?.(TODO_STATE_EVENT, {
231
+ const snapshot = {
229
232
  version: 1,
230
233
  details: {
231
234
  action,
@@ -236,7 +239,9 @@ export function publishTodoState(
236
239
  ...(sessionFile ? { sessionFile } : {}),
237
240
  ...(sessionId ? { sessionId } : {}),
238
241
  checkedAt: Date.now(),
239
- });
242
+ };
243
+ pi.events?.emit?.(TODO_STATE_EVENT, snapshot);
244
+ publishRpcSessionState(ctx as TodoStateEventContext | undefined, TODO_STATE_EVENT, snapshot);
240
245
  }
241
246
 
242
247
  function appendTodoStateSnapshot(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
5
5
  "private": false,
6
6
  "repository": {
@@ -60,6 +60,11 @@
60
60
  "test": "mise exec node@24.16.0 -- npm run test:inner",
61
61
  "test:inner": "node --import tsx --test \"tests/**/*.test.ts\"",
62
62
  "test:tools-suite": "npm --prefix external/pi-tools-suite test",
63
+ "build:acp": "npm --prefix acp run build",
64
+ "check:acp": "npm --prefix acp run check",
65
+ "dev:desktop": "npm --prefix desktop run dev",
66
+ "build:desktop": "npm --prefix desktop run build",
67
+ "check:desktop": "npm --prefix desktop run check",
63
68
  "test:browser-qa-e2e": "npm --prefix external/pi-tools-suite run test:browser-qa-e2e",
64
69
  "sync:pi-tools-suite": "node scripts/sync-pi-tools-suite.mjs",
65
70
  "sync:pi-tools-suite:check": "node scripts/sync-pi-tools-suite.mjs --check",
@@ -78,9 +83,10 @@
78
83
  "prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
79
84
  },
80
85
  "dependencies": {
81
- "@earendil-works/pi-ai": "0.84.4",
82
- "@earendil-works/pi-coding-agent": "0.84.4",
83
- "@earendil-works/pi-tui": "0.84.4",
86
+ "@earendil-works/pi-ai": "0.85.0",
87
+ "@earendil-works/pi-coding-agent": "0.85.0",
88
+ "@earendil-works/pi-server": "0.85.0",
89
+ "@earendil-works/pi-tui": "0.85.0",
84
90
  "@mariozechner/clipboard": "^0.3.9",
85
91
  "grok-mermaid": "0.2.2",
86
92
  "jsonc-parser": "3.3.1",