pi-cc-extensions 0.8.30 → 0.8.32

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 (53) hide show
  1. package/README.en.md +164 -174
  2. package/README.md +113 -115
  3. package/extensions/claude-code-style.ts +371 -208
  4. package/extensions/compact-thinking.ts +58 -0
  5. package/extensions/fixed-editor.ts +185 -0
  6. package/extensions/index.ts +10 -4
  7. package/extensions/subagent-notification.ts +30 -23
  8. package/node_modules/@tifan/pi-fixed-editor/README.md +36 -0
  9. package/node_modules/@tifan/pi-fixed-editor/package.json +46 -0
  10. package/node_modules/@tifan/pi-fixed-editor/src/cluster.ts +113 -0
  11. package/node_modules/@tifan/pi-fixed-editor/src/index.ts +259 -0
  12. package/node_modules/@tifan/pi-fixed-editor/src/terminal-split.ts +1337 -0
  13. package/package.json +9 -5
  14. package/extensions/ask-user-question/LICENSE +0 -21
  15. package/extensions/ask-user-question/README.md +0 -63
  16. package/extensions/ask-user-question/ask-user-question.ts +0 -266
  17. package/extensions/ask-user-question/config.ts +0 -79
  18. package/extensions/ask-user-question/events.ts +0 -55
  19. package/extensions/ask-user-question/index.ts +0 -17
  20. package/extensions/ask-user-question/reconcile.ts +0 -47
  21. package/extensions/ask-user-question/rpc-fallback.ts +0 -172
  22. package/extensions/ask-user-question/state/build-questionnaire.ts +0 -314
  23. package/extensions/ask-user-question/state/dialog-height.ts +0 -16
  24. package/extensions/ask-user-question/state/key-router.ts +0 -291
  25. package/extensions/ask-user-question/state/questionnaire-session.ts +0 -193
  26. package/extensions/ask-user-question/state/row-intent.ts +0 -143
  27. package/extensions/ask-user-question/state/selectors/contract.ts +0 -24
  28. package/extensions/ask-user-question/state/selectors/derivations.ts +0 -40
  29. package/extensions/ask-user-question/state/selectors/focus.ts +0 -17
  30. package/extensions/ask-user-question/state/selectors/projections.ts +0 -102
  31. package/extensions/ask-user-question/state/state-reducer.ts +0 -299
  32. package/extensions/ask-user-question/state/state.ts +0 -49
  33. package/extensions/ask-user-question/tool/format-answer.ts +0 -29
  34. package/extensions/ask-user-question/tool/response-envelope.ts +0 -50
  35. package/extensions/ask-user-question/tool/types.ts +0 -149
  36. package/extensions/ask-user-question/tool/validate-questionnaire.ts +0 -64
  37. package/extensions/ask-user-question/view/component-binding.ts +0 -45
  38. package/extensions/ask-user-question/view/components/inline-input.ts +0 -109
  39. package/extensions/ask-user-question/view/components/multi-select-view.ts +0 -198
  40. package/extensions/ask-user-question/view/components/option-list-view.ts +0 -77
  41. package/extensions/ask-user-question/view/components/preview/markdown-content-cache.ts +0 -76
  42. package/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +0 -114
  43. package/extensions/ask-user-question/view/components/preview/preview-box-renderer.ts +0 -86
  44. package/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +0 -219
  45. package/extensions/ask-user-question/view/components/preview/preview-pane.ts +0 -230
  46. package/extensions/ask-user-question/view/components/submit-picker.ts +0 -66
  47. package/extensions/ask-user-question/view/components/tab-bar.ts +0 -57
  48. package/extensions/ask-user-question/view/components/wrapping-select.ts +0 -307
  49. package/extensions/ask-user-question/view/dialog-builder.ts +0 -225
  50. package/extensions/ask-user-question/view/props-adapter.ts +0 -123
  51. package/extensions/ask-user-question/view/stateful-view.ts +0 -24
  52. package/extensions/ask-user-question/view/tab-components.ts +0 -16
  53. package/extensions/ask-user-question/view/tab-content-strategy.ts +0 -264
@@ -0,0 +1,259 @@
1
+ import {
2
+ copyToClipboard,
3
+ type ExtensionAPI,
4
+ type ExtensionContext,
5
+ } from "@earendil-works/pi-coding-agent"
6
+ import type { Component } from "@earendil-works/pi-tui"
7
+ import { visibleWidth } from "@earendil-works/pi-tui"
8
+ import { renderFixedEditorCluster } from "./cluster.js"
9
+ import {
10
+ emergencyTerminalModeReset,
11
+ TerminalSplitCompositor,
12
+ type TerminalLike,
13
+ } from "./terminal-split.js"
14
+
15
+ const WIDGET_KEY = "pi-fixed-editor-probe"
16
+ const WARNING_MESSAGE = "pi-fixed-editor: unsupported Pi TUI layout"
17
+
18
+ interface ContainerLike extends Component {
19
+ children: Component[]
20
+ }
21
+
22
+ interface TuiLike {
23
+ children: Component[]
24
+ terminal?: TerminalLike
25
+ focusedComponent?: Component | null
26
+ requestRender: (force?: boolean) => void
27
+ getShowHardwareCursor?: () => boolean
28
+ }
29
+
30
+ interface ContainerMatch {
31
+ container: ContainerLike
32
+ index: number
33
+ }
34
+
35
+ let compositor: TerminalSplitCompositor | null = null
36
+ let isInstalled = false
37
+ let didWarnUnsupported = false
38
+ let fixedStatusContainer: Component | null = null
39
+ let fixedWidgetContainerAbove: Component | null = null
40
+ let fixedEditorContainer: Component | null = null
41
+ let fixedWidgetContainerBelow: Component | null = null
42
+ let fixedFooterContainer: Component | null = null
43
+
44
+ class ProbeComponent implements Component {
45
+ private readonly install: () => void
46
+ private hasQueuedInstall = false
47
+
48
+ constructor(onInstall: () => void) {
49
+ this.install = onInstall
50
+ }
51
+
52
+ render(): string[] {
53
+ if (!this.hasQueuedInstall) {
54
+ this.hasQueuedInstall = true
55
+ queueMicrotask(this.install)
56
+ }
57
+ return []
58
+ }
59
+
60
+ invalidate(): void {
61
+ this.hasQueuedInstall = false
62
+ }
63
+ }
64
+
65
+ function isContainerLike(value: unknown): value is ContainerLike {
66
+ return (
67
+ typeof value === "object" &&
68
+ value !== null &&
69
+ Array.isArray(Reflect.get(value, "children")) &&
70
+ typeof Reflect.get(value, "render") === "function"
71
+ )
72
+ }
73
+
74
+ function isRenderable(value: unknown): value is Component {
75
+ return (
76
+ typeof value === "object" &&
77
+ value !== null &&
78
+ typeof Reflect.get(value, "render") === "function" &&
79
+ typeof Reflect.get(value, "invalidate") === "function"
80
+ )
81
+ }
82
+
83
+ function getTuiChildren(tui: TuiLike): Component[] {
84
+ return Array.isArray(tui.children) ? tui.children : []
85
+ }
86
+
87
+ function findContainerWithChild(
88
+ tui: TuiLike,
89
+ child: Component,
90
+ ): ContainerMatch | null {
91
+ const children = getTuiChildren(tui)
92
+ const index = children.findIndex(
93
+ (candidate) =>
94
+ isContainerLike(candidate) && candidate.children.includes(child),
95
+ )
96
+ if (index === -1) return null
97
+
98
+ const container = children[index]
99
+ return isContainerLike(container) ? { container, index } : null
100
+ }
101
+
102
+ function findEditorContainer(tui: TuiLike): ContainerMatch | null {
103
+ const focusedComponent = Reflect.get(tui, "focusedComponent")
104
+ if (isRenderable(focusedComponent)) {
105
+ const match = findContainerWithChild(tui, focusedComponent)
106
+ if (match) return match
107
+ }
108
+
109
+ const children = getTuiChildren(tui)
110
+ const index = children.findIndex((candidate) => {
111
+ if (!isContainerLike(candidate)) return false
112
+ return candidate.children.some((child) => {
113
+ return (
114
+ typeof Reflect.get(child, "getText") === "function" &&
115
+ typeof Reflect.get(child, "setText") === "function" &&
116
+ typeof Reflect.get(child, "handleInput") === "function"
117
+ )
118
+ })
119
+ })
120
+
121
+ if (index === -1) return null
122
+ const container = children[index]
123
+ return isContainerLike(container) ? { container, index } : null
124
+ }
125
+
126
+ function renderHidden(
127
+ activeCompositor: TerminalSplitCompositor,
128
+ component: Component | null,
129
+ width: number,
130
+ ): string[] {
131
+ if (!component) return []
132
+ return activeCompositor
133
+ .renderHidden(component, width)
134
+ .filter((line) => visibleWidth(line) > 0)
135
+ }
136
+
137
+ function resetContainers(): void {
138
+ fixedStatusContainer = null
139
+ fixedWidgetContainerAbove = null
140
+ fixedEditorContainer = null
141
+ fixedWidgetContainerBelow = null
142
+ fixedFooterContainer = null
143
+ }
144
+
145
+ function teardown(options?: { resetExtendedKeyboardModes?: boolean }): void {
146
+ const hadCompositor = compositor !== null
147
+ compositor?.dispose(options)
148
+ if (!hadCompositor && options?.resetExtendedKeyboardModes) {
149
+ try {
150
+ process.stdout.write(emergencyTerminalModeReset())
151
+ } catch {
152
+ // Exit cleanup cannot surface useful terminal write failures.
153
+ }
154
+ }
155
+ compositor = null
156
+ isInstalled = false
157
+ resetContainers()
158
+ }
159
+
160
+ function warnUnsupported(ctx: ExtensionContext): void {
161
+ if (didWarnUnsupported || ctx.mode !== "tui") return
162
+ didWarnUnsupported = true
163
+ ctx.ui.notify(WARNING_MESSAGE, "warning")
164
+ }
165
+
166
+ function install(ctx: ExtensionContext, tui: TuiLike): void {
167
+ if (isInstalled || compositor) return
168
+ if (ctx.mode !== "tui") return
169
+
170
+ const terminal = tui.terminal
171
+ if (!terminal || typeof terminal.write !== "function") {
172
+ warnUnsupported(ctx)
173
+ return
174
+ }
175
+
176
+ const editorMatch = findEditorContainer(tui)
177
+ if (!editorMatch) {
178
+ warnUnsupported(ctx)
179
+ return
180
+ }
181
+
182
+ const children = getTuiChildren(tui)
183
+ fixedStatusContainer = children[editorMatch.index - 2] ?? null
184
+ fixedWidgetContainerAbove = children[editorMatch.index - 1] ?? null
185
+ fixedEditorContainer = editorMatch.container
186
+ fixedWidgetContainerBelow = children[editorMatch.index + 1] ?? null
187
+ fixedFooterContainer = children[editorMatch.index + 2] ?? null
188
+
189
+ let nextCompositor: TerminalSplitCompositor
190
+ nextCompositor = new TerminalSplitCompositor({
191
+ tui,
192
+ terminal,
193
+ onCopySelection: (text) => {
194
+ void copyToClipboard(text)
195
+ },
196
+ getShowHardwareCursor: () =>
197
+ typeof tui.getShowHardwareCursor === "function" &&
198
+ tui.getShowHardwareCursor(),
199
+ renderCluster: (width, terminalRows) =>
200
+ renderFixedEditorCluster({
201
+ width,
202
+ terminalRows,
203
+ statusLines: renderHidden(nextCompositor, fixedStatusContainer, width),
204
+ aboveWidgetLines: renderHidden(
205
+ nextCompositor,
206
+ fixedWidgetContainerAbove,
207
+ width,
208
+ ),
209
+ editorLines: renderHidden(nextCompositor, fixedEditorContainer, width),
210
+ belowWidgetLines: renderHidden(
211
+ nextCompositor,
212
+ fixedWidgetContainerBelow,
213
+ width,
214
+ ),
215
+ footerLines: renderHidden(nextCompositor, fixedFooterContainer, width),
216
+ }),
217
+ })
218
+
219
+ compositor = nextCompositor
220
+ if (fixedStatusContainer) nextCompositor.hideRenderable(fixedStatusContainer)
221
+ if (fixedWidgetContainerAbove)
222
+ nextCompositor.hideRenderable(fixedWidgetContainerAbove)
223
+ if (fixedEditorContainer) nextCompositor.hideRenderable(fixedEditorContainer)
224
+ if (fixedWidgetContainerBelow)
225
+ nextCompositor.hideRenderable(fixedWidgetContainerBelow)
226
+ if (fixedFooterContainer) nextCompositor.hideRenderable(fixedFooterContainer)
227
+
228
+ try {
229
+ nextCompositor.install()
230
+ isInstalled = true
231
+ tui.requestRender(true)
232
+ } catch {
233
+ teardown({ resetExtendedKeyboardModes: true })
234
+ warnUnsupported(ctx)
235
+ }
236
+ }
237
+
238
+ export default function fixedEditor(pi: ExtensionAPI) {
239
+ pi.on("session_start", (_event, ctx) => {
240
+ didWarnUnsupported = false
241
+ if (ctx.mode !== "tui") return
242
+
243
+ ctx.ui.setWidget(
244
+ WIDGET_KEY,
245
+ (tui) => {
246
+ const tuiLike = tui as unknown as TuiLike
247
+ return new ProbeComponent(() => install(ctx, tuiLike))
248
+ },
249
+ { placement: "aboveEditor" },
250
+ )
251
+ })
252
+
253
+ pi.on("session_shutdown", (_event, ctx) => {
254
+ if (ctx.mode === "tui") {
255
+ ctx.ui.setWidget(WIDGET_KEY, undefined)
256
+ }
257
+ teardown({ resetExtendedKeyboardModes: true })
258
+ })
259
+ }