@danypops/papyrus 0.13.5 → 0.13.6

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.
@@ -172,9 +172,24 @@ export interface MessageHistoryTree {
172
172
  * estimate the conversation's context contribution AND surface branches explored via /tree
173
173
  * that are no longer on the active path -- content that cost real tokens to generate but is
174
174
  * NOT currently part of the context window. Bounded and cycle-safe (CONTEXT_TREE_MAX_NODES):
175
- * a session file is external, mutable state, and this deliberately
176
- * hardens past a confirmed real gap in Pi's own getBranch() (no cycle guard at all) rather
177
- * than assuming the tree can never be malformed.
175
+ * a session file is external, mutable state, and this deliberately hardens past a confirmed
176
+ * real gap in Pi's own getBranch() (no cycle guard at all) rather than assuming the tree can
177
+ * never be malformed.
178
+ *
179
+ * `activeEntryIds` MUST come from ctx.sessionManager.buildContextEntries(), not getBranch().
180
+ * getBranch()'s own docstring says it "[i]ncludes all entry types... Use buildSessionContext()
181
+ * to get the resolved messages for the LLM" -- it does not skip entries a real compaction has
182
+ * already summarized away. A real session with 3 compactions confirmed using getBranch() here
183
+ * overcounts activeTokens by over 13x, since every pre-compaction message still reads as
184
+ * "active". buildContextEntries() is Pi's own compaction-aware entry list: the latest
185
+ * compaction entry, its kept entries from firstKeptEntryId onward, and everything after.
186
+ *
187
+ * `branchEntryIds` (optional) is the full raw current-path id set (getBranch()'s own output).
188
+ * When given, an entry on the branch path but excluded from activeEntryIds is labeled
189
+ * "(compacted)" rather than the less accurate "(inactive branch)", which is reserved for
190
+ * entries not on the current path at all (a genuinely abandoned /tree branch). Omitting it
191
+ * preserves the simpler binary active/inactive-branch labeling for callers that only have one
192
+ * set to give (e.g. tests).
178
193
  */
179
194
  interface WalkFrame {
180
195
  node: SessionTreeNodeLike;
@@ -190,7 +205,7 @@ interface WalkFrame {
190
205
  * JavaScript call-stack overflow at that scale, independent of the CONTEXT_TREE_MAX_NODES
191
206
  * bound entirely.
192
207
  */
193
- export function buildMessageHistoryTree(roots: ReadonlyArray<SessionTreeNodeLike>, activeEntryIds: ReadonlySet<string>): MessageHistoryTree {
208
+ export function buildMessageHistoryTree(roots: ReadonlyArray<SessionTreeNodeLike>, activeEntryIds: ReadonlySet<string>, branchEntryIds?: ReadonlySet<string>): MessageHistoryTree {
194
209
  const visited = new Set<string>();
195
210
  let truncated = false;
196
211
  let activeTokens = 0;
@@ -222,12 +237,13 @@ export function buildMessageHistoryTree(roots: ReadonlyArray<SessionTreeNodeLike
222
237
  const tokens = Math.ceil(characters / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN);
223
238
  const isActive = activeEntryIds.has(entry.id);
224
239
  if (isActive) activeTokens += tokens;
240
+ const isOnBranch = branchEntryIds ? branchEntryIds.has(entry.id) : isActive; // no branch set given -- fall back to the old binary active/inactive-branch label
225
241
 
226
242
  const children = childItemsByParent.get(index) ?? [];
227
243
  if (tokens === 0 && children.length === 0) continue; // no content, no descendants with content -- nothing to show
228
244
 
229
245
  const item: ContextSegmentItem = {
230
- label: isActive ? entryLabel(entry) : `${entryLabel(entry)} (inactive branch)`,
246
+ label: isActive ? entryLabel(entry) : isOnBranch ? `${entryLabel(entry)} (compacted)` : `${entryLabel(entry)} (inactive branch)`,
231
247
  estimatedTokens: tokens,
232
248
  ...(children.length > 0 ? { children } : {}),
233
249
  };
@@ -431,8 +431,17 @@ export default async function (pi: ExtensionAPI) {
431
431
  // Real tree (not just the linear current-branch path): surfaces content sitting in an
432
432
  // abandoned /tree branch, which cost real tokens to generate but isn't in context now.
433
433
  const tree = ctx.sessionManager.getTree() as unknown as SessionTreeNodeLike[];
434
- const activeEntryIds = new Set((ctx.sessionManager.getBranch() as unknown as SessionEntryLike[]).map((entry) => entry.id));
435
- const messageHistory = buildMessageHistoryTree(tree, activeEntryIds);
434
+ // buildContextEntries(), NOT getBranch(): getBranch() returns every raw entry on the
435
+ // current path including everything a real compaction has already summarized away.
436
+ // A session with 3 real compactions confirmed this made "active" message-history
437
+ // tokens overcount the real total by over 13x -- getBranch()'s own docstring already
438
+ // says as much ("Use buildSessionContext() to get the resolved messages for the
439
+ // LLM"); buildContextEntries() is the compaction-aware entry list matching what the
440
+ // LLM actually sees (the latest compaction entry itself, plus kept entries from its
441
+ // firstKeptEntryId onward, plus everything after -- older summarized entries omitted).
442
+ const activeEntryIds = new Set((ctx.sessionManager.buildContextEntries() as unknown as SessionEntryLike[]).map((entry) => entry.id));
443
+ const branchEntryIds = new Set((ctx.sessionManager.getBranch() as unknown as SessionEntryLike[]).map((entry) => entry.id));
444
+ const messageHistory = buildMessageHistoryTree(tree, activeEntryIds, branchEntryIds);
436
445
  const breakdown = buildContextBreakdown({
437
446
  totalTokens: usage?.tokens ?? null,
438
447
  contextWindow: ctx.model?.contextWindow ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.13.5",
3
+ "version": "0.13.6",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],