@ssweens/pi-handoff 1.2.0 β†’ 1.3.0

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.
@@ -114,8 +114,25 @@ function extractFileOpsFromMessage(message: any, fileOps: FileOps): void {
114
114
  }
115
115
  }
116
116
 
117
- /** Compute read-only and modified file lists, append to summary as XML tags. */
118
- function appendFileOperations(summary: string, messages: any[]): string {
117
+ // ---------------------------------------------------------------------------
118
+ // Collapsed file markers
119
+ // ---------------------------------------------------------------------------
120
+ // File lists are shown as compact markers in the editor (e.g. "[πŸ“‚ 12 read files]")
121
+ // and expanded to full XML tags when the user submits via the input event hook.
122
+
123
+ /** Pending file lists keyed by marker text β†’ expanded XML content. */
124
+ type FileMarkerStore = Map<string, string>;
125
+
126
+ function createReadMarker(count: number): string {
127
+ return `[+${count} read filename${count === 1 ? "" : "s"}]`;
128
+ }
129
+
130
+ function createModifiedMarker(count: number): string {
131
+ return `[+${count} modified filename${count === 1 ? "" : "s"}]`;
132
+ }
133
+
134
+ /** Build collapsed markers + expansion map from tool-call messages. */
135
+ function buildFileOperations(messages: any[]): { markers: string; expansions: FileMarkerStore } | null {
119
136
  const fileOps = createFileOps();
120
137
  for (const msg of messages) extractFileOpsFromMessage(msg, fileOps);
121
138
 
@@ -123,15 +140,32 @@ function appendFileOperations(summary: string, messages: any[]): string {
123
140
  const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).sort();
124
141
  const modifiedFiles = [...modified].sort();
125
142
 
126
- const sections: string[] = [];
143
+ if (readFiles.length === 0 && modifiedFiles.length === 0) return null;
144
+
145
+ const expansions: FileMarkerStore = new Map();
146
+ const markerLines: string[] = [];
147
+
127
148
  if (readFiles.length > 0) {
128
- sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
149
+ const marker = createReadMarker(readFiles.length);
150
+ expansions.set(marker, `<read-files>\n${readFiles.join("\n")}\n</read-files>`);
151
+ markerLines.push(marker);
129
152
  }
130
153
  if (modifiedFiles.length > 0) {
131
- sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
154
+ const marker = createModifiedMarker(modifiedFiles.length);
155
+ expansions.set(marker, `<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
156
+ markerLines.push(marker);
132
157
  }
133
158
 
134
- return sections.length > 0 ? `${summary}\n\n${sections.join("\n\n")}` : summary;
159
+ return { markers: markerLines.join("\n"), expansions };
160
+ }
161
+
162
+ /** Expand all file markers in text using the stored expansions. */
163
+ function expandFileMarkers(text: string, store: FileMarkerStore): string {
164
+ let result = text;
165
+ for (const [marker, expanded] of store) {
166
+ result = result.replaceAll(marker, expanded);
167
+ }
168
+ return result;
135
169
  }
136
170
 
137
171
  // ---------------------------------------------------------------------------
@@ -308,6 +342,10 @@ export default function (pi: ExtensionAPI) {
308
342
  // Store prompt keyed by parent session for the session_switch handler.
309
343
  const pendingHandoffText = new Map<string, string>();
310
344
 
345
+ // -- Collapsed file marker expansion state --------------------------------
346
+ // Stores marker→XML mappings so the input hook can expand them on submit.
347
+ let activeFileMarkers: FileMarkerStore = new Map();
348
+
311
349
  // ── session_switch ──────────────────────────────────────────────────────
312
350
  // Set editor text for command-path handoffs + clear context filter.
313
351
  pi.on("session_switch", async (event, ctx) => {
@@ -341,6 +379,26 @@ export default function (pi: ExtensionAPI) {
341
379
  }
342
380
  });
343
381
 
382
+ // ── input: expand collapsed file markers before LLM sees the text ───────
383
+ pi.on("input", (event) => {
384
+ if (activeFileMarkers.size === 0) return;
385
+
386
+ // Check if any markers are present in the input text
387
+ let hasMarkers = false;
388
+ for (const marker of activeFileMarkers.keys()) {
389
+ if (event.text.includes(marker)) {
390
+ hasMarkers = true;
391
+ break;
392
+ }
393
+ }
394
+ if (!hasMarkers) return;
395
+
396
+ const expanded = expandFileMarkers(event.text, activeFileMarkers);
397
+ // Clear after first expansion β€” markers are single-use (one handoff prompt)
398
+ activeFileMarkers = new Map();
399
+ return { action: "transform" as const, text: expanded, images: event.images };
400
+ });
401
+
344
402
  // ── agent_end: deferred session switch for tool path ────────────────────
345
403
  pi.on("agent_end", (_event, ctx) => {
346
404
  if (!pendingHandoff) return;
@@ -404,8 +462,11 @@ export default function (pi: ExtensionAPI) {
404
462
  return;
405
463
  }
406
464
 
407
- // Append programmatic file tracking from the messages being summarized
408
- let prompt = appendFileOperations(handoffResult.text, preparation.messagesToSummarize);
465
+ // Build collapsed file markers from the messages being summarized
466
+ const fileOps = buildFileOperations(preparation.messagesToSummarize);
467
+ let prompt = fileOps
468
+ ? `${handoffResult.text}\n\n${fileOps.markers}`
469
+ : handoffResult.text;
409
470
 
410
471
  // Switch session via raw sessionManager (safe β€” no agent loop running)
411
472
  const currentSessionFile = ctx.sessionManager.getSessionFile();
@@ -425,6 +486,8 @@ export default function (pi: ExtensionAPI) {
425
486
  return;
426
487
  }
427
488
 
489
+ // Activate markers for input hook expansion, then set editor text
490
+ if (fileOps) activeFileMarkers = fileOps.expansions;
428
491
  ctx.ui.setEditorText(prompt);
429
492
  ctx.ui.notify("Handoff ready β€” edit if needed, press Enter to send", "info");
430
493
 
@@ -462,8 +525,11 @@ export default function (pi: ExtensionAPI) {
462
525
  return;
463
526
  }
464
527
 
465
- // Append programmatic file tracking (read/modified from tool calls)
466
- let prompt = appendFileOperations(result.text, conv.messages);
528
+ // Build collapsed file markers from tool calls
529
+ const fileOps = buildFileOperations(conv.messages);
530
+ let prompt = fileOps
531
+ ? `${result.text}\n\n${fileOps.markers}`
532
+ : result.text;
467
533
 
468
534
  const currentSessionFile = ctx.sessionManager.getSessionFile();
469
535
 
@@ -473,6 +539,8 @@ export default function (pi: ExtensionAPI) {
473
539
  if (currentSessionFile) {
474
540
  pendingHandoffText.set(currentSessionFile, prompt);
475
541
  }
542
+ // Stage markers β€” they'll be activated in session_switch after editor text is set
543
+ const pendingMarkers = fileOps?.expansions;
476
544
 
477
545
  const sessionResult = await ctx.newSession({ parentSession: currentSessionFile ?? undefined });
478
546
 
@@ -481,6 +549,9 @@ export default function (pi: ExtensionAPI) {
481
549
  ctx.ui.notify("New session cancelled.", "info");
482
550
  return;
483
551
  }
552
+
553
+ // Activate markers for the new session's input hook
554
+ if (pendingMarkers) activeFileMarkers = pendingMarkers;
484
555
  },
485
556
  });
486
557
 
@@ -515,11 +586,17 @@ export default function (pi: ExtensionAPI) {
515
586
  return { content: [{ type: "text" as const, text: `Handoff failed: ${result.message}` }] };
516
587
  }
517
588
 
518
- let prompt = appendFileOperations(result.text, conv.messages);
589
+ const fileOps = buildFileOperations(conv.messages);
590
+ let prompt = fileOps
591
+ ? `${result.text}\n\n${fileOps.markers}`
592
+ : result.text;
519
593
 
520
594
  const currentSessionFile = ctx.sessionManager.getSessionFile();
521
595
  prompt = wrapWithParentSession(prompt, currentSessionFile ?? null);
522
596
 
597
+ // Stage markers for activation after session switch
598
+ if (fileOps) activeFileMarkers = fileOps.expansions;
599
+
523
600
  // Defer session switch to agent_end
524
601
  pendingHandoff = {
525
602
  prompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssweens/pi-handoff",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "scripts": {
5
5
  "test": "bun test tests/",
6
6
  "test:watch": "bun test --watch tests/"