@mtayfur/opencode-prompt-enhancer 0.0.21 → 0.0.22

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.
@@ -36,17 +36,20 @@ const DIALOG_PLACEHOLDER = "Describe the task...";
36
36
  const DIALOG_HINT = "Enter to enhance • Shift+Enter for newline • Esc to cancel";
37
37
  const TOAST_TITLE = "Prompt enhancer";
38
38
  function parseEnhancementInput(input) {
39
- const match = input.trimEnd().match(/^(\/[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:-]*)*)(?:(?: +|\n)([\s\S]*))?$/);
39
+ const match = input.match(/^(\/[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:-]*)*)(?:(?: +|\n)([\s\S]*))?$/);
40
40
  if (!match) return {
41
- draft: input.trim()
41
+ draft: input
42
42
  };
43
43
  return {
44
44
  command: match[1],
45
- draft: match[2]?.trim() ?? ""
45
+ draft: match[2] ?? ""
46
46
  };
47
47
  }
48
48
  function formatEnhancedInput(input, enhancedDraft) {
49
- return input.command ? `${input.command} ${enhancedDraft}` : enhancedDraft;
49
+ if (!input.command) return enhancedDraft;
50
+ const nested = parseEnhancementInput(enhancedDraft);
51
+ const draft = nested.command === input.command ? nested.draft : enhancedDraft;
52
+ return draft ? `${input.command} ${draft}` : input.command;
50
53
  }
51
54
  function parseModelString(value) {
52
55
  if (!value) return undefined;
@@ -62,7 +65,7 @@ function isSessionRoute(route) {
62
65
  return route.name === "session";
63
66
  }
64
67
  function extractVisibleText(parts) {
65
- return parts.filter(part => part.type === "text" && !part.ignored).map(part => part.text).join("").trim();
68
+ return parts.filter(part => part.type === "text" && !part.ignored).map(part => part.text).join("");
66
69
  }
67
70
  function formatContextPreview(text) {
68
71
  if (text.length <= MAX_CONTEXT_ITEM_PREVIEW_LENGTH) return text;
@@ -210,10 +213,13 @@ function restoreEnhancementPrompt(api, state, enhancement, signal) {
210
213
  }
211
214
  async function cancelActiveEnhancement(api, state, signal) {
212
215
  const active = state.activeEnhancement;
213
- if (!state.enhancing || !active) return false;
216
+ if (!active) return false;
214
217
  active.canceled = true;
215
- active.stopAnimation?.();
218
+ active.stopAnimation();
216
219
  active.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE));
220
+ if (active.clearPromise) {
221
+ await active.clearPromise.catch(() => false);
222
+ }
217
223
  return restoreEnhancementPrompt(api, state, active, signal);
218
224
  }
219
225
  function startEnhancementAnimation(api, state, handle, template) {
@@ -248,7 +254,7 @@ function gatherContext(api) {
248
254
  if (recent.length > 0) {
249
255
  const prompts = [];
250
256
  for (const msg of recent) {
251
- const text = extractVisibleText(api.state.part(msg.id));
257
+ const text = extractVisibleText(api.state.part(msg.id)).trim();
252
258
  if (text) {
253
259
  prompts.push(formatContextPreview(text));
254
260
  }
@@ -310,7 +316,7 @@ async function enhanceWithModel(api, options, input, signal) {
310
316
  const parts = response.data?.parts;
311
317
  if (!parts) throw new Error("Enhancer returned no response.");
312
318
  const enhanced = extractVisibleText(parts);
313
- if (!enhanced) throw new Error("Enhancer returned no text.");
319
+ if (!enhanced.trim()) throw new Error("Enhancer returned no text.");
314
320
  return enhanced;
315
321
  } finally {
316
322
  void api.client.session.abort({
@@ -471,7 +477,7 @@ function renderEnhanceDialog(api, dialog) {
471
477
  })();
472
478
  }
473
479
  function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
474
- if (state.enhancing) {
480
+ if (state.activeEnhancement) {
475
481
  api.ui.toast({
476
482
  variant: "warning",
477
483
  title: TOAST_TITLE,
@@ -503,9 +509,8 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
503
509
  const initialValue = originalPrompt?.input ?? "";
504
510
  const closeDialog = () => setEnhanceDialog(undefined);
505
511
  const confirmInput = value => {
506
- if (state.enhancing) return;
507
- const input = value.trim();
508
- if (!input) {
512
+ if (state.activeEnhancement) return;
513
+ if (!value.trim()) {
509
514
  api.ui.toast({
510
515
  variant: "warning",
511
516
  title: TOAST_TITLE,
@@ -515,7 +520,7 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
515
520
  return;
516
521
  }
517
522
  const enhancementInput = parseEnhancementInput(value);
518
- if (!enhancementInput.draft) {
523
+ if (!enhancementInput.draft.trim()) {
519
524
  api.ui.toast({
520
525
  variant: "warning",
521
526
  title: TOAST_TITLE,
@@ -524,7 +529,8 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
524
529
  closeDialog();
525
530
  return;
526
531
  }
527
- if (!isPromptHandleActive(api, state, handle)) {
532
+ const promptChanged = originalPrompt && handle.ref ? !samePromptInfo(handle.ref.current, originalPrompt) : false;
533
+ if (!isPromptHandleActive(api, state, handle) || promptChanged) {
528
534
  api.ui.toast({
529
535
  variant: "warning",
530
536
  title: TOAST_TITLE,
@@ -533,7 +539,6 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
533
539
  closeDialog();
534
540
  return;
535
541
  }
536
- state.enhancing = true;
537
542
  closeDialog();
538
543
  api.ui.toast({
539
544
  variant: "info",
@@ -545,9 +550,10 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
545
550
  const onLifecycleAbort = () => enhancementController.abort(signal.reason);
546
551
  const activeEnhancement = {
547
552
  controller: enhancementController,
553
+ stopAnimation: () => {},
548
554
  handle,
549
555
  originalPrompt,
550
- input
556
+ input: value
551
557
  };
552
558
  state.activeEnhancement = activeEnhancement;
553
559
  if (signal.aborted) {
@@ -558,9 +564,10 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
558
564
  });
559
565
  }
560
566
  void (async () => {
561
- let stopAnimation = () => {};
562
567
  try {
563
- const cleared = await clearPrompt(api, state, handle, signal, originalPrompt);
568
+ const clearPromise = clearPrompt(api, state, handle, signal, originalPrompt);
569
+ activeEnhancement.clearPromise = clearPromise;
570
+ const cleared = await clearPromise;
564
571
  if (!cleared) {
565
572
  api.ui.toast({
566
573
  variant: "warning",
@@ -569,17 +576,14 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
569
576
  });
570
577
  return;
571
578
  }
572
- stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt);
573
- if (state.activeEnhancement) {
574
- state.activeEnhancement.stopAnimation = stopAnimation;
575
- }
579
+ activeEnhancement.stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt);
576
580
  const enhancedDraft = await enhanceWithModel(api, options, enhancementInput.draft, enhancementController.signal);
577
581
  const enhanced = formatEnhancedInput(enhancementInput, enhancedDraft);
578
582
  if (signal.aborted) return;
579
583
  if (enhancementController.signal.aborted) {
580
584
  throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE);
581
585
  }
582
- stopAnimation();
586
+ activeEnhancement.stopAnimation();
583
587
  const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt);
584
588
  if (!wrote) {
585
589
  api.ui.toast({
@@ -608,7 +612,7 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
608
612
  });
609
613
  } catch (error) {
610
614
  if (signal.aborted) return;
611
- stopAnimation();
615
+ activeEnhancement.stopAnimation();
612
616
  const canceled = enhancementController.signal.aborted && !signal.aborted;
613
617
  if (canceled && state.activeEnhancement?.canceled) {
614
618
  // cancelActiveEnhancement already stopped the animation and restored the prompt.
@@ -643,12 +647,11 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
643
647
  message
644
648
  });
645
649
  } finally {
646
- stopAnimation();
650
+ activeEnhancement.stopAnimation();
647
651
  signal.removeEventListener("abort", onLifecycleAbort);
648
652
  if (state.activeEnhancement?.controller === enhancementController) {
649
653
  state.activeEnhancement = undefined;
650
654
  }
651
- state.enhancing = false;
652
655
  }
653
656
  })();
654
657
  };
@@ -659,7 +662,7 @@ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
659
662
  });
660
663
  }
661
664
  function revertEnhancement(api, state, signal) {
662
- if (state.enhancing && state.activeEnhancement) {
665
+ if (state.activeEnhancement) {
663
666
  void (async () => {
664
667
  try {
665
668
  const restored = await cancelActiveEnhancement(api, state, signal);
@@ -768,9 +771,7 @@ function revertEnhancement(api, state, signal) {
768
771
  })();
769
772
  }
770
773
  const tui = async (api, options) => {
771
- const state = {
772
- enhancing: false
773
- };
774
+ const state = {};
774
775
  const [enhanceDialog, setEnhanceDialog] = createSignal();
775
776
  const promptSlots = {
776
777
  slots: {
@@ -860,7 +861,6 @@ const tui = async (api, options) => {
860
861
  active.controller.abort(api.lifecycle.signal.reason);
861
862
  }
862
863
  state.activeEnhancement = undefined;
863
- state.enhancing = false;
864
864
  state.promptRef = undefined;
865
865
  state.promptTarget = undefined;
866
866
  state.lastEnhancement = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtayfur/opencode-prompt-enhancer",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "OpenCode plugin that rewrites rough drafts into stronger prompts.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -52,7 +52,8 @@ type ModelRef = {
52
52
  type Api = Parameters<TuiPlugin>[0]
53
53
  type ActiveEnhancement = {
54
54
  controller: AbortController
55
- stopAnimation?: () => void
55
+ stopAnimation: () => void
56
+ clearPromise?: Promise<boolean>
56
57
  handle: PromptHandle
57
58
  originalPrompt?: TuiPromptInfo
58
59
  input: string
@@ -60,7 +61,6 @@ type ActiveEnhancement = {
60
61
  }
61
62
 
62
63
  type PluginState = {
63
- enhancing: boolean
64
64
  activeEnhancement?: ActiveEnhancement
65
65
  promptRef?: TuiPromptRef
66
66
  promptTarget?: PromptTarget
@@ -108,17 +108,21 @@ type EnhancementInput = {
108
108
  }
109
109
 
110
110
  function parseEnhancementInput(input: string): EnhancementInput {
111
- const match = input.trimEnd().match(/^(\/[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:-]*)*)(?:(?: +|\n)([\s\S]*))?$/)
112
- if (!match) return { draft: input.trim() }
111
+ const match = input.match(/^(\/[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:-]*)*)(?:(?: +|\n)([\s\S]*))?$/)
112
+ if (!match) return { draft: input }
113
113
 
114
114
  return {
115
115
  command: match[1],
116
- draft: match[2]?.trim() ?? "",
116
+ draft: match[2] ?? "",
117
117
  }
118
118
  }
119
119
 
120
120
  function formatEnhancedInput(input: EnhancementInput, enhancedDraft: string): string {
121
- return input.command ? `${input.command} ${enhancedDraft}` : enhancedDraft
121
+ if (!input.command) return enhancedDraft
122
+
123
+ const nested = parseEnhancementInput(enhancedDraft)
124
+ const draft = nested.command === input.command ? nested.draft : enhancedDraft
125
+ return draft ? `${input.command} ${draft}` : input.command
122
126
  }
123
127
 
124
128
  function parseModelString(value: string | undefined): ModelRef | undefined {
@@ -141,7 +145,6 @@ function extractVisibleText(parts: ReadonlyArray<Part>): string {
141
145
  .filter((part): part is TextPart => part.type === "text" && !part.ignored)
142
146
  .map((part) => part.text)
143
147
  .join("")
144
- .trim()
145
148
  }
146
149
 
147
150
  function formatContextPreview(text: string): string {
@@ -342,12 +345,16 @@ async function cancelActiveEnhancement(
342
345
  signal: AbortSignal,
343
346
  ): Promise<boolean> {
344
347
  const active = state.activeEnhancement
345
- if (!state.enhancing || !active) return false
348
+ if (!active) return false
346
349
 
347
350
  active.canceled = true
348
- active.stopAnimation?.()
351
+ active.stopAnimation()
349
352
  active.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE))
350
353
 
354
+ if (active.clearPromise) {
355
+ await active.clearPromise.catch(() => false)
356
+ }
357
+
351
358
  return restoreEnhancementPrompt(api, state, active, signal)
352
359
  }
353
360
 
@@ -395,7 +402,7 @@ function gatherContext(api: Api): string {
395
402
  if (recent.length > 0) {
396
403
  const prompts: string[] = []
397
404
  for (const msg of recent) {
398
- const text = extractVisibleText(api.state.part(msg.id))
405
+ const text = extractVisibleText(api.state.part(msg.id)).trim()
399
406
  if (text) {
400
407
  prompts.push(formatContextPreview(text))
401
408
  }
@@ -476,7 +483,7 @@ async function enhanceWithModel(
476
483
  if (!parts) throw new Error("Enhancer returned no response.")
477
484
 
478
485
  const enhanced = extractVisibleText(parts)
479
- if (!enhanced) throw new Error("Enhancer returned no text.")
486
+ if (!enhanced.trim()) throw new Error("Enhancer returned no text.")
480
487
  return enhanced
481
488
  } finally {
482
489
  void api.client.session
@@ -591,7 +598,7 @@ function openEnhanceDialog(
591
598
  setEnhanceDialog: SetEnhanceDialog,
592
599
  signal: AbortSignal,
593
600
  ): void {
594
- if (state.enhancing) {
601
+ if (state.activeEnhancement) {
595
602
  api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enhancement in progress." })
596
603
  return
597
604
  }
@@ -619,29 +626,30 @@ function openEnhanceDialog(
619
626
  const closeDialog = () => setEnhanceDialog(undefined)
620
627
 
621
628
  const confirmInput = (value: string) => {
622
- if (state.enhancing) return
629
+ if (state.activeEnhancement) return
623
630
 
624
- const input = value.trim()
625
- if (!input) {
631
+ if (!value.trim()) {
626
632
  api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enter a prompt first." })
627
633
  closeDialog()
628
634
  return
629
635
  }
630
636
 
631
637
  const enhancementInput = parseEnhancementInput(value)
632
- if (!enhancementInput.draft) {
638
+ if (!enhancementInput.draft.trim()) {
633
639
  api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enter instructions after the slash command." })
634
640
  closeDialog()
635
641
  return
636
642
  }
637
643
 
638
- if (!isPromptHandleActive(api, state, handle)) {
644
+ const promptChanged = originalPrompt && handle.ref
645
+ ? !samePromptInfo(handle.ref.current, originalPrompt)
646
+ : false
647
+ if (!isPromptHandleActive(api, state, handle) || promptChanged) {
639
648
  api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while dialog was open." })
640
649
  closeDialog()
641
650
  return
642
651
  }
643
652
 
644
- state.enhancing = true
645
653
  closeDialog()
646
654
  api.ui.toast({
647
655
  variant: "info",
@@ -654,9 +662,10 @@ function openEnhanceDialog(
654
662
  const onLifecycleAbort = () => enhancementController.abort(signal.reason)
655
663
  const activeEnhancement: ActiveEnhancement = {
656
664
  controller: enhancementController,
665
+ stopAnimation: () => {},
657
666
  handle,
658
667
  originalPrompt,
659
- input,
668
+ input: value,
660
669
  }
661
670
  state.activeEnhancement = activeEnhancement
662
671
  if (signal.aborted) {
@@ -666,9 +675,10 @@ function openEnhanceDialog(
666
675
  }
667
676
 
668
677
  void (async () => {
669
- let stopAnimation = () => {}
670
678
  try {
671
- const cleared = await clearPrompt(api, state, handle, signal, originalPrompt)
679
+ const clearPromise = clearPrompt(api, state, handle, signal, originalPrompt)
680
+ activeEnhancement.clearPromise = clearPromise
681
+ const cleared = await clearPromise
672
682
  if (!cleared) {
673
683
  api.ui.toast({
674
684
  variant: "warning",
@@ -678,10 +688,7 @@ function openEnhanceDialog(
678
688
  return
679
689
  }
680
690
 
681
- stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt)
682
- if (state.activeEnhancement) {
683
- state.activeEnhancement.stopAnimation = stopAnimation
684
- }
691
+ activeEnhancement.stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt)
685
692
 
686
693
  const enhancedDraft = await enhanceWithModel(api, options, enhancementInput.draft, enhancementController.signal)
687
694
  const enhanced = formatEnhancedInput(enhancementInput, enhancedDraft)
@@ -690,7 +697,7 @@ function openEnhanceDialog(
690
697
  throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE)
691
698
  }
692
699
 
693
- stopAnimation()
700
+ activeEnhancement.stopAnimation()
694
701
 
695
702
  const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt)
696
703
  if (!wrote) {
@@ -723,7 +730,7 @@ function openEnhanceDialog(
723
730
  } catch (error) {
724
731
  if (signal.aborted) return
725
732
 
726
- stopAnimation()
733
+ activeEnhancement.stopAnimation()
727
734
 
728
735
  const canceled = enhancementController.signal.aborted && !signal.aborted
729
736
  if (canceled && state.activeEnhancement?.canceled) {
@@ -756,12 +763,11 @@ function openEnhanceDialog(
756
763
  }
757
764
  api.ui.toast({ variant: canceled && restored ? "info" : "error", title: TOAST_TITLE, message })
758
765
  } finally {
759
- stopAnimation()
766
+ activeEnhancement.stopAnimation()
760
767
  signal.removeEventListener("abort", onLifecycleAbort)
761
768
  if (state.activeEnhancement?.controller === enhancementController) {
762
769
  state.activeEnhancement = undefined
763
770
  }
764
- state.enhancing = false
765
771
  }
766
772
  })()
767
773
  }
@@ -774,7 +780,7 @@ function revertEnhancement(
774
780
  state: PluginState,
775
781
  signal: AbortSignal,
776
782
  ): void {
777
- if (state.enhancing && state.activeEnhancement) {
783
+ if (state.activeEnhancement) {
778
784
  void (async () => {
779
785
  try {
780
786
  const restored = await cancelActiveEnhancement(api, state, signal)
@@ -866,7 +872,7 @@ function revertEnhancement(
866
872
  }
867
873
 
868
874
  const tui: TuiPlugin = async (api, options) => {
869
- const state: PluginState = { enhancing: false }
875
+ const state: PluginState = {}
870
876
  const [enhanceDialog, setEnhanceDialog] = createSignal<EnhanceDialogState | undefined>()
871
877
 
872
878
  const promptSlots: TuiSlotPlugin = {
@@ -947,7 +953,6 @@ const tui: TuiPlugin = async (api, options) => {
947
953
  }
948
954
 
949
955
  state.activeEnhancement = undefined
950
- state.enhancing = false
951
956
  state.promptRef = undefined
952
957
  state.promptTarget = undefined
953
958
  state.lastEnhancement = undefined