@mtayfur/opencode-prompt-enhancer 0.0.16 → 0.0.18

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.
@@ -0,0 +1,835 @@
1
+ import { createComponent as _$createComponent } from "@opentui/solid";
2
+ import { effect as _$effect } from "@opentui/solid";
3
+ import { use as _$use } from "@opentui/solid";
4
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
5
+ import { insertNode as _$insertNode } from "@opentui/solid";
6
+ import { setProp as _$setProp } from "@opentui/solid";
7
+ import { createElement as _$createElement } from "@opentui/solid";
8
+ /** @jsxImportSource @opentui/solid */
9
+
10
+ import { useTerminalDimensions } from "@opentui/solid";
11
+ import { Show, createMemo, createSignal } from "solid-js";
12
+ import { ENHANCER_SYSTEM_PROMPT } from "./enhancer-system-prompt";
13
+ const MAX_RECENT_MESSAGES = 3;
14
+ const MAX_CHANGED_FILES = 25;
15
+ const MAX_PROMPT_PREVIEW_LENGTH = 250;
16
+ const ENHANCEMENT_TIMEOUT_MS = 60_000;
17
+ const ENHANCEMENT_ANIMATION_INTERVAL_MS = 250;
18
+ const TOAST_DURATION_MS = 3_000;
19
+ const ENHANCEMENT_CANCELED_MESSAGE = "Prompt enhancement canceled.";
20
+ const ENHANCEMENT_ANIMATION_FRAMES = ["Enhancing prompt", "Enhancing prompt.", "Enhancing prompt..", "Enhancing prompt..."];
21
+ const DIALOG_TITLE = "Enhance Prompt";
22
+ const DIALOG_WIDTH_RATIO = 0.60;
23
+ const DIALOG_MAX_WIDTH = 120;
24
+ const DIALOG_MIN_WIDTH = 40;
25
+ const DIALOG_SCREEN_MARGIN_X = 4;
26
+ const DIALOG_SCREEN_MARGIN_Y = 4;
27
+ const DIALOG_HEIGHT = 24;
28
+ const DIALOG_PADDING_X = 2;
29
+ const DIALOG_PADDING_Y = 1;
30
+ const DIALOG_TEXTAREA_RESERVED_HEIGHT = 7;
31
+ const DIALOG_TEXTAREA_VERTICAL_PADDING = 2;
32
+ const DIALOG_BACKDROP_COLOR = "#000000";
33
+ const DIALOG_BACKDROP_OPACITY = 0.65;
34
+ const DIALOG_PLACEHOLDER = "Describe the task...";
35
+ const DIALOG_HINT = "Enter to enhance • Shift+Enter for newline • Esc to cancel";
36
+ const TOAST_TITLE = "Prompt enhancer";
37
+ function parseModelString(value) {
38
+ if (!value) return undefined;
39
+ const trimmed = value.trim();
40
+ const slash = trimmed.indexOf("/");
41
+ if (slash <= 0 || slash === trimmed.length - 1) return undefined;
42
+ return {
43
+ providerID: trimmed.slice(0, slash),
44
+ modelID: trimmed.slice(slash + 1)
45
+ };
46
+ }
47
+ function isSessionRoute(route) {
48
+ return route.name === "session";
49
+ }
50
+ function extractVisibleText(parts) {
51
+ return parts.filter(part => part.type === "text" && !part.ignored).map(part => part.text).join("").trim();
52
+ }
53
+ function resolveEnhancerModel(api, options) {
54
+ const override = typeof options?.model === "string" ? parseModelString(options.model) : undefined;
55
+ if (override) return override;
56
+ return parseModelString(api.state.config.small_model || api.state.config.model);
57
+ }
58
+ function clonePromptInfo(prompt) {
59
+ return {
60
+ input: prompt.input,
61
+ mode: prompt.mode,
62
+ parts: prompt.parts.map(part => ({
63
+ ...part
64
+ }))
65
+ };
66
+ }
67
+ function nextPromptInfo(prompt, input) {
68
+ return {
69
+ input,
70
+ mode: prompt.mode,
71
+ parts: prompt.parts.filter(part => part.type !== "text").map(part => ({
72
+ ...part
73
+ }))
74
+ };
75
+ }
76
+ function samePromptInfo(left, right) {
77
+ return left.input === right.input && left.mode === right.mode && JSON.stringify(left.parts) === JSON.stringify(right.parts);
78
+ }
79
+ function errorFromReason(reason, fallbackMessage) {
80
+ return reason instanceof Error ? reason : new Error(fallbackMessage);
81
+ }
82
+ function clamp(value, min, max) {
83
+ return Math.min(Math.max(value, min), max);
84
+ }
85
+ async function withRequestTimeout(signal, timeoutMs, run) {
86
+ const controller = new AbortController();
87
+ const timeoutMessage = `Prompt enhancement timed out after ${Math.floor(timeoutMs / 1000)} seconds.`;
88
+ const onAbort = () => controller.abort(signal.reason);
89
+ const timeout = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs);
90
+ if (signal.aborted) {
91
+ controller.abort(signal.reason);
92
+ } else {
93
+ signal.addEventListener("abort", onAbort, {
94
+ once: true
95
+ });
96
+ }
97
+ try {
98
+ return await run(controller.signal);
99
+ } catch (error) {
100
+ if (controller.signal.aborted && !signal.aborted) {
101
+ throw errorFromReason(controller.signal.reason, timeoutMessage);
102
+ }
103
+ throw error;
104
+ } finally {
105
+ clearTimeout(timeout);
106
+ signal.removeEventListener("abort", onAbort);
107
+ }
108
+ }
109
+ function samePromptTarget(left, right) {
110
+ if (!left || !right || left.name !== right.name) return false;
111
+ if (left.name === "session" && right.name === "session") {
112
+ return left.sessionID === right.sessionID;
113
+ }
114
+ return true;
115
+ }
116
+ function isPromptHandleActive(api, state, handle) {
117
+ const route = api.route.current;
118
+ const target = handle.target;
119
+ if (target.name === "home") {
120
+ if (route.name !== "home") return false;
121
+ } else if (!isSessionRoute(route) || route.params.sessionID !== target.sessionID) {
122
+ return false;
123
+ }
124
+ if (api.state.path.directory !== handle.directory) return false;
125
+ if (handle.ref) {
126
+ return state.promptRef === handle.ref && samePromptTarget(state.promptTarget, handle.target);
127
+ }
128
+ return true;
129
+ }
130
+ function bindPromptRef(state, target, forwarded, ref) {
131
+ if (ref) {
132
+ state.promptRef = ref;
133
+ state.promptTarget = target;
134
+ } else if (samePromptTarget(state.promptTarget, target)) {
135
+ state.promptRef = undefined;
136
+ state.promptTarget = undefined;
137
+ }
138
+ forwarded?.(ref);
139
+ }
140
+ async function applyPromptUpdate(api, state, handle, update, signal) {
141
+ if (!isPromptHandleActive(api, state, handle)) return false;
142
+ const promptRef = handle.ref;
143
+ if (promptRef) {
144
+ promptRef.set(update.createPromptInfo(promptRef.current));
145
+ if (update.refAction === "focus") promptRef.focus();else promptRef.blur();
146
+ return true;
147
+ }
148
+ const requestOptions = {
149
+ signal,
150
+ throwOnError: true
151
+ };
152
+ await api.client.tui.clearPrompt({
153
+ directory: handle.directory
154
+ }, requestOptions);
155
+ if (update.fallbackInput) {
156
+ await api.client.tui.appendPrompt({
157
+ directory: handle.directory,
158
+ text: update.fallbackInput
159
+ }, requestOptions);
160
+ }
161
+ return true;
162
+ }
163
+ function clearPrompt(api, state, handle, signal, template) {
164
+ return applyPromptUpdate(api, state, handle, {
165
+ fallbackInput: "",
166
+ createPromptInfo: current => nextPromptInfo(template ?? current, ""),
167
+ refAction: "blur"
168
+ }, signal);
169
+ }
170
+ function writePrompt(api, state, handle, input, signal, template) {
171
+ return applyPromptUpdate(api, state, handle, {
172
+ fallbackInput: input,
173
+ createPromptInfo: current => nextPromptInfo(template ?? current, input),
174
+ refAction: "focus"
175
+ }, signal);
176
+ }
177
+ function restorePrompt(api, state, handle, prompt, signal) {
178
+ return applyPromptUpdate(api, state, handle, {
179
+ fallbackInput: prompt.input,
180
+ createPromptInfo: () => clonePromptInfo(prompt),
181
+ refAction: "focus"
182
+ }, signal);
183
+ }
184
+ function restoreEnhancementPrompt(api, state, enhancement, signal) {
185
+ return enhancement.originalPrompt ? restorePrompt(api, state, enhancement.handle, enhancement.originalPrompt, signal) : writePrompt(api, state, enhancement.handle, enhancement.input, signal);
186
+ }
187
+ async function cancelActiveEnhancement(api, state, signal) {
188
+ const active = state.activeEnhancement;
189
+ if (!state.enhancing || !active) return false;
190
+ active.canceled = true;
191
+ active.stopAnimation?.();
192
+ active.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE));
193
+ return restoreEnhancementPrompt(api, state, active, signal);
194
+ }
195
+ function startEnhancementAnimation(api, state, handle, template) {
196
+ const promptRef = handle.ref;
197
+ if (!promptRef) return () => {};
198
+ let frame = 0;
199
+ let stopped = false;
200
+ const render = () => {
201
+ if (stopped) return;
202
+ if (!isPromptHandleActive(api, state, handle)) {
203
+ stopped = true;
204
+ return;
205
+ }
206
+ promptRef.set(nextPromptInfo(template ?? promptRef.current, ENHANCEMENT_ANIMATION_FRAMES[frame]));
207
+ frame = (frame + 1) % ENHANCEMENT_ANIMATION_FRAMES.length;
208
+ };
209
+ render();
210
+ const interval = setInterval(render, ENHANCEMENT_ANIMATION_INTERVAL_MS);
211
+ return () => {
212
+ stopped = true;
213
+ clearInterval(interval);
214
+ };
215
+ }
216
+ function gatherContext(api) {
217
+ const sections = [];
218
+ const dir = api.state.path.directory;
219
+ sections.push(`Working directory: ${dir}`);
220
+ const branch = api.state.vcs?.branch;
221
+ if (branch) {
222
+ sections.push(`Current branch: ${branch}`);
223
+ }
224
+ const route = api.route.current;
225
+ if (isSessionRoute(route)) {
226
+ const sessionID = route.params.sessionID;
227
+ const messages = api.state.session.messages(sessionID);
228
+ const userMessages = messages.filter(message => message.role === "user");
229
+ const recent = userMessages.slice(-MAX_RECENT_MESSAGES).reverse();
230
+ if (recent.length > 0) {
231
+ const prompts = [];
232
+ for (const msg of recent) {
233
+ const text = extractVisibleText(api.state.part(msg.id));
234
+ if (text) {
235
+ prompts.push(text.length > MAX_PROMPT_PREVIEW_LENGTH ? `${text.slice(0, MAX_PROMPT_PREVIEW_LENGTH)}...` : text);
236
+ }
237
+ }
238
+ if (prompts.length > 0) {
239
+ sections.push(`Recent user prompts in this session (newest first):\n${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")}`);
240
+ }
241
+ }
242
+ const diff = api.state.session.diff(sessionID);
243
+ if (diff.length > 0) {
244
+ const files = diff.slice(0, MAX_CHANGED_FILES).map(f => ` @${f.file}`);
245
+ sections.push(`Files changed in session:\n${files.join("\n")}`);
246
+ }
247
+ }
248
+ return sections.join("\n\n");
249
+ }
250
+ async function enhanceWithModel(api, options, input, signal) {
251
+ const directory = api.state.path.directory;
252
+ const model = resolveEnhancerModel(api, options);
253
+ const context = gatherContext(api);
254
+ const userMessage = [`--- CONTEXT ---\n${context}\n---`, `--- DRAFT ---\n${input}\n---`].join("\n\n");
255
+ const created = await api.client.session.create({
256
+ directory,
257
+ title: `Prompt Enhancer ${Math.random().toString(36).slice(2, 8)}`,
258
+ permission: [{
259
+ permission: "*",
260
+ action: "deny",
261
+ pattern: "*"
262
+ }]
263
+ }, {
264
+ signal,
265
+ throwOnError: true
266
+ });
267
+ const tempSessionID = created.data?.id;
268
+ if (!tempSessionID) throw new Error("Failed to start prompt enhancer.");
269
+ try {
270
+ const response = await withRequestTimeout(signal, ENHANCEMENT_TIMEOUT_MS, requestSignal => api.client.session.prompt({
271
+ sessionID: tempSessionID,
272
+ directory,
273
+ model,
274
+ system: ENHANCER_SYSTEM_PROMPT,
275
+ parts: [{
276
+ type: "text",
277
+ text: userMessage
278
+ }]
279
+ }, {
280
+ signal: requestSignal,
281
+ throwOnError: true
282
+ }));
283
+ const parts = response.data?.parts;
284
+ if (!parts) throw new Error("Enhancer returned no response.");
285
+ const enhanced = extractVisibleText(parts);
286
+ if (!enhanced) throw new Error("Enhancer returned no text.");
287
+ return enhanced;
288
+ } finally {
289
+ void api.client.session.abort({
290
+ sessionID: tempSessionID,
291
+ directory
292
+ }).catch(() => {}).then(() => api.client.session.delete({
293
+ sessionID: tempSessionID,
294
+ directory
295
+ })).catch(() => {});
296
+ }
297
+ }
298
+ function renderEnhanceDialog(api, dialog) {
299
+ const theme = api.theme.current;
300
+ const terminal = useTerminalDimensions();
301
+ const screenWidth = createMemo(() => Math.max(1, terminal().width));
302
+ const screenHeight = createMemo(() => Math.max(1, terminal().height));
303
+ const panelWidth = createMemo(() => {
304
+ const width = screenWidth();
305
+ const availableWidth = Math.max(1, width - DIALOG_SCREEN_MARGIN_X);
306
+ const minWidth = Math.min(DIALOG_MIN_WIDTH, availableWidth);
307
+ const maxWidth = Math.min(DIALOG_MAX_WIDTH, availableWidth);
308
+ return clamp(Math.floor(width * DIALOG_WIDTH_RATIO), minWidth, maxWidth);
309
+ });
310
+ const panelHeight = createMemo(() => {
311
+ const availableHeight = Math.max(1, screenHeight() - DIALOG_SCREEN_MARGIN_Y);
312
+ return Math.min(DIALOG_HEIGHT, availableHeight);
313
+ });
314
+ const textareaBoxHeight = createMemo(() => Math.max(1, panelHeight() - DIALOG_TEXTAREA_RESERVED_HEIGHT));
315
+ const textareaHeight = createMemo(() => Math.max(1, textareaBoxHeight() - DIALOG_TEXTAREA_VERTICAL_PADDING));
316
+ let dialogInput;
317
+ return (() => {
318
+ var _el$ = _$createElement("box"),
319
+ _el$2 = _$createElement("box"),
320
+ _el$3 = _$createElement("box"),
321
+ _el$4 = _$createElement("text"),
322
+ _el$6 = _$createElement("box"),
323
+ _el$7 = _$createElement("textarea"),
324
+ _el$8 = _$createElement("text");
325
+ _$insertNode(_el$, _el$2);
326
+ _$insertNode(_el$, _el$3);
327
+ _$setProp(_el$, "position", "absolute");
328
+ _$setProp(_el$, "top", 0);
329
+ _$setProp(_el$, "left", 0);
330
+ _$setProp(_el$, "justifyContent", "center");
331
+ _$setProp(_el$, "alignItems", "center");
332
+ _$setProp(_el$2, "position", "absolute");
333
+ _$setProp(_el$2, "top", 0);
334
+ _$setProp(_el$2, "left", 0);
335
+ _$setProp(_el$2, "backgroundColor", "#000000");
336
+ _$setProp(_el$2, "opacity", 0.65);
337
+ _$insertNode(_el$3, _el$4);
338
+ _$insertNode(_el$3, _el$6);
339
+ _$insertNode(_el$3, _el$8);
340
+ _$setProp(_el$3, "flexDirection", "column");
341
+ _$setProp(_el$3, "gap", 1);
342
+ _$setProp(_el$3, "paddingX", 2);
343
+ _$setProp(_el$3, "paddingY", 1);
344
+ _$setProp(_el$3, "border", true);
345
+ _$insertNode(_el$4, _$createTextNode(`Enhance Prompt`));
346
+ _$insertNode(_el$6, _el$7);
347
+ _$setProp(_el$6, "border", true);
348
+ _$setProp(_el$6, "flexDirection", "column");
349
+ _$setProp(_el$6, "paddingX", 1);
350
+ _$setProp(_el$6, "paddingY", 1);
351
+ _$use(node => {
352
+ dialogInput = node;
353
+ if (!node) return;
354
+ node.cursorOffset = node.plainText.length;
355
+ queueMicrotask(() => {
356
+ if (dialogInput === node) node.focus();
357
+ });
358
+ }, _el$7);
359
+ _$setProp(_el$7, "width", "100%");
360
+ _$setProp(_el$7, "placeholder", "Describe the task...");
361
+ _$setProp(_el$7, "wrapMode", "word");
362
+ _$setProp(_el$7, "keyBindings", [{
363
+ name: "return",
364
+ action: "submit"
365
+ }, {
366
+ name: "linefeed",
367
+ action: "submit"
368
+ }, {
369
+ name: "return",
370
+ shift: true,
371
+ action: "newline"
372
+ }, {
373
+ name: "linefeed",
374
+ shift: true,
375
+ action: "newline"
376
+ }]);
377
+ _$setProp(_el$7, "onKeyDown", key => {
378
+ if (key.name === "escape" || key.ctrl && key.name === "c") dialog.onCancel();
379
+ });
380
+ _$setProp(_el$7, "onSubmit", () => dialog.onConfirm(dialogInput?.plainText ?? ""));
381
+ _$insertNode(_el$8, _$createTextNode(`Enter to enhance • Shift+Enter for newline • Esc to cancel`));
382
+ _$effect(_p$ => {
383
+ var _v$ = screenWidth(),
384
+ _v$2 = screenHeight(),
385
+ _v$3 = screenWidth(),
386
+ _v$4 = screenHeight(),
387
+ _v$5 = panelWidth(),
388
+ _v$6 = panelHeight(),
389
+ _v$7 = theme.backgroundPanel,
390
+ _v$8 = theme.border,
391
+ _v$9 = theme.borderActive,
392
+ _v$0 = textareaBoxHeight(),
393
+ _v$1 = textareaHeight(),
394
+ _v$10 = dialog.initialValue,
395
+ _v$11 = theme.text,
396
+ _v$12 = theme.textMuted,
397
+ _v$13 = theme.backgroundPanel,
398
+ _v$14 = theme.backgroundPanel,
399
+ _v$15 = theme.text,
400
+ _v$16 = theme.primary,
401
+ _v$17 = theme.textMuted;
402
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
403
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
404
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "width", _v$3, _p$.a));
405
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "height", _v$4, _p$.o));
406
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$3, "width", _v$5, _p$.i));
407
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "height", _v$6, _p$.n));
408
+ _v$7 !== _p$.s && (_p$.s = _$setProp(_el$3, "backgroundColor", _v$7, _p$.s));
409
+ _v$8 !== _p$.h && (_p$.h = _$setProp(_el$3, "borderColor", _v$8, _p$.h));
410
+ _v$9 !== _p$.r && (_p$.r = _$setProp(_el$6, "borderColor", _v$9, _p$.r));
411
+ _v$0 !== _p$.d && (_p$.d = _$setProp(_el$6, "height", _v$0, _p$.d));
412
+ _v$1 !== _p$.l && (_p$.l = _$setProp(_el$7, "height", _v$1, _p$.l));
413
+ _v$10 !== _p$.u && (_p$.u = _$setProp(_el$7, "initialValue", _v$10, _p$.u));
414
+ _v$11 !== _p$.c && (_p$.c = _$setProp(_el$7, "textColor", _v$11, _p$.c));
415
+ _v$12 !== _p$.w && (_p$.w = _$setProp(_el$7, "placeholderColor", _v$12, _p$.w));
416
+ _v$13 !== _p$.m && (_p$.m = _$setProp(_el$7, "backgroundColor", _v$13, _p$.m));
417
+ _v$14 !== _p$.f && (_p$.f = _$setProp(_el$7, "focusedBackgroundColor", _v$14, _p$.f));
418
+ _v$15 !== _p$.y && (_p$.y = _$setProp(_el$7, "focusedTextColor", _v$15, _p$.y));
419
+ _v$16 !== _p$.g && (_p$.g = _$setProp(_el$7, "cursorColor", _v$16, _p$.g));
420
+ _v$17 !== _p$.p && (_p$.p = _$setProp(_el$8, "fg", _v$17, _p$.p));
421
+ return _p$;
422
+ }, {
423
+ e: undefined,
424
+ t: undefined,
425
+ a: undefined,
426
+ o: undefined,
427
+ i: undefined,
428
+ n: undefined,
429
+ s: undefined,
430
+ h: undefined,
431
+ r: undefined,
432
+ d: undefined,
433
+ l: undefined,
434
+ u: undefined,
435
+ c: undefined,
436
+ w: undefined,
437
+ m: undefined,
438
+ f: undefined,
439
+ y: undefined,
440
+ g: undefined,
441
+ p: undefined
442
+ });
443
+ return _el$;
444
+ })();
445
+ }
446
+ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
447
+ if (state.enhancing) {
448
+ api.ui.toast({
449
+ variant: "warning",
450
+ title: TOAST_TITLE,
451
+ message: "Enhancement in progress."
452
+ });
453
+ return;
454
+ }
455
+ if (signal.aborted) return;
456
+ const target = state.promptTarget ?? (api.route.current.name === "home" ? {
457
+ name: "home"
458
+ } : isSessionRoute(api.route.current) ? {
459
+ name: "session",
460
+ sessionID: api.route.current.params.sessionID
461
+ } : undefined);
462
+ if (!target) {
463
+ api.ui.toast({
464
+ variant: "warning",
465
+ title: TOAST_TITLE,
466
+ message: "Enhancement only works from a prompt."
467
+ });
468
+ return;
469
+ }
470
+ const handle = {
471
+ target,
472
+ directory: api.state.path.directory,
473
+ ref: state.promptRef
474
+ };
475
+ const originalPrompt = handle.ref ? clonePromptInfo(handle.ref.current) : undefined;
476
+ const initialValue = originalPrompt?.input ?? "";
477
+ const closeDialog = () => setEnhanceDialog(undefined);
478
+ const confirmInput = value => {
479
+ if (state.enhancing) return;
480
+ const input = value.trim();
481
+ if (!input) {
482
+ api.ui.toast({
483
+ variant: "warning",
484
+ title: TOAST_TITLE,
485
+ message: "Enter a prompt first."
486
+ });
487
+ closeDialog();
488
+ return;
489
+ }
490
+ if (!isPromptHandleActive(api, state, handle)) {
491
+ api.ui.toast({
492
+ variant: "warning",
493
+ title: TOAST_TITLE,
494
+ message: "Prompt changed while dialog was open."
495
+ });
496
+ closeDialog();
497
+ return;
498
+ }
499
+ state.enhancing = true;
500
+ closeDialog();
501
+ api.ui.toast({
502
+ variant: "info",
503
+ title: TOAST_TITLE,
504
+ message: "Enhancing prompt...",
505
+ duration: TOAST_DURATION_MS
506
+ });
507
+ const enhancementController = new AbortController();
508
+ const onLifecycleAbort = () => enhancementController.abort(signal.reason);
509
+ const activeEnhancement = {
510
+ controller: enhancementController,
511
+ handle,
512
+ originalPrompt,
513
+ input
514
+ };
515
+ state.activeEnhancement = activeEnhancement;
516
+ if (signal.aborted) {
517
+ enhancementController.abort(signal.reason);
518
+ } else {
519
+ signal.addEventListener("abort", onLifecycleAbort, {
520
+ once: true
521
+ });
522
+ }
523
+ void (async () => {
524
+ let stopAnimation = () => {};
525
+ try {
526
+ const cleared = await clearPrompt(api, state, handle, signal, originalPrompt);
527
+ if (!cleared) {
528
+ api.ui.toast({
529
+ variant: "warning",
530
+ title: TOAST_TITLE,
531
+ message: "Prompt changed before enhancement started."
532
+ });
533
+ return;
534
+ }
535
+ stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt);
536
+ if (state.activeEnhancement) {
537
+ state.activeEnhancement.stopAnimation = stopAnimation;
538
+ }
539
+ const enhanced = await enhanceWithModel(api, options, input, enhancementController.signal);
540
+ if (signal.aborted) return;
541
+ if (enhancementController.signal.aborted) {
542
+ throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE);
543
+ }
544
+ stopAnimation();
545
+ const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt);
546
+ if (!wrote) {
547
+ api.ui.toast({
548
+ variant: "warning",
549
+ title: TOAST_TITLE,
550
+ message: "Enhanced prompt is ready, but that prompt is no longer active."
551
+ });
552
+ return;
553
+ }
554
+ if (enhancementController.signal.aborted) {
555
+ throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE);
556
+ }
557
+ state.lastEnhancement = originalPrompt ? {
558
+ original: clonePromptInfo(originalPrompt),
559
+ enhancedInput: enhanced,
560
+ target: {
561
+ ...handle.target
562
+ },
563
+ directory: handle.directory
564
+ } : undefined;
565
+ api.ui.toast({
566
+ variant: "success",
567
+ title: "Prompt enhanced",
568
+ message: "Enhanced prompt added to input.",
569
+ duration: TOAST_DURATION_MS
570
+ });
571
+ } catch (error) {
572
+ if (signal.aborted) return;
573
+ stopAnimation();
574
+ const canceled = enhancementController.signal.aborted && !signal.aborted;
575
+ if (canceled && state.activeEnhancement?.canceled) {
576
+ // cancelActiveEnhancement already stopped the animation and restored the prompt.
577
+ return;
578
+ }
579
+ let restored;
580
+ try {
581
+ restored = await restoreEnhancementPrompt(api, state, activeEnhancement, signal);
582
+ } catch {
583
+ // Best-effort restore; do not suppress the error toast.
584
+ restored = false;
585
+ }
586
+ let baseMessage;
587
+ if (canceled) {
588
+ baseMessage = ENHANCEMENT_CANCELED_MESSAGE;
589
+ } else if (error instanceof Error) {
590
+ baseMessage = error.message;
591
+ } else {
592
+ baseMessage = "Prompt enhancement failed.";
593
+ }
594
+ let message;
595
+ if (canceled && restored) {
596
+ message = "Enhancement canceled. Original prompt restored.";
597
+ } else if (restored) {
598
+ message = baseMessage;
599
+ } else {
600
+ message = `${baseMessage} Original prompt could not be restored because the prompt changed. Please re-enter your prompt manually.`;
601
+ }
602
+ api.ui.toast({
603
+ variant: canceled && restored ? "info" : "error",
604
+ title: TOAST_TITLE,
605
+ message
606
+ });
607
+ } finally {
608
+ stopAnimation();
609
+ signal.removeEventListener("abort", onLifecycleAbort);
610
+ if (state.activeEnhancement?.controller === enhancementController) {
611
+ state.activeEnhancement = undefined;
612
+ }
613
+ state.enhancing = false;
614
+ }
615
+ })();
616
+ };
617
+ setEnhanceDialog({
618
+ initialValue,
619
+ onCancel: closeDialog,
620
+ onConfirm: confirmInput
621
+ });
622
+ }
623
+ function revertEnhancement(api, state, signal) {
624
+ if (state.enhancing && state.activeEnhancement) {
625
+ void (async () => {
626
+ try {
627
+ const restored = await cancelActiveEnhancement(api, state, signal);
628
+ if (!restored) {
629
+ api.ui.toast({
630
+ variant: "warning",
631
+ title: TOAST_TITLE,
632
+ message: "Prompt changed while canceling."
633
+ });
634
+ return;
635
+ }
636
+ api.ui.toast({
637
+ variant: "info",
638
+ title: TOAST_TITLE,
639
+ message: "Enhancement canceled. Original prompt restored.",
640
+ duration: TOAST_DURATION_MS
641
+ });
642
+ } catch (error) {
643
+ if (signal.aborted) return;
644
+ const message = error instanceof Error ? error.message : "Cancel failed.";
645
+ api.ui.toast({
646
+ variant: "error",
647
+ title: TOAST_TITLE,
648
+ message
649
+ });
650
+ }
651
+ })();
652
+ return;
653
+ }
654
+ const lastEnhancement = state.lastEnhancement;
655
+ if (!lastEnhancement) {
656
+ api.ui.toast({
657
+ variant: "warning",
658
+ title: TOAST_TITLE,
659
+ message: "No enhancement to revert."
660
+ });
661
+ return;
662
+ }
663
+ const target = state.promptTarget;
664
+ if (!target) {
665
+ api.ui.toast({
666
+ variant: "warning",
667
+ title: TOAST_TITLE,
668
+ message: "Revert only works from a prompt."
669
+ });
670
+ return;
671
+ }
672
+ if (api.state.path.directory !== lastEnhancement.directory || !samePromptTarget(target, lastEnhancement.target)) {
673
+ api.ui.toast({
674
+ variant: "warning",
675
+ title: TOAST_TITLE,
676
+ message: "The enhanced prompt is no longer active."
677
+ });
678
+ return;
679
+ }
680
+ const handle = {
681
+ target,
682
+ directory: api.state.path.directory,
683
+ ref: state.promptRef
684
+ };
685
+ if (!isPromptHandleActive(api, state, handle)) {
686
+ api.ui.toast({
687
+ variant: "warning",
688
+ title: TOAST_TITLE,
689
+ message: "Prompt changed since enhancement."
690
+ });
691
+ return;
692
+ }
693
+ const currentPrompt = handle.ref?.current;
694
+ const expectedEnhancedPrompt = nextPromptInfo(lastEnhancement.original, lastEnhancement.enhancedInput);
695
+ if (!currentPrompt || !samePromptInfo(currentPrompt, expectedEnhancedPrompt)) {
696
+ api.ui.toast({
697
+ variant: "warning",
698
+ title: TOAST_TITLE,
699
+ message: "Prompt was manually changed after enhancement. Revert skipped."
700
+ });
701
+ return;
702
+ }
703
+ void (async () => {
704
+ try {
705
+ const wrote = await restorePrompt(api, state, handle, lastEnhancement.original, signal);
706
+ if (!wrote) {
707
+ api.ui.toast({
708
+ variant: "warning",
709
+ title: TOAST_TITLE,
710
+ message: "Prompt changed while reverting."
711
+ });
712
+ return;
713
+ }
714
+ state.lastEnhancement = undefined;
715
+ api.ui.toast({
716
+ variant: "success",
717
+ title: TOAST_TITLE,
718
+ message: "Reverted to original prompt.",
719
+ duration: TOAST_DURATION_MS
720
+ });
721
+ } catch (error) {
722
+ if (signal.aborted) return;
723
+ const message = error instanceof Error ? error.message : "Revert failed.";
724
+ api.ui.toast({
725
+ variant: "error",
726
+ title: TOAST_TITLE,
727
+ message
728
+ });
729
+ }
730
+ })();
731
+ }
732
+ const tui = async (api, options) => {
733
+ const state = {
734
+ enhancing: false
735
+ };
736
+ const [enhanceDialog, setEnhanceDialog] = createSignal();
737
+ const promptSlots = {
738
+ slots: {
739
+ app() {
740
+ return _$createComponent(Show, {
741
+ get when() {
742
+ return enhanceDialog();
743
+ },
744
+ children: dialog => renderEnhanceDialog(api, dialog())
745
+ });
746
+ },
747
+ home_prompt(_ctx, props) {
748
+ return _$createComponent(api.ui.Prompt, {
749
+ ref: ref => bindPromptRef(state, {
750
+ name: "home"
751
+ }, props.ref, ref),
752
+ get right() {
753
+ return _$createComponent(api.ui.Slot, {
754
+ name: "home_prompt_right"
755
+ });
756
+ }
757
+ });
758
+ },
759
+ session_prompt(_ctx, props) {
760
+ return _$createComponent(api.ui.Prompt, {
761
+ ref: ref => bindPromptRef(state, {
762
+ name: "session",
763
+ sessionID: props.session_id
764
+ }, props.ref, ref),
765
+ get sessionID() {
766
+ return props.session_id;
767
+ },
768
+ get visible() {
769
+ return props.visible;
770
+ },
771
+ get disabled() {
772
+ return props.disabled;
773
+ },
774
+ get onSubmit() {
775
+ return props.on_submit;
776
+ },
777
+ get right() {
778
+ return _$createComponent(api.ui.Slot, {
779
+ name: "session_prompt_right",
780
+ get session_id() {
781
+ return props.session_id;
782
+ }
783
+ });
784
+ }
785
+ });
786
+ }
787
+ }
788
+ };
789
+ api.slots.register(promptSlots);
790
+ const unregister = api.keymap.registerLayer({
791
+ commands: [{
792
+ name: "prompt-enhancer.enhance",
793
+ title: DIALOG_TITLE,
794
+ desc: "Enhance current prompt",
795
+ category: "Prompt",
796
+ suggested: true,
797
+ run: () => {
798
+ openEnhanceDialog(api, options, state, setEnhanceDialog, api.lifecycle.signal);
799
+ }
800
+ }, {
801
+ name: "prompt-enhancer.revert",
802
+ title: "Revert Enhanced Prompt",
803
+ desc: "Revert last prompt enhancement",
804
+ category: "Prompt",
805
+ run: () => {
806
+ revertEnhancement(api, state, api.lifecycle.signal);
807
+ }
808
+ }],
809
+ bindings: [{
810
+ key: "ctrl+e",
811
+ cmd: "prompt-enhancer.enhance"
812
+ }, {
813
+ key: "ctrl+shift+e",
814
+ cmd: "prompt-enhancer.revert"
815
+ }]
816
+ });
817
+ api.lifecycle.onDispose(() => {
818
+ unregister();
819
+ setEnhanceDialog(undefined);
820
+ const active = state.activeEnhancement;
821
+ if (active) {
822
+ active.controller.abort(api.lifecycle.signal.reason);
823
+ }
824
+ state.activeEnhancement = undefined;
825
+ state.enhancing = false;
826
+ state.promptRef = undefined;
827
+ state.promptTarget = undefined;
828
+ state.lastEnhancement = undefined;
829
+ });
830
+ };
831
+ const plugin = {
832
+ id: "prompt-enhancer",
833
+ tui
834
+ };
835
+ export default plugin;