@herbertgao/pi-extensions 2026.8.8 → 2026.8.9

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.
Files changed (74) hide show
  1. package/README.md +3 -3
  2. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +24 -0
  3. package/node_modules/@czottmann/pi-automode/README.md +78 -112
  4. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +18 -14
  5. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +44 -0
  6. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +5 -0
  7. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +198 -104
  8. package/node_modules/@czottmann/pi-automode/docs/configuration.md +155 -0
  9. package/node_modules/@czottmann/pi-automode/docs/defaults.md +55 -14
  10. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +90 -0
  11. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +61 -26
  12. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +5 -0
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +172 -18
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +127 -30
  15. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +3 -0
  16. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +273 -31
  17. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +60 -5
  18. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +92 -8
  19. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +203 -13
  20. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +1 -0
  21. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +11 -0
  22. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +9 -1
  23. package/node_modules/@czottmann/pi-automode/package.json +9 -2
  24. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +63 -0
  25. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +3 -3
  26. package/node_modules/@herbertgao/pi-cc-extensions/README.md +3 -3
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +8 -3
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +56 -29
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +15 -13
  31. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  32. package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +29 -0
  33. package/node_modules/@herbertgao/pi-handoff/README.md +15 -18
  34. package/node_modules/@herbertgao/pi-handoff/package.json +11 -4
  35. package/node_modules/@herbertgao/pi-handoff/src/complete-text.ts +33 -0
  36. package/node_modules/@herbertgao/pi-handoff/src/index.ts +127 -232
  37. package/node_modules/@herbertgao/pi-handoff/src/session-query.ts +15 -30
  38. package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +14 -0
  39. package/node_modules/@herbertgao/pi-mermaid-open/README.md +21 -4
  40. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +11 -0
  41. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/viewer.mjs +305 -0
  42. package/node_modules/@herbertgao/pi-mermaid-open/package.json +15 -4
  43. package/node_modules/@herbertgao/pi-mermaid-open/src/index.ts +351 -101
  44. package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +12 -0
  45. package/node_modules/@herbertgao/pi-preferred-thinking/README.md +30 -14
  46. package/node_modules/@herbertgao/pi-preferred-thinking/package.json +7 -4
  47. package/node_modules/@herbertgao/pi-preferred-thinking/src/index.ts +140 -85
  48. package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +12 -0
  49. package/node_modules/@herbertgao/pi-recap/README.md +5 -3
  50. package/node_modules/@herbertgao/pi-recap/package.json +9 -3
  51. package/node_modules/@herbertgao/pi-recap/src/index.ts +3 -7
  52. package/node_modules/@herbertgao/pi-recap/src/model-picker.ts +6 -19
  53. package/node_modules/@herbertgao/pi-recap/src/models.ts +9 -21
  54. package/node_modules/@herbertgao/pi-recap/src/tui.ts +1 -1
  55. package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +49 -0
  56. package/node_modules/@herbertgao/pi-rename/README.md +13 -11
  57. package/node_modules/@herbertgao/pi-rename/package.json +13 -3
  58. package/node_modules/@herbertgao/pi-rename/src/index.ts +135 -59
  59. package/node_modules/@herbertgao/pi-rename/src/models.ts +5 -13
  60. package/node_modules/@herbertgao/pi-rename/src/naming.ts +2 -6
  61. package/node_modules/@herbertgao/pi-stash/package.json +3 -3
  62. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  63. package/node_modules/@herbertgao/pi-subagents/README.md +16 -3
  64. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  65. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +15 -4
  66. package/node_modules/@herbertgao/pi-subagents/src/index.ts +16 -0
  67. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +5 -1
  68. package/node_modules/pi-web-access/CHANGELOG.md +13 -0
  69. package/node_modules/pi-web-access/README.md +2 -2
  70. package/node_modules/pi-web-access/chrome-cookies.ts +12 -7
  71. package/node_modules/pi-web-access/gemini-search.ts +28 -10
  72. package/node_modules/pi-web-access/index.ts +24 -10
  73. package/node_modules/pi-web-access/package.json +1 -1
  74. package/package.json +13 -12
@@ -9,7 +9,7 @@ import { Key, Markdown, matchesKey, truncateToWidth, visibleWidth } from "@earen
9
9
  export type ContextPart = {
10
10
  label: string;
11
11
  tokens: number;
12
- color: "accent" | "success" | "warning" | "muted" | "dim";
12
+ color: "accent" | "success" | "warning" | "muted" | "dim" | "error";
13
13
  };
14
14
 
15
15
  type PreviewKey = "systemPrompt" | "tools" | "toolResults" | "contextFiles" | "skills";
@@ -43,11 +43,16 @@ export function escCloseHitbox(bounds: DialogBounds): {
43
43
  };
44
44
  }
45
45
 
46
- let activeTextPreviews = 0;
46
+ let activeContextOverlays = 0;
47
47
 
48
- /** fullscreen 输入包装用于把鼠标事件继续传给当前文本预览 overlay。 */
48
+ /** fullscreen 输入包装用于把鼠标事件继续传给当前 context overlay。 */
49
49
  export function hasActiveTextPreview(): boolean {
50
- return activeTextPreviews > 0;
50
+ return activeContextOverlays > 0;
51
+ }
52
+
53
+ /** fullscreen overlays fall back to 1002; restore motion events for hover states. */
54
+ function ensureFullscreenMouseMotion(tui: any): void {
55
+ if (tui.mode === "fullscreen") tui.terminal?.write?.("\x1b[?1003h\x1b[?1006h");
51
56
  }
52
57
 
53
58
  export async function showTextPreview(
@@ -56,10 +61,11 @@ export async function showTextPreview(
56
61
  rawContent: string,
57
62
  ): Promise<void> {
58
63
  const content = normalizePreviewText(rawContent);
59
- activeTextPreviews++;
64
+ activeContextOverlays++;
60
65
  try {
61
66
  await ctx.ui.custom(
62
67
  (tui, theme, _keybindings, done) => {
68
+ ensureFullscreenMouseMotion(tui);
63
69
  let scrollOffset = 0;
64
70
  let pageSize = 1;
65
71
  let totalLines = 1;
@@ -249,7 +255,7 @@ export async function showTextPreview(
249
255
  },
250
256
  );
251
257
  } finally {
252
- activeTextPreviews--;
258
+ activeContextOverlays--;
253
259
  }
254
260
  }
255
261
 
@@ -424,7 +430,7 @@ export function collectContextBreakdown(ctx: ExtensionCommandContext): ContextBr
424
430
  const parts: ContextPart[] = [
425
431
  { label: "System prompt", tokens: baseSystem, color: "accent" },
426
432
  { label: "Tools", tokens: toolTokens, color: "success" },
427
- { label: "Context files", tokens: contextFileTokens, color: "warning" },
433
+ { label: "Memory", tokens: contextFileTokens, color: "error" },
428
434
  { label: "Skills", tokens: skillTokens, color: "warning" },
429
435
  { label: "User messages", tokens: user, color: "muted" },
430
436
  { label: "Assistant messages", tokens: assistant, color: "accent" },
@@ -448,10 +454,12 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
448
454
  const breakdown = collectContextBreakdown(ctx);
449
455
  const estimated = breakdown.parts.reduce((sum, part) => sum + part.tokens, 0);
450
456
  const fixedParts = breakdown.parts.filter(
451
- (part) => part.label === "System prompt" || part.label === "Tools",
457
+ (part) =>
458
+ part.label === "System prompt" || part.label === "Memory" || part.label === "Tools",
452
459
  );
453
460
  const variableParts = breakdown.parts.filter(
454
- (part) => part.label !== "System prompt" && part.label !== "Tools",
461
+ (part) =>
462
+ part.label !== "System prompt" && part.label !== "Memory" && part.label !== "Tools",
455
463
  );
456
464
  const orderedParts = [...fixedParts, ...variableParts];
457
465
  const fixedTokens = fixedParts.reduce((sum, part) => sum + part.tokens, 0);
@@ -527,9 +535,9 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
527
535
  },
528
536
  {
529
537
  key: "contextFiles",
530
- label: "Context files",
531
- title: "Context Files",
532
- content: contextFilesContent || "No context files loaded.",
538
+ label: "Memory",
539
+ title: "Memory Files",
540
+ content: contextFilesContent || "No memory files loaded.",
533
541
  },
534
542
  {
535
543
  key: "skills",
@@ -549,8 +557,10 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
549
557
  let selectedPreviewIndex = 0;
550
558
 
551
559
  while (true) {
552
- const action = await ctx.ui.custom(
560
+ activeContextOverlays++;
561
+ const actionPromise = ctx.ui.custom(
553
562
  (tui, theme, _keybindings, done) => {
563
+ ensureFullscreenMouseMotion(tui);
554
564
  let previewHitboxes: Array<{
555
565
  key: PreviewKey;
556
566
  row: number;
@@ -558,6 +568,8 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
558
568
  endCol: number;
559
569
  }> = [];
560
570
  let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
571
+ let escHovered = false;
572
+ let hoveredKey: PreviewKey | undefined;
561
573
 
562
574
  const padLine = (text: string, width: number): string => {
563
575
  const truncated = truncateToWidth(text, width, "…");
@@ -588,27 +600,32 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
588
600
  }
589
601
 
590
602
  const mouse = parseSgrMousePacket(data);
591
- if (
592
- mouse?.final !== "M" ||
593
- mouseBaseButton(mouse.code) !== 0 ||
594
- (mouse.code & 32) !== 0
595
- )
596
- return;
597
- if (
603
+ if (!mouse || mouse.final !== "M") return;
604
+ const overEsc = Boolean(
598
605
  escHitbox &&
599
- mouse.row === escHitbox.row &&
600
- mouse.col >= escHitbox.startCol &&
601
- mouse.col <= escHitbox.endCol
602
- ) {
603
- done(undefined);
604
- return;
605
- }
606
+ mouse.row === escHitbox.row &&
607
+ mouse.col >= escHitbox.startCol &&
608
+ mouse.col <= escHitbox.endCol,
609
+ );
606
610
  const hitbox = previewHitboxes.find(
607
611
  (candidate) =>
608
612
  mouse.row === candidate.row &&
609
613
  mouse.col >= candidate.startCol &&
610
614
  mouse.col <= candidate.endCol,
611
615
  );
616
+ if ((mouse.code & 32) !== 0) {
617
+ if (overEsc !== escHovered || hitbox?.key !== hoveredKey) {
618
+ escHovered = overEsc;
619
+ hoveredKey = hitbox?.key;
620
+ tui.requestRender();
621
+ }
622
+ return;
623
+ }
624
+ if (mouseBaseButton(mouse.code) !== 0) return;
625
+ if (overEsc) {
626
+ done(undefined);
627
+ return;
628
+ }
612
629
  if (hitbox) {
613
630
  selectedPreviewIndex = Math.max(
614
631
  0,
@@ -643,6 +660,9 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
643
660
  Math.max(...allParts.map((part) => part.label.length)),
644
661
  );
645
662
  const selectedLabel = visiblePreviews[selectedPreviewIndex]?.label;
663
+ const hoverLabel = visiblePreviews.find(
664
+ (preview) => preview.key === hoveredKey,
665
+ )?.label;
646
666
  const partRows = allParts.map((part) => {
647
667
  const pct = contextWindow > 0 ? (part.tokens / contextWindow) * 100 : 0;
648
668
  const swatch = theme.fg(part.color, "■");
@@ -651,12 +671,13 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
651
671
  const selected = part.label === selectedLabel;
652
672
  const prefix = selected ? "› " : " ";
653
673
  const row = padLine(`${prefix}${swatch} ${label} ${amount}`, inner);
654
- return selected ? theme.bg("selectedBg", row) : row;
674
+ if (selected) return theme.bg("selectedBg", row);
675
+ return part.label === hoverLabel ? theme.bg("customMessageBg", row) : row;
655
676
  });
656
677
  const border = (text: string) => theme.fg("border", text);
657
678
  const lines = [
658
679
  border(`╭${"─".repeat(inner)}╮`),
659
- `${border("│")}${padLine(` ${title} ${theme.fg("muted", subtitle)}`, inner - escWidth)}${theme.fg("muted", "[esc]")}${border("│")}`,
680
+ `${border("│")}${padLine(` ${title} ${theme.fg("muted", subtitle)}`, inner - escWidth)}${theme.fg(escHovered ? "text" : "muted", "[esc]")}${border("│")}`,
660
681
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
661
682
  `${border("│")}${padLine(` ${segments}`, inner)}${border("│")}`,
662
683
  `${border("│")}${" ".repeat(inner)}${border("│")}`,
@@ -706,6 +727,12 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
706
727
  },
707
728
  },
708
729
  );
730
+ let action;
731
+ try {
732
+ action = await actionPromise;
733
+ } finally {
734
+ activeContextOverlays--;
735
+ }
709
736
 
710
737
  if (!action) break;
711
738
  const preview = previewByKey.get(action as PreviewKey);
@@ -141,7 +141,7 @@ export function installMessageDisplayRendering(): () => void {
141
141
  tracked: new WeakSet(),
142
142
  dispose: () => {},
143
143
  };
144
- const track = (component: object): void => {
144
+ const track = (component: Record<PropertyKey, unknown>): void => {
145
145
  for (const ref of patch.components) {
146
146
  if (!ref.deref()) patch.components.delete(ref);
147
147
  }
@@ -86,7 +86,8 @@ const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
86
86
  const TOOL_MOUSE_MOTION_DISABLE = "\x1b[?1003l";
87
87
  const FULLSCREEN_MOTION_ENABLED = Symbol("ccstyle.fullscreen-motion-enabled");
88
88
  const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi.ccstyle.tool-mouse-owner");
89
- const DEFAULT_TOOL_MOUSE_OWNER = {};
89
+ type ToolMouseOwner = object;
90
+ const DEFAULT_TOOL_MOUSE_OWNER: ToolMouseOwner = {};
90
91
  export const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
91
92
 
92
93
  let toolMouseUi: any = null;
@@ -96,7 +97,7 @@ let toolMouseRenderPatchOriginal: ((...args: any[]) => any) | null = null;
96
97
  let toolMouseRenderPatchWrapper: ((...args: any[]) => any) | null = null;
97
98
  let toolMouseRenderPatchState: { active: boolean } | null = null;
98
99
  let toolMouseRawWrite: ((data: string) => unknown) | null = null;
99
- let toolMouseInstallationOwner: object | null = null;
100
+ let toolMouseInstallationOwner: ToolMouseOwner | null = null;
100
101
  let fullscreenMotionTerminal: any = null;
101
102
  let ownsFullscreenMotion = false;
102
103
  let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
@@ -156,9 +157,10 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
156
157
  }
157
158
 
158
159
  const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
159
- let lastExpandPanelClick: { id: unknown; at: number } | null = null;
160
+ type ExpandPanelIdentity = string | object;
161
+ let lastExpandPanelClick: { id: ExpandPanelIdentity; at: number } | null = null;
160
162
 
161
- function expandPanelIdentity(card: any): unknown {
163
+ function expandPanelIdentity(card: any): ExpandPanelIdentity {
162
164
  return card instanceof ThinkingPreviewBlock
163
165
  ? thinkingRunKey(card.messageTimestamp, card.runStartIndex)
164
166
  : card;
@@ -471,7 +473,7 @@ function patchFullscreenViewportInput(tui: any): void {
471
473
  }
472
474
  }
473
475
  }
474
- return Reflect.apply(original, this, [data]);
476
+ return original.apply(this, [data]);
475
477
  };
476
478
  }
477
479
 
@@ -611,7 +613,7 @@ function wrapToolRendersForFrame(
611
613
  ) {
612
614
  const boxOriginal = box.render;
613
615
  const boxWrapped = function (this: any, ...boxArgs: any[]) {
614
- const boxLines = Reflect.apply(boxOriginal, this, boxArgs);
616
+ const boxLines = boxOriginal.apply(this, boxArgs);
615
617
  if (Array.isArray(boxLines)) contentBoxLines = boxLines.length;
616
618
  return boxLines;
617
619
  };
@@ -621,7 +623,7 @@ function wrapToolRendersForFrame(
621
623
  }
622
624
  }
623
625
  try {
624
- const lines = Reflect.apply(originalRender, this, renderArgs);
626
+ const lines = originalRender.apply(this, renderArgs);
625
627
  if (!Array.isArray(lines)) return lines;
626
628
  renderedTools.push({
627
629
  component,
@@ -697,10 +699,10 @@ function patchToolMouseMotionAfterRender(tui: any): void {
697
699
  const rawWrite = typeof terminal?.write === "function" ? terminal.write : undefined;
698
700
  if (typeof original !== "function") return;
699
701
 
700
- toolMouseRawWrite = rawWrite ? (data) => Reflect.apply(rawWrite, terminal, [data]) : null;
702
+ toolMouseRawWrite = rawWrite ? (data) => rawWrite.apply(terminal, [data]) : null;
701
703
  const patchState = { active: true };
702
704
  const wrapper = function (this: any, ...args: any[]) {
703
- if (!patchState.active) return Reflect.apply(original, this, args);
705
+ if (!patchState.active) return original.apply(this, args);
704
706
  const renderedTools: FrameToolRender[] = [];
705
707
  const idToComponent = new Map<number, any>();
706
708
  const frame: IoViewFrameState = {
@@ -717,7 +719,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
717
719
  let sawTuiRender = false;
718
720
  if (originalTuiRender) {
719
721
  const wrappedTuiRender = function (this: any, ...renderArgs: any[]) {
720
- const lines = Reflect.apply(originalTuiRender, this, renderArgs);
722
+ const lines = originalTuiRender.apply(this, renderArgs);
721
723
  if (!Array.isArray(lines)) return lines;
722
724
  sawTuiRender = true;
723
725
  const extracted = extractToolFramePlacements(
@@ -734,7 +736,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
734
736
  const previousFrame = getActiveIoViewFrame();
735
737
  setActiveIoViewFrame(frame);
736
738
  try {
737
- const result = Reflect.apply(original, this, args);
739
+ const result = original.apply(this, args);
738
740
  succeeded = true;
739
741
  // Test harnesses may paint via doRender without tui.render; recover markers there.
740
742
  if (!sawTuiRender && Array.isArray(this.previousLines)) {
@@ -811,7 +813,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
811
813
  }
812
814
 
813
815
  export function teardownToolMouseInteraction(
814
- owner: object = toolMouseInstallationOwner ?? DEFAULT_TOOL_MOUSE_OWNER,
816
+ owner: ToolMouseOwner = toolMouseInstallationOwner ?? DEFAULT_TOOL_MOUSE_OWNER,
815
817
  ): void {
816
818
  const host = globalThis as any;
817
819
  if (host[TOOL_MOUSE_OWNER_KEY] && host[TOOL_MOUSE_OWNER_KEY] !== owner) return;
@@ -861,7 +863,7 @@ export function resetToolHoverState(): void {
861
863
 
862
864
  export function installToolMouseInteraction(
863
865
  ctx: any,
864
- owner: object = DEFAULT_TOOL_MOUSE_OWNER,
866
+ owner: ToolMouseOwner = DEFAULT_TOOL_MOUSE_OWNER,
865
867
  ): void {
866
868
  teardownToolMouseInteraction(toolMouseInstallationOwner ?? owner);
867
869
  if (ctx?.mode !== "tui" || !ctx?.hasUI) return;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-cc-extensions",
3
- "version": "0.8.57",
3
+ "version": "0.8.58",
4
4
  "license": "MIT",
5
5
  "description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
6
6
  "type": "module",
@@ -69,8 +69,8 @@
69
69
  },
70
70
  "x-upstream": {
71
71
  "package": "pi-cc-extensions",
72
- "version": "0.8.64",
72
+ "version": "0.8.66",
73
73
  "repository": "https://github.com/minuque/pi-cc-extensions",
74
- "commit": "4709081a28255b8bdfe610620a2a9ee8951013d6"
74
+ "commit": "e971a39d1685590de81bd445e3a829279d216039"
75
75
  }
76
76
  }
@@ -1,5 +1,34 @@
1
1
  # @tifan/pi-handoff
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#116](https://github.com/HerbertGao/pi-extensions/pull/116) [`ecda398`](https://github.com/HerbertGao/pi-extensions/commit/ecda398b9a95212c512d53325c617e485e6ded8d) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Adopt prompt-marker handoffs that generate a handoff artifact and start a clean session, while retaining the session query tool and local rename package integration.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`ecda398`](https://github.com/HerbertGao/pi-extensions/commit/ecda398b9a95212c512d53325c617e485e6ded8d)]:
12
+ - @herbertgao/pi-rename@0.5.0
13
+
14
+ ## 2.0.1
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [[`b2ea184`](https://github.com/tifandotme/pi-extensions/commit/b2ea1841e99243aa4773bd5c33f43c40248859db)]:
19
+ - @tifan/pi-rename@0.5.0
20
+
21
+ ## 2.0.0
22
+
23
+ ### Major Changes
24
+
25
+ - [`297e825`](https://github.com/tifandotme/pi-extensions/commit/297e825ba42eeed32814a34cd4fe5bdbe935b11c) Thanks [@tifandotme](https://github.com/tifandotme)! - Replace `/handoff-session` with the `-handoff` prompt marker and automatically start the new session from the generated handoff.
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [[`e8709bc`](https://github.com/tifandotme/pi-extensions/commit/e8709bc388aa8e094918ec465cfab511358f18ff)]:
30
+ - @tifan/pi-rename@0.4.3
31
+
3
32
  ## 1.1.2
4
33
 
5
34
  ### Patch Changes
@@ -10,42 +10,39 @@ Start a fresh pi session from a handoff document, and query past sessions for co
10
10
  pi install npm:@herbertgao/pi-handoff
11
11
  ```
12
12
 
13
- For local development:
13
+ ## Requirements
14
+
15
+ Matt Pocock's [`handoff` skill](https://github.com/mattpocock/skills/blob/main/skills/productivity/handoff/SKILL.md) is required:
14
16
 
15
17
  ```bash
16
- pi install /absolute/path/to/pi-extensions/packages/pi-handoff
18
+ npx skills add https://github.com/mattpocock/skills --global --skill handoff
17
19
  ```
18
20
 
19
- You also need a `handoff` skill installed and discoverable by pi. For example, install or expose Matt Pocock's `handoff` skill, then run `/reload`.
20
-
21
21
  ## How it works
22
22
 
23
- Run `/handoff-session` to generate a handoff document from the current session and start a clean session from it. The command asks what the next session is for, uses the installed `handoff` skill as the document policy, and writes the handoff under the OS temp directory.
23
+ Submit a prompt containing a standalone `-handoff` marker to generate a handoff document from the current session and automatically start a clean session from it. The marker can appear anywhere in the prompt. The handoff is written under the OS temp directory.
24
24
 
25
- The new session name is generated from the current conversation using pi-rename-style rules: lowercase, hyphen-separated, and under 60 characters. Naming uses `openai-codex/gpt-5.6-luna` when available, with a local fallback. The focus answer is used for the handoff document, not for the session name. The prompt is left in the editor for review and manual submit.
25
+ The new session name uses the current session name when available. Otherwise, it uses the next-session focus, sanitized to lowercase, hyphen-separated text under 60 characters.
26
26
 
27
27
  The handoff includes the previous session path when available, so the next agent can use `session_query` if the handoff omits a detail.
28
28
 
29
- ## Commands
29
+ ## Usage
30
30
 
31
- - `/handoff-session`: Generate a handoff from the current session and start a renamed session from it.
31
+ ```text
32
+ fix the auth flow -handoff
33
+ ```
32
34
 
33
- ## Tools
35
+ The standalone `-handoff` marker is consumed before the prompt reaches the agent. The text before and after it becomes the next-session focus. A prompt containing only `-handoff` continues the current work.
34
36
 
35
- - `session_query`: Answer a question about a previous pi session, given the full path to its `.jsonl` file and the question to ask.
37
+ ## `session_query`
36
38
 
37
- ## Behavior
39
+ `session_query`: Answer a question about a previous pi session, given the full path to its `.jsonl` file and the question to ask. It uses the configured `openai-codex/gpt-5.6-luna` model, which must be available and authenticated.
38
40
 
39
- - Uses the current selected model to generate the handoff document.
40
- - Uses `openai-codex/gpt-5.6-luna` to name the new pi session when available.
41
- - Requires a discoverable skill named exactly `handoff`.
42
- - Writes stable, readable files under the OS temp directory, such as `/tmp/pi-handoffs/pi-handoff-2026-06-01-inline-skills.md`.
43
- - Starts a clean session with native parent-session linking.
44
- - Does not autosend.
41
+ Pi provides session loading and compaction-aware context reconstruction, but not semantic questions about an arbitrary previous session. `session_query` supplies that model-powered query layer.
45
42
 
46
43
  ## Release notes
47
44
 
48
- See [CHANGELOG.md](CHANGELOG.md)
45
+ See [CHANGELOG.md](CHANGELOG.md).
49
46
 
50
47
  ## License
51
48
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-handoff",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Transfer pi session context to a new session and query past sessions.",
5
5
  "keywords": [
6
6
  "handoff",
@@ -33,7 +33,14 @@
33
33
  "provenance": true
34
34
  },
35
35
  "dependencies": {
36
- "@herbertgao/pi-rename": "^0.4.2"
36
+ "@herbertgao/pi-rename": "^0.5.0",
37
+ "typebox": "^1.1.38"
38
+ },
39
+ "peerDependencies": {
40
+ "@earendil-works/pi-agent-core": ">=0.84.2",
41
+ "@earendil-works/pi-ai": ">=0.84.2",
42
+ "@earendil-works/pi-coding-agent": ">=0.84.2",
43
+ "@earendil-works/pi-tui": ">=0.84.2"
37
44
  },
38
45
  "engines": {
39
46
  "node": ">=20"
@@ -46,8 +53,8 @@
46
53
  },
47
54
  "x-upstream": {
48
55
  "package": "@tifan/pi-handoff",
49
- "version": "1.1.2",
56
+ "version": "2.0.1",
50
57
  "repository": "https://github.com/tifandotme/pi-extensions",
51
- "commit": "460d580"
58
+ "commit": "3f6b5e628e31817960a02cfae9bcb4a3f4bcd79b"
52
59
  }
53
60
  }
@@ -0,0 +1,33 @@
1
+ import { complete, type Message } from "@earendil-works/pi-ai/compat"
2
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent"
3
+
4
+ export async function completeText(
5
+ ctx: Pick<ExtensionContext, "modelRegistry">,
6
+ model: NonNullable<ExtensionContext["model"]>,
7
+ systemPrompt: string,
8
+ message: Message,
9
+ signal?: AbortSignal,
10
+ ): Promise<string | null> {
11
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model)
12
+ if (!auth.ok) throw new Error(auth.error)
13
+
14
+ const response = await complete(
15
+ model,
16
+ { systemPrompt, messages: [message] },
17
+ {
18
+ ...(auth.apiKey ? { apiKey: auth.apiKey } : {}),
19
+ ...(auth.headers ? { headers: auth.headers } : {}),
20
+ ...(signal ? { signal } : {}),
21
+ },
22
+ )
23
+
24
+ if (response.stopReason === "aborted") return null
25
+
26
+ return response.content
27
+ .filter((content): content is { type: "text"; text: string } => {
28
+ return content.type === "text"
29
+ })
30
+ .map((content) => content.text)
31
+ .join("\n")
32
+ .trim()
33
+ }