@diegopetrucci/pi-extensions 0.1.68 → 0.1.69

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.
@@ -105,11 +105,33 @@ const CONTRARIAN_STATUS_ID = "contrarian";
105
105
  const CONTRARIAN_WIDGET_ID = "contrarian";
106
106
  const CONTRARIAN_CONFIG_FILE = "contrarian.json";
107
107
  const CONTRARIAN_MODEL_PREFERENCES = [
108
+ "openai/gpt-6-astra ",
109
+ "global.openai.gpt-6-astra ",
110
+ "us.openai.gpt-6-astra ",
111
+ "openai.gpt-6-astra ",
112
+ "gpt-6-astra ",
108
113
  "gpt-6-astra",
114
+ "openai/gpt-6-sol ",
115
+ "gpt-6-sol ",
116
+ "gpt-6-sol",
109
117
  "gpt-5.6-sol",
110
118
  "gpt-5.6-terra",
119
+ "openai/gpt-6-luna ",
120
+ "gpt-6-luna ",
121
+ "gpt-6-luna",
111
122
  "gpt-5.6-luna",
112
123
  "gpt-5.5",
124
+ "anthropic/claude-opus-5.5 ",
125
+ "global.anthropic.claude-opus-5-5 ",
126
+ "au.anthropic.claude-opus-5-5 ",
127
+ "eu.anthropic.claude-opus-5-5 ",
128
+ "jp.anthropic.claude-opus-5-5 ",
129
+ "us.anthropic.claude-opus-5-5 ",
130
+ "anthropic.claude-opus-5-5 ",
131
+ "claude-opus-5.5 ",
132
+ "claude-opus-5-5 ",
133
+ "claude-opus-5-5",
134
+ "claude-opus-5.5",
113
135
  "claude-opus-5",
114
136
  "claude-opus-4-8",
115
137
  "claude-opus-4.8",
@@ -119,12 +141,24 @@ const CONTRARIAN_MODEL_PREFERENCES = [
119
141
  "claude-sonnet-4-5",
120
142
  "claude-sonnet-4.5",
121
143
  "claude-sonnet-4",
144
+ "x-ai/grok-4.7 ",
145
+ "spacexai/grok-4.7 ",
146
+ "grok-4.7 ",
147
+ "grok-4.7",
148
+ "grok-4.6",
122
149
  "grok-4.5",
123
150
  "gemini-3.5-flash",
124
151
  ];
125
152
 
126
153
  const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
127
154
  "amazon-bedrock": [
155
+ "global.anthropic.claude-opus-5-5 ",
156
+ "au.anthropic.claude-opus-5-5 ",
157
+ "eu.anthropic.claude-opus-5-5 ",
158
+ "jp.anthropic.claude-opus-5-5 ",
159
+ "us.anthropic.claude-opus-5-5 ",
160
+ "anthropic.claude-opus-5-5 ",
161
+ "claude-opus-5-5",
128
162
  "claude-opus-5",
129
163
  "claude-fable-5",
130
164
  "claude-opus-4-8",
@@ -133,6 +167,10 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
133
167
  "claude-opus-4-5",
134
168
  "claude-opus-4-1",
135
169
  "claude-opus-4",
170
+ "global.openai.gpt-6-astra ",
171
+ "us.openai.gpt-6-astra ",
172
+ "openai.gpt-6-astra ",
173
+ "gpt-6-astra",
136
174
  "openai.gpt-5.6-sol",
137
175
  "openai.gpt-5.6-terra",
138
176
  "openai.gpt-5.6-luna",
@@ -141,7 +179,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
141
179
  "claude-sonnet-4-5",
142
180
  "claude-sonnet-4",
143
181
  "deepseek.v3.2",
144
- "deepseek.r1",
145
182
  "kimi-k2.5",
146
183
  "minimax-m2.5",
147
184
  "minimax-m2.1",
@@ -149,6 +186,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
149
186
  "xai.grok-4.6",
150
187
  ],
151
188
  anthropic: [
189
+ "claude-opus-5-5 ",
190
+ "claude-opus-5-5",
152
191
  "claude-opus-5",
153
192
  "claude-fable-5",
154
193
  "claude-opus-4-8",
@@ -161,8 +200,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
161
200
  ],
162
201
  "ant-ling": ["Ling-2.6-1T", "Ling-2.6-flash"],
163
202
  "azure-openai-responses": [
203
+ "gpt-6-astra ",
204
+ "gpt-6-astra",
205
+ "gpt-6-sol ",
206
+ "gpt-6-sol",
164
207
  "gpt-5.6-sol",
165
208
  "gpt-5.6-terra",
209
+ "gpt-6-luna ",
210
+ "gpt-6-luna",
166
211
  "gpt-5.6-luna",
167
212
  "gpt-5.5-pro",
168
213
  "gpt-5.5",
@@ -199,7 +244,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
199
244
  "zai-org/GLM-5.2-Fast",
200
245
  "zai-org/GLM-4.7",
201
246
  ],
202
- cerebras: ["gpt-oss-120b", "gemma-4-31b"],
247
+ cerebras: ["gpt-oss-120b", "qwen-3.8-27b"],
203
248
  "cloudflare-ai-gateway": [
204
249
  "claude-fable-5",
205
250
  "claude-opus-5",
@@ -207,6 +252,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
207
252
  "claude-opus-4.7",
208
253
  "claude-opus-4.6",
209
254
  "claude-opus-4.5",
255
+ "gpt-6-astra ",
256
+ "gpt-6-astra",
210
257
  "gpt-5.5",
211
258
  "gpt-5.4",
212
259
  "workers-ai/@cf/moonshotai/kimi-k2.7-code",
@@ -230,7 +277,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
230
277
  "@cf/qwen/qwen3-30b-a3b-fp8",
231
278
  "@cf/zai-org/glm-4.7-flash",
232
279
  ],
233
- deepseek: ["deepseek-v4-pro", "deepseek-v4-flash"],
280
+ deepseek: ["deepseek-v4-pro", "deepseek-flash"],
234
281
  fireworks: [
235
282
  "accounts/fireworks/models/kimi-k3",
236
283
  "accounts/fireworks/routers/kimi-k3-fast",
@@ -244,13 +291,20 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
244
291
  "accounts/fireworks/models/gpt-oss-120b",
245
292
  ],
246
293
  "github-copilot": [
294
+ "claude-opus-5.5 ",
295
+ "claude-opus-5.5",
247
296
  "claude-opus-5",
248
297
  "claude-fable-5",
249
298
  "claude-opus-4.8",
250
299
  "claude-opus-4.7",
300
+ "gpt-6-astra ",
251
301
  "gpt-6-astra",
302
+ "gpt-6-sol ",
303
+ "gpt-6-sol",
252
304
  "gpt-5.6-sol",
253
305
  "gpt-5.6-terra",
306
+ "gpt-6-luna ",
307
+ "gpt-6-luna",
254
308
  "gpt-5.6-luna",
255
309
  "gpt-5.5",
256
310
  "gpt-5.4",
@@ -259,6 +313,10 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
259
313
  "claude-sonnet-5",
260
314
  "claude-sonnet-4.6",
261
315
  "gpt-5-mini",
316
+ "grok-4.7 ",
317
+ "grok-4.7",
318
+ "grok-4.6",
319
+ "grok-4.5",
262
320
  "kimi-k3",
263
321
  "kimi-k2.7-code",
264
322
  ],
@@ -303,6 +361,13 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
303
361
  "Qwen/Qwen3-Coder-Next",
304
362
  ],
305
363
  "kimi-coding": ["k3", "kimi-for-coding", "kimi-for-coding-highspeed"],
364
+ meta: [
365
+ "muse-spark-1.3",
366
+ "muse-spark-1.3-contributor",
367
+ "muse-spark-1.2",
368
+ "muse-spark-1.2-contributor",
369
+ "muse-spark-1.1",
370
+ ],
306
371
  minimax: ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
307
372
  "minimax-cn": ["MiniMax-M3", "MiniMax-M2.7-highspeed", "MiniMax-M2.7"],
308
373
  mistral: [
@@ -319,9 +384,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
319
384
  "zai-glm-5-2",
320
385
  ],
321
386
  openai: [
387
+ "gpt-6-astra ",
322
388
  "gpt-6-astra",
389
+ "gpt-6-sol ",
390
+ "gpt-6-sol",
323
391
  "gpt-5.6-sol",
324
392
  "gpt-5.6-terra",
393
+ "gpt-6-luna ",
394
+ "gpt-6-luna",
325
395
  "gpt-5.6-luna",
326
396
  "gpt-5.5-pro",
327
397
  "gpt-5.5",
@@ -339,18 +409,32 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
339
409
  "gpt-5-mini",
340
410
  ],
341
411
  "openai-codex": [
412
+ "gpt-6-astra ",
342
413
  "gpt-6-astra",
414
+ "gpt-6-sol ",
415
+ "gpt-6-sol",
343
416
  "gpt-5.6-sol",
344
417
  "gpt-5.6-terra",
418
+ "gpt-6-luna ",
419
+ "gpt-6-luna",
345
420
  "gpt-5.6-luna",
346
421
  "gpt-5.5",
347
- "gpt-5.4",
348
- "gpt-5.4-mini",
349
422
  "gpt-5.3-codex-spark",
350
423
  ],
351
424
  opencode: [
425
+ "claude-opus-5-5 ",
426
+ "claude-opus-5-5",
352
427
  "claude-opus-5",
353
428
  "claude-fable-5",
429
+ "gpt-6-astra ",
430
+ "gpt-6-astra",
431
+ "gpt-6-sol ",
432
+ "gpt-6-sol",
433
+ "gpt-5.6-sol",
434
+ "gpt-5.6-terra",
435
+ "gpt-6-luna ",
436
+ "gpt-6-luna",
437
+ "gpt-5.6-luna",
354
438
  "gpt-5.5-pro",
355
439
  "gpt-5.5",
356
440
  "gpt-5.4-pro",
@@ -362,6 +446,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
362
446
  "gpt-5.3-codex",
363
447
  "gpt-5.2-codex",
364
448
  "gpt-5.1-codex",
449
+ "grok-4.6",
365
450
  "grok-4.5",
366
451
  "gemini-3.5-flash",
367
452
  "gemini-3.1-pro",
@@ -392,8 +477,13 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
392
477
  "kimi-k3",
393
478
  "kimi-k2.7-code",
394
479
  "kimi-k2.6",
480
+ "grok-4.7 ",
481
+ "grok-4.7",
482
+ "grok-4.6",
395
483
  ],
396
484
  openrouter: [
485
+ "anthropic/claude-opus-5.5 ",
486
+ "anthropic/claude-opus-5.5",
397
487
  "anthropic/claude-opus-5",
398
488
  "anthropic/claude-fable-5",
399
489
  "~anthropic/claude-fable-latest",
@@ -402,15 +492,21 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
402
492
  "anthropic/claude-opus-4.6",
403
493
  "anthropic/claude-opus-4.5",
404
494
  "anthropic/claude-opus-4.1",
495
+ "openai/gpt-6-astra ",
496
+ "openai/gpt-6-astra",
497
+ "openai/gpt-6-sol ",
498
+ "openai/gpt-6-sol",
405
499
  "openai/gpt-5.6-sol-pro",
406
500
  "openai/gpt-5.6-sol",
407
501
  "openai/gpt-5.6-terra-pro",
408
502
  "openai/gpt-5.6-terra",
503
+ "openai/gpt-6-luna ",
504
+ "openai/gpt-6-luna",
409
505
  "openai/gpt-5.6-luna-pro",
410
506
  "openai/gpt-5.6-luna",
411
507
  "openai/gpt-5.5-pro",
412
508
  "openai/gpt-5.5",
413
- "~openai/gpt-latest",
509
+ "~openai/gpt-astra-latest",
414
510
  "google/gemini-3.1-pro-preview-customtools",
415
511
  "google/gemini-3.1-pro-preview",
416
512
  "~google/gemini-pro-latest",
@@ -421,6 +517,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
421
517
  "~moonshotai/kimi-latest",
422
518
  "moonshotai/kimi-k2.6",
423
519
  "moonshotai/kimi-k2-thinking",
520
+ "x-ai/grok-4.7 ",
521
+ "x-ai/grok-4.7",
522
+ "x-ai/grok-4.6",
424
523
  "x-ai/grok-4.5",
425
524
  "~x-ai/grok-latest",
426
525
  "deepseek/deepseek-v4-pro",
@@ -462,6 +561,34 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
462
561
  "glm-5.2",
463
562
  "deepseek-v4-flash-0731",
464
563
  ],
564
+ radius: [
565
+ "gpt-6-astra ",
566
+ "gpt-6-astra",
567
+ "claude-opus-5-5 ",
568
+ "claude-opus-5-5",
569
+ "claude-opus-5",
570
+ "gpt-6-sol ",
571
+ "gpt-6-sol",
572
+ "gpt-5.6-sol",
573
+ "gpt-5.6-terra",
574
+ "gpt-6-luna ",
575
+ "gpt-6-luna",
576
+ "gpt-5.6-luna",
577
+ "claude-fable-5-1",
578
+ "claude-fable-5",
579
+ "claude-opus-4-8",
580
+ "claude-sonnet-5",
581
+ "gpt-5.5",
582
+ "gpt-5.4",
583
+ "gpt-5.3-codex",
584
+ "kimi-k3",
585
+ "deepseek-v4-pro",
586
+ "glm-5.3",
587
+ "kimi-k2.7-code",
588
+ "deepseek-v4.1-flash",
589
+ "gpt-5.4-mini",
590
+ "glm-5.3-flash",
591
+ ],
465
592
  together: [
466
593
  "deepseek-ai/DeepSeek-V4-Pro",
467
594
  "moonshotai/Kimi-K3",
@@ -479,6 +606,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
479
606
  "google/gemma-4-31B-it",
480
607
  ],
481
608
  "vercel-ai-gateway": [
609
+ "anthropic/claude-opus-5.5 ",
610
+ "anthropic/claude-opus-5.5",
482
611
  "anthropic/claude-opus-5",
483
612
  "anthropic/claude-fable-5",
484
613
  "anthropic/claude-opus-4.8",
@@ -487,8 +616,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
487
616
  "anthropic/claude-opus-4.5",
488
617
  "anthropic/claude-sonnet-5",
489
618
  "anthropic/claude-sonnet-4.6",
619
+ "openai/gpt-6-astra ",
620
+ "openai/gpt-6-astra",
621
+ "openai/gpt-6-sol ",
622
+ "openai/gpt-6-sol",
490
623
  "openai/gpt-5.6-sol",
491
624
  "openai/gpt-5.6-terra",
625
+ "openai/gpt-6-luna ",
626
+ "openai/gpt-6-luna",
492
627
  "openai/gpt-5.6-luna",
493
628
  "openai/gpt-5.5-pro",
494
629
  "openai/gpt-5.5",
@@ -513,6 +648,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
513
648
  "google/gemini-3.1-pro-preview",
514
649
  "google/gemini-3.5-flash",
515
650
  "google/gemini-3-flash",
651
+ "spacexai/grok-4.7 ",
652
+ "spacexai/grok-4.7",
653
+ "spacexai/grok-4.6",
516
654
  "xai/grok-4.5",
517
655
  "xai/grok-4.20-reasoning",
518
656
  "minimax/minimax-m3",
@@ -521,6 +659,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
521
659
  "zai/glm-5.1",
522
660
  ],
523
661
  xai: [
662
+ "grok-4.7 ",
663
+ "grok-4.7",
524
664
  "grok-4.6",
525
665
  "grok-4.5",
526
666
  "grok-4.3",
@@ -533,9 +673,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
533
673
  "nvidia/nemotron-3-ultra-550b-a55b",
534
674
  "nvidia/nemotron-3-super-120b-a12b",
535
675
  "moonshotai/kimi-k3",
536
- "deepseek-ai/deepseek-v4-pro-0813",
537
676
  "moonshotai/kimi-k2.6",
538
- "minimaxai/minimax-m3",
539
677
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
540
678
  ],
541
679
  zai: [
@@ -546,13 +684,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
546
684
  ],
547
685
  "zai-coding-cn": [
548
686
  "glm-5.3",
549
- "glm-5.2",
550
- "glm-5-turbo",
551
- "glm-5",
552
- "glm-4.7",
687
+ "glm-5.3-highspeed",
688
+ "glm-5.3-flash",
689
+ "glm-4.6v",
553
690
  ],
554
- moonshotai: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
555
- "moonshotai-cn": ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2-thinking-turbo", "kimi-k2-thinking", "kimi-k2.5"],
691
+ moonshotai: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
692
+ "moonshotai-cn": ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
556
693
  };
557
694
 
558
695
  const CONTRARIAN_SYSTEM_PROMPT = [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-contrarian",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "A read-only contrarian subagent extension for pi that stress-tests plans, designs, assumptions, and conclusions.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -154,7 +154,7 @@ Gate modes (`/context-pruning gate <mode>`):
154
154
  ### `/prune`
155
155
 
156
156
  Interactive picker (TUI/RPC) over every prunable tool result in the current
157
- branch:
157
+ model context:
158
158
 
159
159
  ```text
160
160
  /prune
@@ -75,6 +75,54 @@ export interface MinimalSessionEntry {
75
75
  [key: string]: unknown;
76
76
  }
77
77
 
78
+ export interface MinimalProjectedSessionEntry {
79
+ sourceEntry: MinimalSessionEntry;
80
+ messages: MinimalMessage[];
81
+ }
82
+
83
+ export interface MinimalSessionProjection {
84
+ entries: MinimalProjectedSessionEntry[];
85
+ messages?: MinimalMessage[];
86
+ }
87
+
88
+ function isRecord(value: unknown): value is Record<string, unknown> {
89
+ return typeof value === "object" && value !== null;
90
+ }
91
+
92
+ function isMinimalMessage(value: unknown): value is MinimalMessage {
93
+ return isRecord(value) && typeof value.role === "string" && value.role.length > 0;
94
+ }
95
+
96
+ function isProjectionSourceEntry(value: unknown): value is MinimalSessionEntry {
97
+ return isRecord(value)
98
+ && typeof value.type === "string"
99
+ && value.type.length > 0
100
+ && typeof value.id === "string"
101
+ && value.id.length > 0;
102
+ }
103
+
104
+ export function normalizeSessionProjection(value: unknown): MinimalSessionProjection | undefined {
105
+ if (!isRecord(value) || !Array.isArray(value.entries)) return undefined;
106
+
107
+ const entries: MinimalProjectedSessionEntry[] = [];
108
+ for (const candidate of value.entries) {
109
+ if (!isRecord(candidate) || !isProjectionSourceEntry(candidate.sourceEntry) || !Array.isArray(candidate.messages)) return undefined;
110
+ if (!candidate.messages.every(isMinimalMessage)) return undefined;
111
+ entries.push({
112
+ sourceEntry: candidate.sourceEntry,
113
+ messages: candidate.messages,
114
+ });
115
+ }
116
+
117
+ if ("messages" in value && (!Array.isArray(value.messages) || !value.messages.every(isMinimalMessage))) return undefined;
118
+ const messages = Array.isArray(value.messages) ? value.messages : entries.flatMap((entry) => entry.messages);
119
+ return { entries, messages };
120
+ }
121
+
122
+ export function sessionProjectionToMessages(projection: MinimalSessionProjection): MinimalMessage[] {
123
+ return projection.messages ?? projection.entries.flatMap((entry) => entry.messages);
124
+ }
125
+
78
126
  // ============================================================================
79
127
  // Constants
80
128
  // ============================================================================
@@ -699,13 +747,7 @@ export function findToolCallPairIndices(
699
747
  return pair;
700
748
  }
701
749
 
702
- /**
703
- * Fallback correlation for decisions that reference a session entry rather
704
- * than a toolCallId (reserved for future non-tool-call strategies). Zips
705
- * message-producing session entries against the current context projection
706
- * by position; entry/message counts are expected to match in the common
707
- * case, but this degrades gracefully otherwise.
708
- */
750
+ /** Map raw session-entry provenance to projected message positions when Pi exposes it. */
709
751
  function expectedRoleForEntry(entry: MinimalSessionEntry): string | undefined {
710
752
  if (entry.type === "message") return entry.message?.role;
711
753
  if (entry.type === "custom_message") return "custom";
@@ -714,15 +756,53 @@ function expectedRoleForEntry(entry: MinimalSessionEntry): string | undefined {
714
756
  return undefined;
715
757
  }
716
758
 
717
- export function buildEntryIdToMessageIndexMap(
759
+ function buildProjectedEntryIdToMessageIndexMap(
718
760
  entries: MinimalSessionEntry[],
719
761
  messages: MinimalMessage[],
762
+ projection: MinimalSessionProjection,
720
763
  ): Map<string, number> {
721
764
  const map = new Map<string, number>();
765
+ const entryIds = new Set(entries.map((entry) => entry.id));
766
+ let cursor = 0;
767
+
768
+ for (const projectedEntry of projection.entries) {
769
+ let firstMessageIndex: number | undefined;
770
+ for (const projectedMessage of projectedEntry.messages) {
771
+ let messageIndex = messages.indexOf(projectedMessage, cursor);
772
+ if (messageIndex < cursor) messageIndex = -1;
773
+
774
+ if (messageIndex < 0 && typeof projectedMessage.timestamp === "number") {
775
+ messageIndex = messages.findIndex(
776
+ (candidate, index) =>
777
+ index >= cursor && candidate.role === projectedMessage.role && candidate.timestamp === projectedMessage.timestamp,
778
+ );
779
+ }
780
+ if (messageIndex < 0) continue;
781
+ firstMessageIndex ??= messageIndex;
782
+ cursor = messageIndex + 1;
783
+ }
784
+
785
+ if (firstMessageIndex !== undefined && entryIds.has(projectedEntry.sourceEntry.id)) {
786
+ map.set(projectedEntry.sourceEntry.id, firstMessageIndex);
787
+ }
788
+ }
789
+ return map;
790
+ }
791
+
792
+ export function buildEntryIdToMessageIndexMap(
793
+ entries: MinimalSessionEntry[],
794
+ messages: MinimalMessage[],
795
+ projection?: MinimalSessionProjection,
796
+ ): Map<string, number> {
797
+ if (projection) return buildProjectedEntryIdToMessageIndexMap(entries, messages, projection);
798
+
799
+ // Older runtimes expose only raw branch entries; preserve their positional
800
+ // correlation and use role/timestamp only when the counts diverge.
722
801
  const producing = entries.filter((entry) =>
723
802
  entry.type === "message" || entry.type === "custom_message" || entry.type === "branch_summary" || entry.type === "compaction",
724
803
  );
725
804
  const length = Math.min(producing.length, messages.length);
805
+ const map = new Map<string, number>();
726
806
  for (let i = 0; i < length; i++) {
727
807
  const entry = producing[i];
728
808
  const expectedRole = expectedRoleForEntry(entry);
@@ -747,15 +827,21 @@ export function findMessageIndexByRoleAndTimestamp(
747
827
  return index >= 0 ? index : undefined;
748
828
  }
749
829
 
750
- /** Resolve a session entry id to a message index using order-zip, then role+timestamp fallback. */
830
+ /** Resolve a session entry id using projection provenance, then legacy role+timestamp fallback. */
751
831
  export function resolveMessageIndexForEntry(
752
832
  entryId: string,
753
833
  entries: MinimalSessionEntry[],
754
834
  messages: MinimalMessage[],
835
+ projection?: MinimalSessionProjection,
755
836
  ): number | undefined {
756
- const zipMap = buildEntryIdToMessageIndexMap(entries, messages);
837
+ const zipMap = buildEntryIdToMessageIndexMap(entries, messages, projection);
757
838
  const zipped = zipMap.get(entryId);
758
839
  if (zipped !== undefined) return zipped;
840
+
841
+ // An empty projected contribution is an explicit omission/replacement state;
842
+ // only a validated projection may make that context-edit-aware decision.
843
+ if (projection?.entries.some((entry) => entry.sourceEntry.id === entryId && entry.messages.length === 0)) return undefined;
844
+
759
845
  const entry = entries.find((candidate) => candidate.id === entryId);
760
846
  if (!entry) return undefined;
761
847
  return findMessageIndexByRoleAndTimestamp(entry, messages);
@@ -2172,8 +2258,22 @@ export function rebuildDecisionStateFromEntries(entries: MinimalSessionEntry[]):
2172
2258
  // /prune picker helpers (pe-8re9)
2173
2259
  // ============================================================================
2174
2260
 
2175
- /** Convert branch entries into the plain message array the pipeline/pickers correlate against. */
2176
- export function sessionEntriesToMessages(entries: MinimalSessionEntry[]): MinimalMessage[] {
2261
+ function resolveSessionProjection(ctx: ExtensionContext): MinimalSessionProjection | undefined {
2262
+ try {
2263
+ const sessionManager = ctx.sessionManager as unknown as { buildSessionProjection?: () => unknown };
2264
+ const buildProjection = sessionManager.buildSessionProjection;
2265
+ if (typeof buildProjection !== "function") return undefined;
2266
+ return normalizeSessionProjection(buildProjection.call(sessionManager));
2267
+ } catch {
2268
+ return undefined;
2269
+ }
2270
+ }
2271
+
2272
+ export function sessionEntriesToMessages(
2273
+ entries: MinimalSessionEntry[],
2274
+ projection?: MinimalSessionProjection,
2275
+ ): MinimalMessage[] {
2276
+ if (projection) return sessionProjectionToMessages(projection);
2177
2277
  const messages: MinimalMessage[] = [];
2178
2278
  for (const entry of entries) {
2179
2279
  if (entry.type === "message" && entry.message) messages.push(entry.message);
@@ -2288,7 +2388,7 @@ export function formatPrunableItemDetail(item: PrunableItem, cost?: CacheCostMod
2288
2388
 
2289
2389
  /** Plain-text report used both by the non-UI /prune fallback and as a debugging aid. */
2290
2390
  export function formatPrunableItemsReport(items: PrunableItem[]): string[] {
2291
- if (items.length === 0) return ["No prunable tool results found in this session."];
2391
+ if (items.length === 0) return ["No prunable tool results found in the current model context."];
2292
2392
  return items.map((item, index) => `${index + 1}. ${formatPrunableItemOption(item)}`);
2293
2393
  }
2294
2394
 
@@ -2596,7 +2696,8 @@ export default function dynamicContextPruningExtension(pi: ExtensionAPI) {
2596
2696
  description: "Review, manually prune, and restore prunable tool results (interactive picker in TUI/RPC)",
2597
2697
  handler: async (_args, ctx) => {
2598
2698
  const branchEntries = ctx.sessionManager.getBranch() as unknown as MinimalSessionEntry[];
2599
- const messages = sessionEntriesToMessages(branchEntries);
2699
+ const projection = resolveSessionProjection(ctx);
2700
+ const messages = sessionEntriesToMessages(branchEntries, projection);
2600
2701
  const activeByToolCallId = buildActiveResultDecisionMap(persistedDecisions);
2601
2702
  const restoredToolCallIds = new Set(
2602
2703
  Array.from(lastDecisionByKey.values())
@@ -2606,19 +2707,20 @@ export default function dynamicContextPruningExtension(pi: ExtensionAPI) {
2606
2707
  const items = buildPrunableItems(messages, activeByToolCallId, restoredToolCallIds);
2607
2708
 
2608
2709
  if (!ctx.hasUI) {
2609
- notify(ctx, ["/prune: no interactive UI available. Prunable tool results in this session:", "", ...formatPrunableItemsReport(items)].join("\n"), "info");
2710
+ notify(ctx, ["/prune: no interactive UI available. Prunable tool results in the current model context:", "", ...formatPrunableItemsReport(items)].join("\n"), "info");
2610
2711
  return;
2611
2712
  }
2612
2713
 
2613
2714
  if (items.length === 0) {
2614
- notify(ctx, "No prunable tool results found in this session.", "info");
2715
+ notify(ctx, "No prunable tool results found in the current model context.", "info");
2615
2716
  return;
2616
2717
  }
2617
2718
 
2618
2719
  const doneLabel = "Done";
2619
2720
  while (true) {
2620
2721
  const latestBranch = ctx.sessionManager.getBranch() as unknown as MinimalSessionEntry[];
2621
- const latestMessages = sessionEntriesToMessages(latestBranch);
2722
+ const latestProjection = resolveSessionProjection(ctx);
2723
+ const latestMessages = sessionEntriesToMessages(latestBranch, latestProjection);
2622
2724
  const latestActive = buildActiveResultDecisionMap(persistedDecisions);
2623
2725
  const latestRestoredToolCallIds = new Set(
2624
2726
  Array.from(lastDecisionByKey.values())
@@ -2627,7 +2729,7 @@ export default function dynamicContextPruningExtension(pi: ExtensionAPI) {
2627
2729
  );
2628
2730
  const latestItems = buildPrunableItems(latestMessages, latestActive, latestRestoredToolCallIds);
2629
2731
  if (latestItems.length === 0) {
2630
- notify(ctx, "No prunable tool results found in this session.", "info");
2732
+ notify(ctx, "No prunable tool results found in the current model context.", "info");
2631
2733
  return;
2632
2734
  }
2633
2735
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dynamic-context-pruning",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "A pi extension that dynamically prunes stale context to keep sessions lean.",
6
6
  "keywords": [