@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
@@ -3,31 +3,17 @@ import path from "node:path"
3
3
  import {
4
4
  getAgentDir,
5
5
  type ExtensionAPI,
6
+ type ExtensionCommandContext,
6
7
  type ExtensionContext,
7
8
  } from "@earendil-works/pi-coding-agent"
8
9
 
9
10
  type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0]
10
-
11
- type PreferredThinkingConfig = Record<string, unknown> & {
12
- preferredThinking?: Record<string, unknown>
11
+ type PiSettings = Record<string, unknown> & {
12
+ enabledModels?: unknown
13
13
  }
14
14
 
15
- const CONFIG_PATH = path.join(
16
- getAgentDir(),
17
- "extensions",
18
- "pi-preferred-thinking.json",
19
- )
20
- const CONFIG_DIR = path.dirname(CONFIG_PATH)
15
+ const SETTINGS_PATH = path.join(getAgentDir(), "settings.json")
21
16
  const UNSET_OPTION = "unset"
22
- const VALID_THINKING_LEVELS = new Set<ThinkingLevel>([
23
- "off",
24
- "minimal",
25
- "low",
26
- "medium",
27
- "high",
28
- "xhigh",
29
- "max",
30
- ])
31
17
  const THINKING_LEVEL_OPTIONS: readonly ThinkingLevel[] = [
32
18
  "off",
33
19
  "minimal",
@@ -37,137 +23,206 @@ const THINKING_LEVEL_OPTIONS: readonly ThinkingLevel[] = [
37
23
  "xhigh",
38
24
  "max",
39
25
  ]
26
+ const HINT_WIDGET_KEY = "pi-preferred-thinking"
27
+ const HINT_DURATION_MS = 3_000
28
+
29
+ let hintTimer: ReturnType<typeof setTimeout> | undefined
30
+ const nudgedModels = new Set<string>()
40
31
 
41
32
  function getModelKey(provider: string, modelId: string): string {
42
33
  return `${provider}/${modelId}`
43
34
  }
44
35
 
45
- function readConfig(): PreferredThinkingConfig {
36
+ function readSettings(): PiSettings {
37
+ const content = readFileSync(SETTINGS_PATH, "utf8")
38
+ let settings: unknown
46
39
  try {
47
- const content = readFileSync(CONFIG_PATH, "utf-8")
48
- const config = JSON.parse(content) as unknown
49
- return config && typeof config === "object" && !Array.isArray(config)
50
- ? (config as PreferredThinkingConfig)
51
- : {}
52
- } catch (error) {
53
- if (error instanceof SyntaxError) throw error
40
+ settings = JSON.parse(content)
41
+ } catch {
54
42
  return {}
55
43
  }
44
+ return settings && typeof settings === "object" && !Array.isArray(settings)
45
+ ? (settings as PiSettings)
46
+ : {}
56
47
  }
57
48
 
58
- function readPreferredThinking(): Readonly<Record<string, ThinkingLevel>> {
59
- try {
60
- const config = readConfig()
61
- const configured = config.preferredThinking
62
- if (!configured || typeof configured !== "object") return {}
63
-
64
- const preferredThinking: Record<string, ThinkingLevel> = {}
65
- for (const [modelKey, level] of Object.entries(configured)) {
66
- if (
67
- typeof level === "string" &&
68
- VALID_THINKING_LEVELS.has(level as ThinkingLevel)
69
- ) {
70
- preferredThinking[modelKey] = level as ThinkingLevel
71
- }
72
- }
49
+ function getEnabledModels(settings: PiSettings): string[] {
50
+ return Array.isArray(settings.enabledModels)
51
+ ? settings.enabledModels.filter(
52
+ (entry): entry is string => typeof entry === "string",
53
+ )
54
+ : []
55
+ }
73
56
 
74
- return preferredThinking
75
- } catch {
76
- return {}
77
- }
57
+ function isExactModelEntry(entry: string, modelKey: string): boolean {
58
+ return (
59
+ entry === modelKey ||
60
+ THINKING_LEVEL_OPTIONS.some((level) => entry === `${modelKey}:${level}`)
61
+ )
78
62
  }
79
63
 
80
- function savePreferredThinking(
64
+ function saveThinkingLevel(
81
65
  modelKey: string,
82
66
  level: ThinkingLevel | undefined,
83
67
  ): void {
84
- const config = readConfig()
85
- const configured = config.preferredThinking
86
- const preferredThinking =
87
- configured && typeof configured === "object" && !Array.isArray(configured)
88
- ? { ...configured }
89
- : {}
68
+ const settings = readSettings()
69
+ if (!Array.isArray(settings.enabledModels)) {
70
+ throw new Error(
71
+ "Pi enabledModels is not configured. Add the model with /scoped-models first.",
72
+ )
73
+ }
90
74
 
91
- if (level) {
92
- preferredThinking[modelKey] = level
93
- } else {
94
- delete preferredThinking[modelKey]
75
+ const entries = getEnabledModels(settings)
76
+ const exactIndex = entries.findIndex((entry) =>
77
+ isExactModelEntry(entry, modelKey),
78
+ )
79
+ const nextEntries = entries.filter(
80
+ (entry) => !isExactModelEntry(entry, modelKey),
81
+ )
82
+ const replacement = level
83
+ ? `${modelKey}:${level}`
84
+ : exactIndex >= 0
85
+ ? modelKey
86
+ : undefined
87
+
88
+ if (replacement) {
89
+ const insertAt =
90
+ exactIndex < 0 ? 0 : Math.min(exactIndex, nextEntries.length)
91
+ nextEntries.splice(insertAt, 0, replacement)
95
92
  }
96
93
 
97
- config.preferredThinking = preferredThinking
98
- mkdirSync(CONFIG_DIR, { recursive: true })
99
- writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8")
94
+ settings.enabledModels = nextEntries
95
+ mkdirSync(path.dirname(SETTINGS_PATH), { recursive: true })
96
+ writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, "utf8")
97
+ }
98
+
99
+ function getPinnedThinkingLevel(
100
+ ctx: ExtensionContext,
101
+ modelKey: string,
102
+ ): ThinkingLevel | undefined {
103
+ const scopedModel = ctx.scopedModels.find(
104
+ (entry) => getModelKey(entry.model.provider, entry.model.id) === modelKey,
105
+ )
106
+ if (scopedModel?.thinkingLevel !== undefined) {
107
+ return scopedModel.thinkingLevel
108
+ }
109
+
110
+ try {
111
+ const entry = getEnabledModels(readSettings()).find((candidate) =>
112
+ candidate.startsWith(`${modelKey}:`),
113
+ )
114
+ const level = entry?.slice(modelKey.length + 1) as ThinkingLevel | undefined
115
+ return level && THINKING_LEVEL_OPTIONS.includes(level) ? level : undefined
116
+ } catch {
117
+ return undefined
118
+ }
119
+ }
120
+
121
+ function clearHint(ctx: ExtensionContext): void {
122
+ if (hintTimer) {
123
+ clearTimeout(hintTimer)
124
+ hintTimer = undefined
125
+ }
126
+ ctx.ui.setWidget(HINT_WIDGET_KEY, undefined)
127
+ }
128
+
129
+ function showHint(ctx: ExtensionContext, modelKey: string): void {
130
+ if (!ctx.hasUI) return
131
+ clearHint(ctx)
132
+ ctx.ui.setWidget(
133
+ HINT_WIDGET_KEY,
134
+ [
135
+ `No saved thinking level for ${modelKey}.`,
136
+ "Run /preferred-thinking to set one.",
137
+ ],
138
+ { placement: "aboveEditor" },
139
+ )
140
+ hintTimer = setTimeout(() => {
141
+ ctx.ui.setWidget(HINT_WIDGET_KEY, undefined)
142
+ hintTimer = undefined
143
+ }, HINT_DURATION_MS)
100
144
  }
101
145
 
102
146
  function applyPreferredThinking(
103
147
  pi: ExtensionAPI,
104
- provider: string,
105
- modelId: string,
148
+ ctx: ExtensionContext,
149
+ model: NonNullable<ExtensionContext["model"]>,
106
150
  ): void {
107
- const modelKey = getModelKey(provider, modelId)
108
- const preferredThinking = readPreferredThinking()[modelKey]
109
- if (!preferredThinking) return
151
+ const modelKey = getModelKey(model.provider, model.id)
152
+ const thinkingLevel = getPinnedThinkingLevel(ctx, modelKey)
153
+
154
+ if (thinkingLevel !== undefined) {
155
+ clearHint(ctx)
156
+ if (pi.getThinkingLevel() !== thinkingLevel) {
157
+ pi.setThinkingLevel(thinkingLevel)
158
+ }
159
+ return
160
+ }
110
161
 
111
- if (pi.getThinkingLevel() !== preferredThinking) {
112
- pi.setThinkingLevel(preferredThinking)
162
+ if (model.reasoning && !nudgedModels.has(modelKey)) {
163
+ nudgedModels.add(modelKey)
164
+ showHint(ctx, modelKey)
113
165
  }
114
166
  }
115
167
 
116
168
  async function configurePreferredThinking(
117
169
  pi: ExtensionAPI,
118
- ctx: ExtensionContext,
170
+ ctx: ExtensionCommandContext,
119
171
  ): Promise<void> {
120
- if (!ctx.model) {
172
+ const model = ctx.model
173
+ if (!model) {
121
174
  ctx.ui.notify("No current model selected.", "error")
122
175
  return
123
176
  }
124
177
 
125
- const modelKey = getModelKey(ctx.model.provider, ctx.model.id)
126
- const current = readPreferredThinking()[modelKey] ?? UNSET_OPTION
178
+ const modelKey = getModelKey(model.provider, model.id)
179
+ const current = getPinnedThinkingLevel(ctx, modelKey) ?? UNSET_OPTION
127
180
  const selected = await ctx.ui.select(
128
181
  `Preferred thinking for ${modelKey} (current: ${current})`,
129
182
  [...THINKING_LEVEL_OPTIONS, UNSET_OPTION],
130
183
  )
131
184
  if (selected === undefined) return
132
185
 
133
- const level = VALID_THINKING_LEVELS.has(selected as ThinkingLevel)
186
+ const level = THINKING_LEVEL_OPTIONS.includes(selected as ThinkingLevel)
134
187
  ? (selected as ThinkingLevel)
135
188
  : undefined
136
189
 
137
190
  try {
138
- savePreferredThinking(modelKey, level)
191
+ saveThinkingLevel(modelKey, level)
139
192
  } catch (error) {
140
- const reason =
141
- error instanceof SyntaxError ? "invalid JSON" : "write failed"
142
- ctx.ui.notify(
143
- `Could not update preferred thinking config: ${reason}.`,
144
- "error",
145
- )
193
+ const reason = error instanceof SyntaxError ? "invalid JSON" : String(error)
194
+ ctx.ui.notify(`Could not update Pi settings: ${reason}`, "error")
146
195
  return
147
196
  }
148
197
 
149
198
  if (level) {
150
199
  pi.setThinkingLevel(level)
151
- ctx.ui.notify(`Preferred thinking for ${modelKey} set to ${level}.`, "info")
200
+ ctx.ui.notify(`Thinking for ${modelKey} set to ${level}.`, "info")
152
201
  } else {
153
- ctx.ui.notify(`Preferred thinking for ${modelKey} unset.`, "info")
202
+ ctx.ui.notify(`Thinking pin for ${modelKey} removed.`, "info")
154
203
  }
204
+
205
+ await ctx.reload()
155
206
  }
156
207
 
157
208
  export default function (pi: ExtensionAPI): void {
158
209
  pi.registerCommand("preferred-thinking", {
159
- description: "set the preferred thinking level for the current model",
210
+ description: "set the native thinking level for the current model",
160
211
  handler: async (_args, ctx) => {
161
212
  await configurePreferredThinking(pi, ctx)
162
213
  },
163
214
  })
164
215
 
165
216
  pi.on("session_start", async (_event, ctx) => {
166
- if (!ctx.model) return
167
- applyPreferredThinking(pi, ctx.model.provider, ctx.model.id)
217
+ nudgedModels.clear()
218
+ if (ctx.model) applyPreferredThinking(pi, ctx, ctx.model)
219
+ })
220
+
221
+ pi.on("model_select", async (event, ctx) => {
222
+ applyPreferredThinking(pi, ctx, event.model)
168
223
  })
169
224
 
170
- pi.on("model_select", async (event) => {
171
- applyPreferredThinking(pi, event.model.provider, event.model.id)
225
+ pi.on("session_shutdown", async (_event, ctx) => {
226
+ clearHint(ctx)
172
227
  })
173
228
  }
@@ -1,5 +1,17 @@
1
1
  # @tifan/pi-recap
2
2
 
3
+ ## 0.4.5
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)! - Use Pi 0.84.2 model-registry completion and authentication APIs for recap generation and model selection.
8
+
9
+ ## 0.4.5
10
+
11
+ ### Patch Changes
12
+
13
+ - [`12ef66c`](https://github.com/tifandotme/pi-extensions/commit/12ef66c7ec6efe04202c91f70e20cd97e9c167f3) Thanks [@tifandotme](https://github.com/tifandotme)! - Require Pi 0.84.2 or newer for recap generation.
14
+
3
15
  ## 0.4.4
4
16
 
5
17
  ### Patch Changes
@@ -14,6 +14,8 @@ Re-enter a session without rereading the transcript.
14
14
  pi install npm:@herbertgao/pi-recap
15
15
  ```
16
16
 
17
+ This package requires Pi 0.84.2 or newer.
18
+
17
19
  ## How it works
18
20
 
19
21
  - `/recap` generates a fresh, goal-first recap and shows it above the editor.
@@ -36,7 +38,7 @@ A good recap should answer "what was I trying to do here?". For example:
36
38
 
37
39
  ## Configuration
38
40
 
39
- Out of the box, `pi-recap` uses this default model: `openai-codex/gpt-5.4-mini`.
41
+ Out of the box, `pi-recap` uses this default model: `openai-codex/gpt-5.6-luna`.
40
42
 
41
43
  Run `/recap config` to choose a different model.
42
44
 
@@ -48,13 +50,13 @@ You can also edit `~/.config/pi/extensions/pi-recap.json` manually:
48
50
 
49
51
  ```json
50
52
  {
51
- "model": "openai-codex/gpt-5.4-mini"
53
+ "model": "openai-codex/gpt-5.6-luna"
52
54
  }
53
55
  ```
54
56
 
55
57
  ## Release notes
56
58
 
57
- See [CHANGELOG.md](CHANGELOG.md)
59
+ See [CHANGELOG.md](CHANGELOG.md).
58
60
 
59
61
  ## License
60
62
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-recap",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "One-line session recap on demand or after you have been away.",
5
5
  "keywords": [
6
6
  "pi",
@@ -32,6 +32,12 @@
32
32
  "access": "public",
33
33
  "provenance": true
34
34
  },
35
+ "peerDependencies": {
36
+ "@earendil-works/pi-agent-core": ">=0.84.2",
37
+ "@earendil-works/pi-ai": ">=0.84.2",
38
+ "@earendil-works/pi-coding-agent": ">=0.84.2",
39
+ "@earendil-works/pi-tui": ">=0.84.2"
40
+ },
35
41
  "engines": {
36
42
  "node": ">=20"
37
43
  },
@@ -42,8 +48,8 @@
42
48
  },
43
49
  "x-upstream": {
44
50
  "package": "@tifan/pi-recap",
45
- "version": "0.4.4",
51
+ "version": "0.4.5",
46
52
  "repository": "https://github.com/tifandotme/pi-extensions",
47
- "commit": "460d580"
53
+ "commit": "b39d1e6359c78718cbf8fe5c74c82b89065e9a0b"
48
54
  }
49
55
  }
@@ -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 { AutocompleteItem } from "@earendil-works/pi-tui"
4
4
  import type {
5
5
  ExtensionAPI,
@@ -276,22 +276,18 @@ async function generateRecap(
276
276
  return
277
277
  }
278
278
 
279
- const auth = modelAuth.auth
280
-
281
279
  const abortController = new AbortController()
282
280
  abortPendingGeneration(state)
283
281
  state.abortController = abortController
284
282
 
285
283
  try {
286
- const response = await complete(
287
- auth.model,
284
+ const response = await ctx.modelRegistry.complete(
285
+ modelAuth.auth,
288
286
  {
289
287
  systemPrompt: RECAP_SYSTEM_PROMPT,
290
288
  messages: [buildPrompt(messages)],
291
289
  },
292
290
  {
293
- apiKey: auth.apiKey,
294
- ...(auth.headers ? { headers: auth.headers } : {}),
295
291
  maxTokens: RECAP_MAX_TOKENS,
296
292
  maxRetries: 0,
297
293
  cacheRetention: "none",
@@ -13,7 +13,6 @@ import {
13
13
  type RecapModelPreference,
14
14
  } from "./models.js"
15
15
 
16
- const USE_DEFAULT_VALUE = "__pi-recap-default__"
17
16
  const MAX_VISIBLE_ITEMS = 10
18
17
 
19
18
  export type ModelPickerResult =
@@ -22,7 +21,6 @@ export type ModelPickerResult =
22
21
  | { action: "cancel" }
23
22
 
24
23
  interface ModelPickerItem {
25
- readonly value: string
26
24
  readonly label: string
27
25
  readonly description?: string
28
26
  readonly model?: RecapModelPreference
@@ -43,12 +41,10 @@ class RecapModelPicker implements Component, Focusable {
43
41
  this.theme = theme
44
42
  this.items = [
45
43
  {
46
- value: USE_DEFAULT_VALUE,
47
44
  label: "Use default",
48
45
  description: formatRecapModelKey(DEFAULT_RECAP_MODEL),
49
46
  },
50
47
  ...models.map((model) => ({
51
- value: formatRecapModelKey(model),
52
48
  label: formatRecapModelKey(model),
53
49
  model,
54
50
  })),
@@ -90,11 +86,7 @@ class RecapModelPicker implements Component, Focusable {
90
86
  } else {
91
87
  for (const [index, item] of visibleItems.entries()) {
92
88
  lines.push(
93
- this.renderItem(
94
- item,
95
- startIndex + index === this.selectedIndex,
96
- width,
97
- ),
89
+ this.renderItem(item, startIndex + index === this.selectedIndex),
98
90
  )
99
91
  }
100
92
  }
@@ -146,11 +138,7 @@ class RecapModelPicker implements Component, Focusable {
146
138
  }
147
139
  }
148
140
 
149
- private renderItem(
150
- item: ModelPickerItem,
151
- isSelected: boolean,
152
- width: number,
153
- ): string {
141
+ private renderItem(item: ModelPickerItem, isSelected: boolean): string {
154
142
  const prefix = isSelected ? "→ " : " "
155
143
  const label = isSelected
156
144
  ? this.theme.fg("accent", item.label)
@@ -158,7 +146,7 @@ class RecapModelPicker implements Component, Focusable {
158
146
  const description = item.description
159
147
  ? this.theme.fg("dim", ` ${item.description}`)
160
148
  : ""
161
- return truncateToWidth(`${prefix}${label}${description}`, width)
149
+ return `${prefix}${label}${description}`
162
150
  }
163
151
 
164
152
  private moveSelection(delta: number): void {
@@ -171,14 +159,13 @@ class RecapModelPicker implements Component, Focusable {
171
159
  const item = this.getFilteredItems()[this.selectedIndex]
172
160
  if (!item) return
173
161
 
174
- if (item.value === USE_DEFAULT_VALUE) {
162
+ const model = item.model
163
+ if (!model) {
175
164
  this.onDone({ action: "default" })
176
165
  return
177
166
  }
178
167
 
179
- if (item.model) {
180
- this.onDone({ action: "model", model: item.model })
181
- }
168
+ this.onDone({ action: "model", model })
182
169
  }
183
170
 
184
171
  private getFilter(): string {
@@ -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,
@@ -20,12 +20,6 @@ export interface RecapModelPreference {
20
20
  readonly id: string
21
21
  }
22
22
 
23
- export interface FastModelAuth {
24
- readonly model: Model<Api>
25
- readonly apiKey: string
26
- readonly headers: ProviderHeaders | undefined
27
- }
28
-
29
23
  export type RecapModelConfig =
30
24
  | { readonly kind: "missing" }
31
25
  | { readonly kind: "invalid" }
@@ -34,7 +28,7 @@ export type RecapModelConfig =
34
28
  export type ResolvedRecapModelAuth =
35
29
  | {
36
30
  readonly status: "ok"
37
- readonly auth: FastModelAuth
31
+ readonly auth: Model<Api>
38
32
  readonly source: "configured" | "default"
39
33
  }
40
34
  | { readonly status: "invalid-config" }
@@ -66,8 +60,8 @@ export function formatRecapModelKey({
66
60
  return `${provider}/${id}`
67
61
  }
68
62
 
69
- export function formatAuthModelKey(auth: FastModelAuth): string {
70
- return `${auth.model.provider}/${auth.model.id}`
63
+ export function formatAuthModelKey(auth: Model<Api>): string {
64
+ return `${auth.provider}/${auth.id}`
71
65
  }
72
66
 
73
67
  export function parseModelSpec(
@@ -127,21 +121,15 @@ export function resolveInitialModelConfig(): RecapModelConfig {
127
121
  async function getModelAuth(
128
122
  ctx: ExtensionContext,
129
123
  modelPreference: RecapModelPreference,
130
- ): Promise<FastModelAuth | undefined> {
124
+ ): Promise<Model<Api> | undefined> {
131
125
  const model = ctx.modelRegistry.find(
132
126
  modelPreference.provider,
133
127
  modelPreference.id,
134
128
  )
135
129
  if (!model) return undefined
136
130
 
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
131
+ const auth = await ctx.modelRegistry.getProviderAuth(model.provider)
132
+ return auth?.auth.apiKey ? model : undefined
145
133
  }
146
134
 
147
135
  export async function getRecapModelAuth(
@@ -181,8 +169,8 @@ export async function getAuthenticatedTextModelPreferences(
181
169
  .filter((model) => model.input.includes("text"))
182
170
  const authenticatedModels = await Promise.all(
183
171
  models.map(async (model) => {
184
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model)
185
- return auth.ok && auth.apiKey ? toModelPreference(model) : undefined
172
+ const auth = await ctx.modelRegistry.getProviderAuth(model.provider)
173
+ return auth?.auth.apiKey ? toModelPreference(model) : undefined
186
174
  }),
187
175
  )
188
176
 
@@ -82,7 +82,7 @@ class PiRecapLine implements Component {
82
82
  const labelWidth = visibleWidth(LABEL)
83
83
  const label = this.renderLabel()
84
84
 
85
- if (width < MIN_RECAP_WIDTH || width <= labelWidth) {
85
+ if (width < MIN_RECAP_WIDTH) {
86
86
  return [
87
87
  truncateToWidth(this.theme.fg("muted", `${LABEL}${this.recap}`), width),
88
88
  ]
@@ -0,0 +1,49 @@
1
+ # @tifan/pi-rename
2
+
3
+ ## 0.5.0
4
+
5
+ ### Minor 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)! - Use Pi 0.84.2 model-registry APIs, show rename progress, and synchronize generated names across Herdr panes and tabs.
8
+
9
+ ## 0.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`b2ea184`](https://github.com/tifandotme/pi-extensions/commit/b2ea1841e99243aa4773bd5c33f43c40248859db) Thanks [@tifandotme](https://github.com/tifandotme)! - Update Herdr synchronization to rename the current pane in split tabs and both the pane and tab in single-pane tabs.
14
+
15
+ ## 0.4.3
16
+
17
+ ### Patch Changes
18
+
19
+ - [`e8709bc`](https://github.com/tifandotme/pi-extensions/commit/e8709bc388aa8e094918ec465cfab511358f18ff) Thanks [@tifandotme](https://github.com/tifandotme)! - Require Pi 0.84.2 or newer and show progress while `/rename` generates a session name.
20
+
21
+ ## 0.4.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [`b27ba8a`](https://github.com/tifandotme/pi-extensions/commit/b27ba8aef49ea00879cec87d913840f39e02b19e) Thanks [@tifandotme](https://github.com/tifandotme)! - Use gpt-5.6-luna by default and keep Herdr tab names after quitting Pi.
26
+
27
+ ## 0.4.1
28
+
29
+ ### Patch Changes
30
+
31
+ - [`ba259a0`](https://github.com/tifandotme/pi-extensions/commit/ba259a0c990563e4dad3443371cc2ed31d122cd0) Thanks [@tifandotme](https://github.com/tifandotme)! - Keep model-powered handoffs and session names compatible with Pi 0.80.
32
+
33
+ ## 0.4.0
34
+
35
+ ### Minor Changes
36
+
37
+ - [`416ac2f`](https://github.com/tifandotme/pi-extensions/commit/416ac2fecc6aa731c23ae895220bac87317152e4) Thanks [@tifandotme](https://github.com/tifandotme)! - Expose the rename naming utilities through the package exports for reuse.
38
+
39
+ ## 0.3.0
40
+
41
+ ### Minor Changes
42
+
43
+ - [`57565bd`](https://github.com/tifandotme/pi-extensions/commit/57565bd14cf86cb51ca5ba5c6d937568fc4e251f) Thanks [@tifandotme](https://github.com/tifandotme)! - Sync Herdr tab labels with saved pi session names on resume, and reset matching tab labels to the tab number when pi quits.
44
+
45
+ ## 0.2.0
46
+
47
+ ### Minor Changes
48
+
49
+ - [`392f57c`](https://github.com/tifandotme/pi-extensions/commit/392f57c3dd62858d41f462801ef53773596e3f96) Thanks [@tifandotme](https://github.com/tifandotme)! - Add `/rename` to generate hyphen-separated session names, configure the rename model, and mirror names to the current Herdr tab.