pi-ui-extend 1.0.38 → 1.0.40

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 (46) hide show
  1. package/README.md +1 -1
  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/README.md +207 -0
  20. package/external/pi-tools-suite/docs/evals.md +684 -0
  21. package/external/pi-tools-suite/package.json +7 -3
  22. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +8 -0
  23. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +41 -2
  24. package/external/pi-tools-suite/src/async-subagents/core/config.ts +12 -0
  25. package/external/pi-tools-suite/src/async-subagents/index.ts +6 -2
  26. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +1 -1
  27. package/external/pi-tools-suite/src/coding-discipline/index.ts +41 -142
  28. package/external/pi-tools-suite/src/config.ts +0 -21
  29. package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
  30. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -51
  31. package/external/pi-tools-suite/src/dcp/debug-log.ts +6 -0
  32. package/external/pi-tools-suite/src/dcp/index.ts +27 -125
  33. package/external/pi-tools-suite/src/dcp/prompts.ts +2 -2
  34. package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +2 -1
  35. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +31 -10
  36. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +6 -7
  37. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +153 -111
  38. package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +28 -17
  39. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +61 -27
  40. package/external/pi-tools-suite/src/dcp/pruner.ts +21 -13
  41. package/external/pi-tools-suite/src/dcp/state.ts +59 -0
  42. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +27 -6
  43. package/external/pi-tools-suite/src/lib/rpc-session-state.ts +34 -0
  44. package/external/pi-tools-suite/src/todo/todo.ts +8 -3
  45. package/external/pi-tools-suite/src/tool-descriptions.ts +4 -3
  46. package/package.json +7 -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
  }
@@ -24,12 +24,6 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
24
24
  // Vision-capable model used by the coding-discipline lookup tool for blind-model
25
25
  // screenshot/image questions. Remove or set to null to disable lookup.
26
26
  "lookupModel": "zai/glm-5.3-flash",
27
- // coding-discipline working-state strictness.
28
- // "lenient" (default): batch independent read-only tool calls; brief reasoning
29
- // text is acceptable when thinking is off. Text only counts as chatter when a
30
- // thinking/reasoning block already captured the reasoning.
31
- // "strict": one tool call per turn, no assistant text at all (Opus-like).
32
- "codingDisciplineStrictness": "lenient",
33
27
  "terminalBell": { "sound": true },
34
28
  // comment-checker: nudges the agent to remove AI-slop code comments it just
35
29
  // added via write/edit/apply_patch. Net-new comments are classified and a
@@ -329,6 +323,10 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
329
323
  "description": "Use when the sub-agent should make or plan code changes for a feature, bug fix, or refactor.",
330
324
  "model": "openai-codex/gpt-5.6-sol",
331
325
  "fallbackModels": ["zai/glm-5.3"],
326
+ "modelByParent": {
327
+ "openai-codex/gpt-5.6-luna*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] },
328
+ "openai-codex/gpt-5.6-sol*": { "model": "openai-codex/gpt-5.6-terra", "fallbackModels": ["zai/glm-5.3"] }
329
+ },
332
330
  "thinking": "high"
333
331
  },
334
332
  "deep": {
@@ -394,6 +392,29 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
394
392
  }
395
393
  // ,
396
394
  // {
395
+ // "id": "svelte",
396
+ // "include": [
397
+ // "**/*.svelte"
398
+ // ],
399
+ // "exclude": [
400
+ // "**/node_modules/**"
401
+ // ],
402
+ // "rootMarkers": [
403
+ // "svelte.config.js",
404
+ // "package.json"
405
+ // ],
406
+ // "bin": "svelteserver",
407
+ // "args": [
408
+ // "--stdio"
409
+ // ],
410
+ // "startupTimeoutMs": 30000,
411
+ // "diagnosticsWaitMs": 8000,
412
+ // "languageIdByExtension": {
413
+ // ".svelte": "svelte"
414
+ // }
415
+ // }
416
+ // ,
417
+ // {
397
418
  // "id": "python",
398
419
  // "include": [
399
420
  // "**/*.py",
@@ -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(
@@ -39,10 +39,11 @@ export function astGrepToolDescriptions(maxLines: number, maxBytesLabel: string)
39
39
  astGrep: {
40
40
  name: "ast_grep",
41
41
  label: "ast-grep",
42
- description: `Read-only AST structural search/scan. Use for language-aware patterns, sgconfig/rule scans, JSON matches, and rewrite previews. Use text search for plain strings and ast_apply for mutations. Output truncates at ${maxLines} lines or ${maxBytesLabel} with full output saved to a temp file.`,
43
- promptSnippet: "Use ast_grep for AST/structural code search, not plain text search. It previews rewrites only; use ast_apply to mutate files.",
42
+ description: `Read-only AST structural search/scan. MANDATORY ROUTING: for structural, syntax-aware, AST, language-aware, or code-shape matching, ast_grep must be the FIRST tool call. Never preflight with Glob, Grep, Read, or shell even when files are unknown; omit paths to scan the current project. Use for sgconfig/rule scans, JSON matches, and rewrite previews. Use Grep for exact literals/regex, Glob only for filename/path-only requests, and ast_apply for mutations. Output truncates at ${maxLines} lines or ${maxBytesLabel} with full output saved to a temp file.`,
43
+ promptSnippet: "MANDATORY: structural/syntax-aware/AST/code-shape matching => call ast_grep FIRST. Do not call Glob, Grep, Read, or shell before it; unknown files are not a reason to pre-search because ast_grep scans the project by default. Exact literal/regex only => Grep; filename/path only => Glob. Use ast_apply for mutations.",
44
44
  promptGuidelines: [
45
- "Use ast_grep when syntax/AST structure matters; use text search for exact strings/regex. Keep paths/globs narrow and set lang for ambiguous snippets.",
45
+ "The first tool for syntax relationships or code-shape matching must be ast_grep, even when the file or language is unknown. Do not make a preliminary Glob/Grep/Read/shell call; start at the current project and narrow within ast_grep when needed.",
46
+ "Use Grep/Read directly for exact literal or regex lookups and Glob only for filename/path-only discovery; those text-only tasks must not trigger ast_grep.",
46
47
  "ast_grep is read-only: use rewrite to preview only; use ast_apply for mutations or command=scan fixes.",
47
48
  ],
48
49
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
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": {
@@ -62,6 +62,9 @@
62
62
  "test:tools-suite": "npm --prefix external/pi-tools-suite test",
63
63
  "build:acp": "npm --prefix acp run build",
64
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",
65
68
  "test:browser-qa-e2e": "npm --prefix external/pi-tools-suite run test:browser-qa-e2e",
66
69
  "sync:pi-tools-suite": "node scripts/sync-pi-tools-suite.mjs",
67
70
  "sync:pi-tools-suite:check": "node scripts/sync-pi-tools-suite.mjs --check",
@@ -80,9 +83,9 @@
80
83
  "prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
81
84
  },
82
85
  "dependencies": {
83
- "@earendil-works/pi-ai": "0.84.4",
84
- "@earendil-works/pi-coding-agent": "0.84.4",
85
- "@earendil-works/pi-tui": "0.84.4",
86
+ "@earendil-works/pi-ai": "0.85.1",
87
+ "@earendil-works/pi-coding-agent": "0.85.1",
88
+ "@earendil-works/pi-tui": "0.85.1",
86
89
  "@mariozechner/clipboard": "^0.3.9",
87
90
  "grok-mermaid": "0.2.2",
88
91
  "jsonc-parser": "3.3.1",