pi-maestro-teammate 2.7.0 → 2.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-maestro-teammate",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -222,11 +222,11 @@
222
222
  "pi-maestro-settings-core"
223
223
  ],
224
224
  "devDependencies": {
225
- "@earendil-works/pi-agent-core": "0.84.4",
225
+ "@earendil-works/pi-agent-core": "0.87.0",
226
226
  "@types/cross-spawn": "6.0.6",
227
- "@earendil-works/pi-ai": "0.84.4",
228
- "@earendil-works/pi-coding-agent": "0.84.4",
229
- "@earendil-works/pi-tui": "0.84.4",
227
+ "@earendil-works/pi-ai": "0.87.0",
228
+ "@earendil-works/pi-coding-agent": "0.87.0",
229
+ "@earendil-works/pi-tui": "0.87.0",
230
230
  "@types/ssh2": "1.15.5",
231
231
  "typebox": "1.3.7"
232
232
  },
@@ -90,6 +90,8 @@ const SESSION_ENTRY_TYPES = new Set([
90
90
  "branch_summary",
91
91
  "custom",
92
92
  "custom_message",
93
+ "context_edit",
94
+ "usage",
93
95
  "label",
94
96
  "session_info",
95
97
  ]);
@@ -595,9 +595,40 @@ function projectChain(sessionId: string, chain: readonly RawEntry[], include: Re
595
595
  } {
596
596
  const entries: SessionHistoryEntry[] = [];
597
597
  const turnSeeds: TurnSeed[] = [];
598
+ // Apply branch-local context edits so the history shows what the model
599
+ // actually received: a null replacement omits the target, otherwise the
600
+ // replacement content stands in for the entry's own content. Mirror the
601
+ // host's scope: only edits surviving the latest compaction boundary (the
602
+ // kept range plus everything after it) can affect the projection.
603
+ let editScopeStart = 0;
604
+ for (let index = chain.length - 1; index >= 0; index--) {
605
+ if (chain[index]?.type !== "compaction") continue;
606
+ const firstKept = chain[index]?.firstKeptEntryId;
607
+ const keptIndex = typeof firstKept === "string" ? chain.findIndex((entry) => entry.id === firstKept) : -1;
608
+ editScopeStart = keptIndex >= 0 && keptIndex < index ? keptIndex : index;
609
+ break;
610
+ }
611
+ const contextEdits = new Map<string, unknown>();
612
+ for (const entry of chain.slice(editScopeStart)) {
613
+ if (entry.type === "context_edit" && typeof entry.targetId === "string" && entry.replacement !== undefined) {
614
+ contextEdits.set(entry.targetId, entry.replacement);
615
+ }
616
+ }
617
+ const projectedContent = (entryIndex: number, entry: RawEntry, content: unknown): { omit: boolean; content: unknown } => {
618
+ // Edits to entries outside the surviving scope are inert for the host —
619
+ // its projection no longer contains the target — so history shows the
620
+ // original bytes the model last saw.
621
+ if (entryIndex < editScopeStart) return { omit: false, content };
622
+ const edit = contextEdits.get(entry.id);
623
+ if (edit === null) return { omit: true, content };
624
+ if (edit && typeof edit === "object" && !Array.isArray(edit) && "content" in edit) {
625
+ return { omit: false, content: (edit as Record<string, unknown>).content };
626
+ }
627
+ return { omit: false, content };
628
+ };
598
629
  let turn = 0;
599
630
  let firstUser: string | undefined;
600
- for (const entry of chain) {
631
+ for (const [entryIndex, entry] of chain.entries()) {
601
632
  const timestamp = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) || 0 : 0;
602
633
  if (entry.type === "message") {
603
634
  const message = entry.message;
@@ -605,9 +636,11 @@ function projectChain(sessionId: string, chain: readonly RawEntry[], include: Re
605
636
  const value = message as Record<string, unknown>;
606
637
  const role = value.role;
607
638
  const messageTimestamp = typeof value.timestamp === "number" ? value.timestamp : timestamp;
639
+ const projected = projectedContent(entryIndex, entry, value.content);
640
+ if (projected.omit) continue;
608
641
  if (role === "user") {
609
642
  turn += 1;
610
- const text = contentText(value.content);
643
+ const text = contentText(projected.content);
611
644
  turnSeeds.push({
612
645
  turn,
613
646
  startedAt: messageTimestamp,
@@ -616,7 +649,14 @@ function projectChain(sessionId: string, chain: readonly RawEntry[], include: Re
616
649
  if (include.has("user")) firstUser ??= text;
617
650
  pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "user", text, timestamp: messageTimestamp });
618
651
  } else if (role === "assistant") {
619
- const blocks = Array.isArray(value.content) ? value.content : [];
652
+ // The host wraps a string replacement into a single text block for
653
+ // assistant/toolResult roles; mirror that so an edited assistant
654
+ // message still renders instead of vanishing.
655
+ const blocks = Array.isArray(projected.content)
656
+ ? projected.content
657
+ : typeof projected.content === "string"
658
+ ? [{ type: "text", text: projected.content }]
659
+ : [];
620
660
  for (const block of blocks) {
621
661
  if (!block || typeof block !== "object" || Array.isArray(block)) continue;
622
662
  const candidate = block as Record<string, unknown>;
@@ -625,13 +665,15 @@ function projectChain(sessionId: string, chain: readonly RawEntry[], include: Re
625
665
  }
626
666
  }
627
667
  } else if (role === "toolResult") {
628
- pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "tool_result", text: contentText(value.content), timestamp: messageTimestamp });
668
+ pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "tool_result", text: contentText(projected.content), timestamp: messageTimestamp });
629
669
  } else if (role === "custom" && value.display === true) {
630
- pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "visible_custom", text: contentText(value.content), timestamp: messageTimestamp });
670
+ pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "visible_custom", text: contentText(projected.content), timestamp: messageTimestamp });
631
671
  }
632
672
  // bashExecution and all other message roles are deliberately excluded.
633
673
  } else if (entry.type === "custom_message" && entry.display === true) {
634
- pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "visible_custom", text: contentText(entry.content), timestamp });
674
+ const projected = projectedContent(entryIndex, entry, entry.content);
675
+ if (projected.omit) continue;
676
+ pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "visible_custom", text: contentText(projected.content), timestamp });
635
677
  } else if (entry.type === "compaction") {
636
678
  pushEntry(entries, include, { sessionId, entryId: entry.id, turn, kind: "compaction", text: typeof entry.summary === "string" ? entry.summary : "", timestamp });
637
679
  }