@herbertgao/pi-extensions 2026.8.7 → 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 (161) hide show
  1. package/README.md +8 -6
  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/compact-thinking.ts +63 -15
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +56 -29
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +78 -16
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +13 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +30 -17
  33. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  34. package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +29 -0
  35. package/node_modules/@herbertgao/pi-handoff/README.md +15 -18
  36. package/node_modules/@herbertgao/pi-handoff/package.json +11 -4
  37. package/node_modules/@herbertgao/pi-handoff/src/complete-text.ts +33 -0
  38. package/node_modules/@herbertgao/pi-handoff/src/index.ts +127 -232
  39. package/node_modules/@herbertgao/pi-handoff/src/session-query.ts +15 -30
  40. package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +14 -0
  41. package/node_modules/@herbertgao/pi-mermaid-open/README.md +21 -4
  42. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +11 -0
  43. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/viewer.mjs +305 -0
  44. package/node_modules/@herbertgao/pi-mermaid-open/package.json +15 -4
  45. package/node_modules/@herbertgao/pi-mermaid-open/src/index.ts +351 -101
  46. package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +12 -0
  47. package/node_modules/@herbertgao/pi-preferred-thinking/README.md +30 -14
  48. package/node_modules/@herbertgao/pi-preferred-thinking/package.json +7 -4
  49. package/node_modules/@herbertgao/pi-preferred-thinking/src/index.ts +140 -85
  50. package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +12 -0
  51. package/node_modules/@herbertgao/pi-recap/README.md +5 -3
  52. package/node_modules/@herbertgao/pi-recap/package.json +9 -3
  53. package/node_modules/@herbertgao/pi-recap/src/index.ts +3 -7
  54. package/node_modules/@herbertgao/pi-recap/src/model-picker.ts +6 -19
  55. package/node_modules/@herbertgao/pi-recap/src/models.ts +9 -21
  56. package/node_modules/@herbertgao/pi-recap/src/tui.ts +1 -1
  57. package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +49 -0
  58. package/node_modules/@herbertgao/pi-rename/README.md +13 -11
  59. package/node_modules/@herbertgao/pi-rename/package.json +13 -3
  60. package/node_modules/@herbertgao/pi-rename/src/index.ts +135 -59
  61. package/node_modules/@herbertgao/pi-rename/src/models.ts +5 -13
  62. package/node_modules/@herbertgao/pi-rename/src/naming.ts +2 -6
  63. package/node_modules/@herbertgao/pi-stash/package.json +3 -3
  64. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  65. package/node_modules/@herbertgao/pi-subagents/README.md +16 -3
  66. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  67. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +15 -4
  68. package/node_modules/@herbertgao/pi-subagents/src/index.ts +16 -0
  69. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +5 -1
  70. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +4 -4
  71. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +154 -96
  72. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +3 -0
  73. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  74. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +7 -5
  75. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  76. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +3 -0
  77. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  78. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +137 -96
  79. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +44 -32
  80. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  81. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  82. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  83. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  84. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  85. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  86. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  87. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +56 -34
  88. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +50 -4
  89. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  90. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  91. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  92. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  93. package/node_modules/pi-lens/CHANGELOG.md +90 -0
  94. package/node_modules/pi-lens/README.md +106 -0
  95. package/node_modules/pi-lens/dist/clients/bash-file-access.js +97 -6
  96. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +16 -6
  97. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +14 -0
  98. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  99. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +22 -1
  100. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  101. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  102. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +8 -10
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +10 -10
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -15
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +19 -11
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +35 -13
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +8 -8
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +110 -23
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +117 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +16 -14
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +8 -9
  119. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +22 -0
  120. package/node_modules/pi-lens/dist/clients/jscpd-client.js +5 -2
  121. package/node_modules/pi-lens/dist/clients/language-profile.js +7 -2
  122. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +27 -2
  123. package/node_modules/pi-lens/dist/clients/lsp/client.js +144 -5
  124. package/node_modules/pi-lens/dist/clients/lsp/index.js +151 -6
  125. package/node_modules/pi-lens/dist/clients/module-report.js +14 -1
  126. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  127. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +18 -9
  128. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  129. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +7 -2
  130. package/node_modules/pi-lens/dist/clients/read-guard.js +88 -19
  131. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  132. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  133. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  134. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +44 -0
  135. package/node_modules/pi-lens/dist/clients/runtime-session.js +41 -0
  136. package/node_modules/pi-lens/dist/clients/runtime-turn.js +47 -2
  137. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  138. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  139. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  140. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  141. package/node_modules/pi-lens/dist/index.js +1224 -393
  142. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +10 -3
  143. package/node_modules/pi-lens/package.json +18 -5
  144. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  145. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  146. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  147. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  148. package/node_modules/pi-web-access/CHANGELOG.md +27 -0
  149. package/node_modules/pi-web-access/README.md +8 -7
  150. package/node_modules/pi-web-access/chrome-cookies.ts +96 -20
  151. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  152. package/node_modules/pi-web-access/extract.ts +12 -2
  153. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  154. package/node_modules/pi-web-access/gemini-search.ts +28 -10
  155. package/node_modules/pi-web-access/github-extract.ts +65 -36
  156. package/node_modules/pi-web-access/index.ts +43 -14
  157. package/node_modules/pi-web-access/openai-search.ts +17 -6
  158. package/node_modules/pi-web-access/package.json +1 -1
  159. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  160. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  161. package/package.json +19 -18
@@ -10,17 +10,13 @@ Generate session names for pi and Herdr.
10
10
  pi install npm:@herbertgao/pi-rename
11
11
  ```
12
12
 
13
- For local development:
14
-
15
- ```bash
16
- pi install /absolute/path/to/pi-extensions/packages/pi-rename
17
- ```
13
+ This package requires Pi 0.84.2 or newer.
18
14
 
19
15
  ## How it works
20
16
 
21
- Run `/rename` to generate a fresh hyphen-separated session name. The extension applies the name to the pi session and, when pi is running inside Herdr, to the current Herdr tab.
17
+ Run `/rename` to generate a fresh hyphen-separated session name. The extension applies the name to the pi session and, when pi is running inside Herdr, to the current pane and tab when it is the tab's only pane, or to the current pane in a split tab.
22
18
 
23
- When a named session starts or resumes in Herdr, the extension also applies the saved pi session name to the current tab if the tab still has its default Herdr label.
19
+ When a named session starts or resumes in Herdr, the extension applies the saved pi session name when the target still has its default label or the temporary `herdr-wrap` label.
24
20
 
25
21
  `/rename` builds naming context from the first user message plus up to three latest user messages. It ignores assistant replies, tool output, and attachments. Before sending context to the rename model, it redacts common secrets.
26
22
 
@@ -43,7 +39,7 @@ Run `/rename config` to choose a different model.
43
39
 
44
40
  After you choose a model, `pi-rename` uses only that model. Choose `Use default` in `/rename config` to return to the default.
45
41
 
46
- You can also edit `~/.config/pi/extensions/pi-rename.json` manually:
42
+ You can also edit `$PI_CODING_AGENT_DIR/extensions/pi-rename.json` manually:
47
43
 
48
44
  ```json
49
45
  {
@@ -53,14 +49,20 @@ You can also edit `~/.config/pi/extensions/pi-rename.json` manually:
53
49
 
54
50
  ## Herdr behavior
55
51
 
56
- The extension uses `HERDR_PANE_ID` to find the current Herdr pane, then renames that pane's tab.
52
+ Herdr tab renaming requires the `herdr` CLI. If it is unavailable, the extension still renames the pi session.
57
53
 
58
- On session startup or resume, it only auto-renames tabs that still have the default Herdr label, such as the tab number. It does not overwrite custom Herdr tab labels.
54
+ The extension uses `HERDR_PANE_ID` to find the current Herdr pane. With one pane in the tab, it renames both the pane and tab. With sibling panes, it renames only the current pane.
59
55
 
60
- On quit, the Herdr tab keeps the last session name.
56
+ On session startup or resume, it does not overwrite custom Herdr labels. It recognizes the temporary label from the Nushell `herdr-wrap` wrapper so saved session names still apply.
57
+
58
+ On quit, the target keeps the last session name.
61
59
 
62
60
  If pi is not running inside Herdr, only the pi session name is updated.
63
61
 
62
+ ## Release notes
63
+
64
+ See [CHANGELOG.md](CHANGELOG.md).
65
+
64
66
  ## License
65
67
 
66
68
  [MIT](LICENSE)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-rename",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Generate pi session names and rename the current Herdr tab.",
5
5
  "keywords": [
6
6
  "herdr",
@@ -26,6 +26,7 @@
26
26
  "files": [
27
27
  "src/**/*.ts",
28
28
  "README.md",
29
+ "CHANGELOG.md",
29
30
  "LICENSE"
30
31
  ],
31
32
  "type": "module",
@@ -36,6 +37,15 @@
36
37
  "access": "public",
37
38
  "provenance": true
38
39
  },
40
+ "scripts": {
41
+ "test": "node --experimental-strip-types --test tests/**/*.test.ts"
42
+ },
43
+ "peerDependencies": {
44
+ "@earendil-works/pi-agent-core": ">=0.84.2",
45
+ "@earendil-works/pi-ai": ">=0.84.2",
46
+ "@earendil-works/pi-coding-agent": ">=0.84.2",
47
+ "@earendil-works/pi-tui": ">=0.84.2"
48
+ },
39
49
  "engines": {
40
50
  "node": ">=20"
41
51
  },
@@ -46,8 +56,8 @@
46
56
  },
47
57
  "x-upstream": {
48
58
  "package": "@tifan/pi-rename",
49
- "version": "0.4.2",
59
+ "version": "0.5.0",
50
60
  "repository": "https://github.com/tifandotme/pi-extensions",
51
- "commit": "460d580"
61
+ "commit": "3f6b5e628e31817960a02cfae9bcb4a3f4bcd79b"
52
62
  }
53
63
  }
@@ -1,7 +1,11 @@
1
1
  import { execFile } from "node:child_process"
2
+ import path from "node:path"
2
3
  import { promisify } from "node:util"
3
4
  import type { AgentMessage } from "@earendil-works/pi-agent-core"
4
- import type { AutocompleteItem } from "@earendil-works/pi-tui"
5
+ import {
6
+ CancellableLoader,
7
+ type AutocompleteItem,
8
+ } from "@earendil-works/pi-tui"
5
9
  import type {
6
10
  ExtensionAPI,
7
11
  ExtensionContext,
@@ -61,10 +65,17 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
61
65
  return value as Record<string, unknown>
62
66
  }
63
67
 
68
+ interface HerdrPaneInfo {
69
+ readonly id: string
70
+ readonly label?: string
71
+ readonly tabId: string
72
+ }
73
+
64
74
  interface HerdrTabInfo {
65
75
  readonly id: string
66
76
  readonly label?: string
67
77
  readonly number?: number
78
+ readonly paneCount?: number
68
79
  }
69
80
 
70
81
  function parseRecord(value: string): Record<string, unknown> | undefined {
@@ -75,12 +86,33 @@ function parseRecord(value: string): Record<string, unknown> | undefined {
75
86
  }
76
87
  }
77
88
 
78
- function extractTabId(stdout: string): string | undefined {
89
+ interface HerdrContext {
90
+ readonly pane: HerdrPaneInfo
91
+ readonly tab: HerdrTabInfo
92
+ }
93
+
94
+ function extractPaneInfo(stdout: string): HerdrPaneInfo | undefined {
79
95
  const parsed = parseRecord(stdout)
80
96
  const result = asRecord(parsed?.["result"])
81
97
  const pane = asRecord(result?.["pane"])
98
+ const paneId = pane?.["pane_id"]
82
99
  const tabId = pane?.["tab_id"]
83
- return typeof tabId === "string" && tabId.trim() ? tabId : undefined
100
+
101
+ if (
102
+ typeof paneId !== "string" ||
103
+ !paneId.trim() ||
104
+ typeof tabId !== "string" ||
105
+ !tabId.trim()
106
+ ) {
107
+ return undefined
108
+ }
109
+
110
+ const label = pane?.["label"]
111
+ return {
112
+ id: paneId,
113
+ tabId,
114
+ ...(typeof label === "string" ? { label } : {}),
115
+ }
84
116
  }
85
117
 
86
118
  function extractTabInfo(stdout: string): HerdrTabInfo | undefined {
@@ -93,28 +125,35 @@ function extractTabInfo(stdout: string): HerdrTabInfo | undefined {
93
125
 
94
126
  const label = tab?.["label"]
95
127
  const number = tab?.["number"]
128
+ const paneCount = tab?.["pane_count"]
96
129
 
97
130
  return {
98
131
  id: tabId,
99
132
  ...(typeof label === "string" ? { label } : {}),
100
133
  ...(typeof number === "number" ? { number } : {}),
134
+ ...(typeof paneCount === "number" ? { paneCount } : {}),
101
135
  }
102
136
  }
103
137
 
104
- async function getCurrentHerdrTabId(): Promise<string | undefined> {
138
+ async function getCurrentHerdrContext(): Promise<HerdrContext | undefined> {
105
139
  const paneId = process.env["HERDR_PANE_ID"]?.trim()
106
140
  if (!paneId) return undefined
107
141
 
108
- const { stdout } = await execFileAsync("herdr", ["pane", "get", paneId])
109
- return extractTabId(stdout)
110
- }
111
-
112
- async function getCurrentHerdrTabInfo(): Promise<HerdrTabInfo | undefined> {
113
- const tabId = await getCurrentHerdrTabId()
114
- if (!tabId) return undefined
115
-
116
- const { stdout } = await execFileAsync("herdr", ["tab", "get", tabId])
117
- return extractTabInfo(stdout)
142
+ const { stdout: paneStdout } = await execFileAsync("herdr", [
143
+ "pane",
144
+ "get",
145
+ paneId,
146
+ ])
147
+ const pane = extractPaneInfo(paneStdout)
148
+ if (!pane) return undefined
149
+
150
+ const { stdout: tabStdout } = await execFileAsync("herdr", [
151
+ "tab",
152
+ "get",
153
+ pane.tabId,
154
+ ])
155
+ const tab = extractTabInfo(tabStdout)
156
+ return tab ? { pane, tab } : undefined
118
157
  }
119
158
 
120
159
  function isDefaultHerdrTabLabel(tab: HerdrTabInfo): boolean {
@@ -124,22 +163,49 @@ function isDefaultHerdrTabLabel(tab: HerdrTabInfo): boolean {
124
163
  return typeof tab.number === "number" && label === String(tab.number)
125
164
  }
126
165
 
127
- async function renameCurrentHerdrTab(name: string): Promise<boolean> {
128
- const tabId = await getCurrentHerdrTabId()
129
- if (!tabId) return false
166
+ function isPiWrapperLabel(label: string | undefined): boolean {
167
+ return label?.trim() === `${path.basename(process.cwd())} (pi)`
168
+ }
169
+
170
+ function canRenameSessionStart(context: HerdrContext): boolean {
171
+ const singlePane = context.tab.paneCount === 1
172
+ const label = singlePane ? context.tab.label : context.pane.label
173
+ return (
174
+ (singlePane ? isDefaultHerdrTabLabel(context.tab) : !label?.trim()) ||
175
+ isPiWrapperLabel(label)
176
+ )
177
+ }
130
178
 
131
- await execFileAsync("herdr", ["tab", "rename", tabId, name])
179
+ async function renameHerdrTarget(
180
+ context: HerdrContext,
181
+ name: string,
182
+ ): Promise<boolean> {
183
+ await execFileAsync("herdr", ["pane", "rename", context.pane.id, name])
184
+ if (context.tab.paneCount === 1) {
185
+ await execFileAsync("herdr", ["tab", "rename", context.tab.id, name])
186
+ }
132
187
  return true
133
188
  }
134
189
 
135
- async function renameCurrentHerdrTabIfDefault(name: string): Promise<boolean> {
136
- const tab = await getCurrentHerdrTabInfo()
137
- if (!tab || tab.label?.trim() === name || !isDefaultHerdrTabLabel(tab)) {
190
+ async function renameCurrentHerdrTarget(name: string): Promise<boolean> {
191
+ const context = await getCurrentHerdrContext()
192
+ return context ? renameHerdrTarget(context, name) : false
193
+ }
194
+
195
+ async function renameCurrentHerdrTargetIfDefault(
196
+ name: string,
197
+ ): Promise<boolean> {
198
+ const context = await getCurrentHerdrContext()
199
+ if (
200
+ !context ||
201
+ !canRenameSessionStart(context) ||
202
+ context.tab.label?.trim() === name ||
203
+ context.pane.label?.trim() === name
204
+ ) {
138
205
  return false
139
206
  }
140
207
 
141
- await execFileAsync("herdr", ["tab", "rename", tab.id, name])
142
- return true
208
+ return renameHerdrTarget(context, name)
143
209
  }
144
210
 
145
211
  function hasSessionContextReader(
@@ -166,7 +232,7 @@ function getCurrentSessionMessages(ctx: ExtensionContext): AgentMessage[] {
166
232
 
167
233
  async function applyRename(pi: ExtensionAPI, name: string): Promise<boolean> {
168
234
  pi.setSessionName(name)
169
- return renameCurrentHerdrTab(name)
235
+ return renameCurrentHerdrTarget(name)
170
236
  }
171
237
 
172
238
  async function runRenameCommand(
@@ -180,47 +246,57 @@ async function runRenameCommand(
180
246
  return
181
247
  }
182
248
 
183
- const result = await generateRename(ctx, state.modelConfig, context)
184
- if (!result) {
185
- ctx.ui.notify("Could not generate a session name.", "error")
186
- return
249
+ if (ctx.mode === "tui") {
250
+ ctx.ui.setWidget("pi-rename", (tui, theme) => {
251
+ const color = (text: string) => theme.fg("dim", text)
252
+ return new CancellableLoader(tui, color, color, "renaming session...")
253
+ })
187
254
  }
255
+ try {
256
+ const result = await generateRename(ctx, state.modelConfig, context)
257
+ if (!result) {
258
+ ctx.ui.notify("Could not generate a session name.", "error")
259
+ return
260
+ }
188
261
 
189
- let renamedHerdr = false
190
- let herdrError: string | undefined
262
+ let renamedHerdr = false
263
+ let herdrError: string | undefined
191
264
 
192
- try {
193
- renamedHerdr = await applyRename(pi, result.name)
194
- } catch (error) {
195
- herdrError = error instanceof Error ? error.message : String(error)
196
- }
265
+ try {
266
+ renamedHerdr = await applyRename(pi, result.name)
267
+ } catch (error) {
268
+ herdrError = error instanceof Error ? error.message : String(error)
269
+ }
197
270
 
198
- if (result.source === "fallback") {
199
- ctx.ui.notify(
200
- [
201
- `Session renamed with fallback: ${result.name}`,
202
- `Could not use rename model: ${result.reason}`,
203
- ...(herdrError ? [`Herdr tab rename failed: ${herdrError}`] : []),
204
- ].join("\n"),
205
- "warning",
206
- )
207
- return
208
- }
271
+ if (result.source === "fallback") {
272
+ ctx.ui.notify(
273
+ [
274
+ `Session renamed with fallback: ${result.name}`,
275
+ `Could not use rename model: ${result.reason}`,
276
+ ...(herdrError ? [`Herdr label rename failed: ${herdrError}`] : []),
277
+ ].join("\n"),
278
+ "warning",
279
+ )
280
+ return
281
+ }
282
+
283
+ if (herdrError) {
284
+ ctx.ui.notify(
285
+ `Session renamed, but Herdr label rename failed: ${herdrError}`,
286
+ "warning",
287
+ )
288
+ return
289
+ }
209
290
 
210
- if (herdrError) {
211
291
  ctx.ui.notify(
212
- `Session renamed, but Herdr tab rename failed: ${herdrError}`,
213
- "warning",
292
+ renamedHerdr
293
+ ? `Session and Herdr label renamed: ${result.name}`
294
+ : `Session renamed: ${result.name}`,
295
+ "info",
214
296
  )
215
- return
297
+ } finally {
298
+ if (ctx.mode === "tui") ctx.ui.setWidget("pi-rename", undefined)
216
299
  }
217
-
218
- ctx.ui.notify(
219
- renamedHerdr
220
- ? `Session and Herdr tab renamed: ${result.name}`
221
- : `Session renamed: ${result.name}`,
222
- "info",
223
- )
224
300
  }
225
301
 
226
302
  function getRenameArgumentCompletions(
@@ -293,7 +369,7 @@ async function notifyRenameStatus(
293
369
  }
294
370
 
295
371
  const context = getUserMessageContext(getCurrentSessionMessages(ctx))
296
- const herdrLine = `herdr tab: ${process.env["HERDR_PANE_ID"]?.trim() ? "available" : "unavailable"}`
372
+ const herdrLine = `herdr: ${process.env["HERDR_PANE_ID"]?.trim() ? "available" : "unavailable"}`
297
373
  const contextLine = `context: ${context?.count ?? 0} user messages`
298
374
 
299
375
  ctx.ui.notify(
@@ -361,7 +437,7 @@ export default function (pi: ExtensionAPI): void {
361
437
  if (!sessionName) return
362
438
 
363
439
  try {
364
- await renameCurrentHerdrTabIfDefault(sessionName)
440
+ await renameCurrentHerdrTargetIfDefault(sessionName)
365
441
  } catch {
366
442
  // Keep session startup quiet if Herdr is unavailable or rejects the rename.
367
443
  }
@@ -6,7 +6,7 @@ import {
6
6
  writeFileSync,
7
7
  } from "node:fs"
8
8
  import path from "node:path"
9
- import type { Api, Model, ProviderHeaders } from "@earendil-works/pi-ai"
9
+ import type { Api, Model } from "@earendil-works/pi-ai"
10
10
  import {
11
11
  getAgentDir,
12
12
  type ExtensionContext,
@@ -22,8 +22,6 @@ export interface RenameModelPreference {
22
22
 
23
23
  export interface RenameModelAuth {
24
24
  readonly model: Model<Api>
25
- readonly apiKey: string
26
- readonly headers: ProviderHeaders | undefined
27
25
  }
28
26
 
29
27
  export type RenameModelConfig =
@@ -134,14 +132,8 @@ async function getModelAuth(
134
132
  )
135
133
  if (!model) return undefined
136
134
 
137
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model)
138
- return auth.ok && auth.apiKey
139
- ? {
140
- model,
141
- apiKey: auth.apiKey,
142
- headers: auth.headers,
143
- }
144
- : undefined
135
+ const auth = await ctx.modelRegistry.getProviderAuth(model.provider)
136
+ return auth?.auth.apiKey ? { model } : undefined
145
137
  }
146
138
 
147
139
  export async function getRenameModelAuth(
@@ -181,8 +173,8 @@ export async function getAuthenticatedTextModelPreferences(
181
173
  .filter((model) => model.input.includes("text"))
182
174
  const authenticatedModels = await Promise.all(
183
175
  models.map(async (model) => {
184
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model)
185
- return auth.ok && auth.apiKey ? toModelPreference(model) : undefined
176
+ const auth = await ctx.modelRegistry.getProviderAuth(model.provider)
177
+ return auth?.auth.apiKey ? toModelPreference(model) : undefined
186
178
  }),
187
179
  )
188
180
 
@@ -1,5 +1,5 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core"
2
- import { complete, type Message } from "@earendil-works/pi-ai/compat"
2
+ import type { Message } from "@earendil-works/pi-ai"
3
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent"
4
4
  import {
5
5
  formatRenameModelKey,
@@ -123,17 +123,13 @@ export async function generateRename(
123
123
  const modelAuth = await getRenameModelAuth(ctx, modelConfig)
124
124
 
125
125
  if (modelAuth.status === "ok") {
126
- const response = await complete(
126
+ const response = await ctx.modelRegistry.complete(
127
127
  modelAuth.auth.model,
128
128
  {
129
129
  systemPrompt: RENAME_SYSTEM_PROMPT,
130
130
  messages: [buildRenamePrompt(context)],
131
131
  },
132
132
  {
133
- apiKey: modelAuth.auth.apiKey,
134
- ...(modelAuth.auth.headers
135
- ? { headers: modelAuth.auth.headers }
136
- : {}),
137
133
  maxTokens: RENAME_MAX_TOKENS,
138
134
  maxRetries: 0,
139
135
  cacheRetention: "none",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-stash",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Stash one pi draft and restore it after the next message.",
5
5
  "keywords": [
6
6
  "pi",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "x-upstream": {
46
46
  "package": "@tifan/pi-stash",
47
- "version": "0.1.0",
47
+ "version": "0.2.0",
48
48
  "repository": "https://github.com/tifandotme/pi-extensions",
49
- "commit": "460d580"
49
+ "commit": "b39d1e6359c78718cbf8fe5c74c82b89065e9a0b"
50
50
  }
51
51
  }
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch 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)! - Port Ctrl+C conversation-viewer dismissal and cross-extension consumption of settled subagent results while preserving local spawn and ownership safeguards.
8
+
3
9
  ## 0.16.0
4
10
 
5
11
  ### Minor Changes
@@ -49,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
49
55
 
50
56
  ### Fixed
51
57
 
58
+ - **Ctrl+C closes the conversation viewer** when it is not composing a steering message.
59
+ - **Cross-extension callers can consume settled RPC-spawned results** with `subagents:rpc:consume`, suppressing a duplicate completion notification after another extension has already shown the result.
52
60
  - **One malformed agent file no longer aborts extension activation** ([#212](https://github.com/tintinweb/pi-subagents/issues/212) — thanks [@daromaj](https://github.com/daromaj)). Unreadable and unparseable files are skipped with a path-specific warning, including the earlier source that remains active when a broken file was an override.
53
61
 
54
62
  ## [0.15.1] - 2026-08-10
@@ -32,7 +32,7 @@ Spawn specialized agents that run in isolated sessions — each with its own too
32
32
  - **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
33
33
  - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
34
34
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
35
- - **Cross-extension RPC** — other pi extensions can spawn and stop subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
35
+ - **Cross-extension RPC** — other pi extensions can spawn, stop, and consume subagent results via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
36
36
  - **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
37
37
  - **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
38
38
 
@@ -520,7 +520,7 @@ Listen for `subagents:ready` to know when RPC handlers are available:
520
520
 
521
521
  ```typescript
522
522
  pi.events.on("subagents:ready", () => {
523
- // RPC handlers are registered — safe to call ping/spawn/stop
523
+ // RPC handlers are registered — safe to call ping/spawn/stop/consume
524
524
  })
525
525
  ```
526
526
 
@@ -581,6 +581,19 @@ const unsub = pi.events.on(`subagents:rpc:stop:reply:${requestId}`, (reply) => {
581
581
  pi.events.emit("subagents:rpc:stop", { requestId, agentId: "agent-id-here" })
582
582
  ```
583
583
 
584
+ ### Consume
585
+
586
+ Mark a settled agent result as already shown so its completion notification is suppressed:
587
+
588
+ ```typescript
589
+ pi.events.emit("subagents:rpc:consume", {
590
+ requestId: crypto.randomUUID(),
591
+ agentId: "agent-id-here",
592
+ })
593
+ ```
594
+
595
+ This is the event-bus equivalent of `get_subagent_result` consuming a result. Running, queued, unknown, and nested agents are refused; the channel is additive and remains outside the protocol-version handshake.
596
+
584
597
  Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
585
598
 
586
599
  ## Persistent Agent Memory
@@ -677,7 +690,7 @@ src/
677
690
  agent-color.ts # Claude Code/Agency Agents name color parsing and badge rendering
678
691
  agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
679
692
  agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
680
- cross-extension-rpc.ts # RPC handlers for cross-extension spawn/ping via pi.events
693
+ cross-extension-rpc.ts # RPC handlers for cross-extension spawn/stop/consume via pi.events
681
694
  group-join.ts # Group join manager: batched completion notifications with timeout
682
695
  custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, and global agents
683
696
  memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-subagents",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Claude Code-style autonomous subagents for Pi, with HerbertGao-maintained UI extensions.",
5
5
  "keywords": [
6
6
  "agent",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cross-extension RPC handlers for the subagents extension.
3
3
  *
4
- * Exposes ping, spawn, and stop RPCs over the pi.events event bus,
4
+ * Exposes ping, spawn, stop, and consume RPCs over the pi.events event bus,
5
5
  * using per-request scoped reply channels.
6
6
  *
7
7
  * Reply envelope follows pi-mono convention:
@@ -25,7 +25,7 @@ export type RpcReply<T = void> =
25
25
  /** RPC protocol version — bumped when the envelope or method contracts change. */
26
26
  export const PROTOCOL_VERSION = 2
27
27
 
28
- /** Minimal AgentManager interface needed by the spawn/stop RPCs. */
28
+ /** Minimal AgentManager interface needed by the spawn/stop/consume RPCs. */
29
29
  export interface SpawnCapable {
30
30
  spawn(
31
31
  pi: unknown,
@@ -35,6 +35,7 @@ export interface SpawnCapable {
35
35
  options: any,
36
36
  ): string
37
37
  abort(id: string): boolean
38
+ consumeResult(id: string): boolean
38
39
  }
39
40
 
40
41
  export interface RpcDeps {
@@ -48,6 +49,7 @@ export interface RpcHandle {
48
49
  unsubPing: () => void
49
50
  unsubSpawn: () => void
50
51
  unsubStop: () => void
52
+ unsubConsume: () => void
51
53
  }
52
54
 
53
55
  /**
@@ -76,7 +78,7 @@ function handleRpc<P extends { requestId: string }>(
76
78
  }
77
79
 
78
80
  /**
79
- * Register ping, spawn, and stop RPC handlers on the event bus.
81
+ * Register ping, spawn, stop, and consume RPC handlers on the event bus.
80
82
  * Returns unsub functions for cleanup.
81
83
  */
82
84
  export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
@@ -130,5 +132,14 @@ export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
130
132
  },
131
133
  )
132
134
 
133
- return { unsubPing, unsubSpawn, unsubStop }
135
+ const unsubConsume = handleRpc<{
136
+ requestId: string
137
+ agentId: string
138
+ }>(events, "subagents:rpc:consume", ({ agentId }) => {
139
+ if (!manager.consumeResult(agentId)) {
140
+ throw new Error("Agent not found or still running")
141
+ }
142
+ })
143
+
144
+ return { unsubPing, unsubSpawn, unsubStop, unsubConsume }
134
145
  }
@@ -890,6 +890,20 @@ export default function (pi: ExtensionAPI) {
890
890
  const record = manager.getRecord(id)
891
891
  return !record?.parentAgentId && manager.abort(id)
892
892
  },
893
+ consumeResult: (id) => {
894
+ const record = manager.getRecord(id)
895
+ if (
896
+ !record ||
897
+ record.parentAgentId ||
898
+ record.status === "running" ||
899
+ record.status === "queued"
900
+ ) {
901
+ return false
902
+ }
903
+ record.resultConsumed = true
904
+ cancelNudge(record.id)
905
+ return true
906
+ },
893
907
  },
894
908
  })
895
909
  // Broadcast readiness so extensions loaded alongside us can discover us.
@@ -911,6 +925,7 @@ export default function (pi: ExtensionAPI) {
911
925
  rpcHandle?.unsubSpawn()
912
926
  rpcHandle?.unsubStop()
913
927
  rpcHandle?.unsubPing()
928
+ rpcHandle?.unsubConsume()
914
929
  rpcHandle = undefined
915
930
  currentCtx = undefined
916
931
  // Only release the global slot if this activation claimed it — a child
@@ -1068,6 +1083,7 @@ export default function (pi: ExtensionAPI) {
1068
1083
  // Grab UI context from first tool execution + clear lingering widget on new turn
1069
1084
  pi.on("tool_execution_start", async (_event, ctx) => {
1070
1085
  widget.setUICtx(ctx.ui as UICtx)
1086
+ // SAFETY: both UI adapters receive the same Pi ExtensionContext UI surface.
1071
1087
  fleet.setUICtx(ctx.ui as unknown as FleetUICtx)
1072
1088
  widget.onTurnStart()
1073
1089
  })
@@ -94,7 +94,11 @@ export class ConversationViewer implements Component {
94
94
  return
95
95
  }
96
96
 
97
- if (matchesKey(data, "escape") || matchesKey(data, "q")) {
97
+ if (
98
+ matchesKey(data, "escape") ||
99
+ matchesKey(data, "ctrl+c") ||
100
+ matchesKey(data, "q")
101
+ ) {
98
102
  this.closed = true
99
103
  this.done(undefined)
100
104
  return