@ssweens/pi-handoff 1.2.0 β 1.3.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/extensions/handoff.ts +91 -12
- package/extensions/session-query.ts +3 -1
- package/package.json +1 -1
package/extensions/handoff.ts
CHANGED
|
@@ -114,8 +114,25 @@ function extractFileOpsFromMessage(message: any, fileOps: FileOps): void {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -154,7 +188,9 @@ async function generateHandoffPrompt(
|
|
|
154
188
|
loader.onAbort = () => done(null);
|
|
155
189
|
|
|
156
190
|
const run = async () => {
|
|
157
|
-
const
|
|
191
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
|
|
192
|
+
if (!auth.ok) throw new Error(auth.error);
|
|
193
|
+
const apiKey = auth.apiKey;
|
|
158
194
|
|
|
159
195
|
const userMessage: Message = {
|
|
160
196
|
role: "user",
|
|
@@ -308,6 +344,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
308
344
|
// Store prompt keyed by parent session for the session_switch handler.
|
|
309
345
|
const pendingHandoffText = new Map<string, string>();
|
|
310
346
|
|
|
347
|
+
// -- Collapsed file marker expansion state --------------------------------
|
|
348
|
+
// Stores markerβXML mappings so the input hook can expand them on submit.
|
|
349
|
+
let activeFileMarkers: FileMarkerStore = new Map();
|
|
350
|
+
|
|
311
351
|
// ββ session_switch ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
312
352
|
// Set editor text for command-path handoffs + clear context filter.
|
|
313
353
|
pi.on("session_switch", async (event, ctx) => {
|
|
@@ -341,6 +381,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
381
|
}
|
|
342
382
|
});
|
|
343
383
|
|
|
384
|
+
// ββ input: expand collapsed file markers before LLM sees the text βββββββ
|
|
385
|
+
pi.on("input", (event) => {
|
|
386
|
+
if (activeFileMarkers.size === 0) return;
|
|
387
|
+
|
|
388
|
+
// Check if any markers are present in the input text
|
|
389
|
+
let hasMarkers = false;
|
|
390
|
+
for (const marker of activeFileMarkers.keys()) {
|
|
391
|
+
if (event.text.includes(marker)) {
|
|
392
|
+
hasMarkers = true;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!hasMarkers) return;
|
|
397
|
+
|
|
398
|
+
const expanded = expandFileMarkers(event.text, activeFileMarkers);
|
|
399
|
+
// Clear after first expansion β markers are single-use (one handoff prompt)
|
|
400
|
+
activeFileMarkers = new Map();
|
|
401
|
+
return { action: "transform" as const, text: expanded, images: event.images };
|
|
402
|
+
});
|
|
403
|
+
|
|
344
404
|
// ββ agent_end: deferred session switch for tool path ββββββββββββββββββββ
|
|
345
405
|
pi.on("agent_end", (_event, ctx) => {
|
|
346
406
|
if (!pendingHandoff) return;
|
|
@@ -404,8 +464,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
404
464
|
return;
|
|
405
465
|
}
|
|
406
466
|
|
|
407
|
-
//
|
|
408
|
-
|
|
467
|
+
// Build collapsed file markers from the messages being summarized
|
|
468
|
+
const fileOps = buildFileOperations(preparation.messagesToSummarize);
|
|
469
|
+
let prompt = fileOps
|
|
470
|
+
? `${handoffResult.text}\n\n${fileOps.markers}`
|
|
471
|
+
: handoffResult.text;
|
|
409
472
|
|
|
410
473
|
// Switch session via raw sessionManager (safe β no agent loop running)
|
|
411
474
|
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
@@ -425,6 +488,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
425
488
|
return;
|
|
426
489
|
}
|
|
427
490
|
|
|
491
|
+
// Activate markers for input hook expansion, then set editor text
|
|
492
|
+
if (fileOps) activeFileMarkers = fileOps.expansions;
|
|
428
493
|
ctx.ui.setEditorText(prompt);
|
|
429
494
|
ctx.ui.notify("Handoff ready β edit if needed, press Enter to send", "info");
|
|
430
495
|
|
|
@@ -462,8 +527,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
462
527
|
return;
|
|
463
528
|
}
|
|
464
529
|
|
|
465
|
-
//
|
|
466
|
-
|
|
530
|
+
// Build collapsed file markers from tool calls
|
|
531
|
+
const fileOps = buildFileOperations(conv.messages);
|
|
532
|
+
let prompt = fileOps
|
|
533
|
+
? `${result.text}\n\n${fileOps.markers}`
|
|
534
|
+
: result.text;
|
|
467
535
|
|
|
468
536
|
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
469
537
|
|
|
@@ -473,6 +541,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
473
541
|
if (currentSessionFile) {
|
|
474
542
|
pendingHandoffText.set(currentSessionFile, prompt);
|
|
475
543
|
}
|
|
544
|
+
// Stage markers β they'll be activated in session_switch after editor text is set
|
|
545
|
+
const pendingMarkers = fileOps?.expansions;
|
|
476
546
|
|
|
477
547
|
const sessionResult = await ctx.newSession({ parentSession: currentSessionFile ?? undefined });
|
|
478
548
|
|
|
@@ -481,6 +551,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
481
551
|
ctx.ui.notify("New session cancelled.", "info");
|
|
482
552
|
return;
|
|
483
553
|
}
|
|
554
|
+
|
|
555
|
+
// Activate markers for the new session's input hook
|
|
556
|
+
if (pendingMarkers) activeFileMarkers = pendingMarkers;
|
|
484
557
|
},
|
|
485
558
|
});
|
|
486
559
|
|
|
@@ -515,11 +588,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
515
588
|
return { content: [{ type: "text" as const, text: `Handoff failed: ${result.message}` }] };
|
|
516
589
|
}
|
|
517
590
|
|
|
518
|
-
|
|
591
|
+
const fileOps = buildFileOperations(conv.messages);
|
|
592
|
+
let prompt = fileOps
|
|
593
|
+
? `${result.text}\n\n${fileOps.markers}`
|
|
594
|
+
: result.text;
|
|
519
595
|
|
|
520
596
|
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
521
597
|
prompt = wrapWithParentSession(prompt, currentSessionFile ?? null);
|
|
522
598
|
|
|
599
|
+
// Stage markers for activation after session switch
|
|
600
|
+
if (fileOps) activeFileMarkers = fileOps.expansions;
|
|
601
|
+
|
|
523
602
|
// Defer session switch to agent_end
|
|
524
603
|
pendingHandoff = {
|
|
525
604
|
prompt,
|
|
@@ -171,7 +171,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
try {
|
|
174
|
-
const
|
|
174
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
175
|
+
if (!auth.ok) return errorResult(`Auth error: ${auth.error}`);
|
|
176
|
+
const apiKey = auth.apiKey;
|
|
175
177
|
|
|
176
178
|
const userMessage: Message = {
|
|
177
179
|
role: "user",
|