@mtayfur/opencode-prompt-enhancer 0.0.4 → 0.0.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtayfur/opencode-prompt-enhancer",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "OpenCode plugin that rewrites rough drafts into stronger prompts.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -65,6 +65,17 @@ type Api = Parameters<TuiPlugin>[0]
65
65
  type PluginState = {
66
66
  enhancing: boolean
67
67
  promptRef?: TuiPromptRef
68
+ promptTarget?: PromptTarget
69
+ }
70
+
71
+ type PromptTarget =
72
+ | { name: "home", workspaceID?: string }
73
+ | { name: "session", sessionID: string }
74
+
75
+ type PromptHandle = {
76
+ target: PromptTarget
77
+ directory: string
78
+ ref?: TuiPromptRef
68
79
  }
69
80
 
70
81
  function parseModelString(value: string | undefined): ModelRef | undefined {
@@ -134,40 +145,117 @@ function nextPromptInfo(prompt: TuiPromptInfo, input: string): TuiPromptInfo {
134
145
  }
135
146
  }
136
147
 
137
- function currentPromptText(state: PluginState): string {
138
- return state.promptRef?.current.input ?? ""
148
+ function samePromptTarget(left: PromptTarget | undefined, right: PromptTarget | undefined): boolean {
149
+ if (!left || !right || left.name !== right.name) return false
150
+ if (left.name === "home" && right.name === "home") {
151
+ return left.workspaceID === right.workspaceID
152
+ }
153
+ if (left.name === "session" && right.name === "session") {
154
+ return left.sessionID === right.sessionID
155
+ }
156
+ return false
157
+ }
158
+
159
+ function resolvePromptTarget(route: TuiRouteCurrent, workspaceID?: string): PromptTarget | undefined {
160
+ if (route.name === "home") return { name: "home", workspaceID }
161
+ if (isSessionRoute(route)) {
162
+ return { name: "session", sessionID: route.params.sessionID }
163
+ }
164
+ return undefined
165
+ }
166
+
167
+ function isPromptTargetActive(route: TuiRouteCurrent, target: PromptTarget): boolean {
168
+ if (target.name === "home") return route.name === "home"
169
+ return isSessionRoute(route) && route.params.sessionID === target.sessionID
139
170
  }
140
171
 
141
- function bindPromptRef(state: PluginState, forwarded: ((ref: TuiPromptRef | undefined) => void) | undefined, ref: TuiPromptRef | undefined): void {
142
- state.promptRef = ref
172
+ function currentPromptTarget(api: Api, state: PluginState): PromptTarget | undefined {
173
+ return state.promptTarget ?? resolvePromptTarget(api.route.current)
174
+ }
175
+
176
+ function isPromptHandleActive(api: Api, state: PluginState, handle: PromptHandle): boolean {
177
+ if (!isPromptTargetActive(api.route.current, handle.target)) return false
178
+ if (api.state.path.directory !== handle.directory) return false
179
+
180
+ if (handle.ref) {
181
+ return state.promptRef === handle.ref && samePromptTarget(state.promptTarget, handle.target)
182
+ }
183
+
184
+ return true
185
+ }
186
+
187
+ function bindPromptRef(
188
+ state: PluginState,
189
+ target: PromptTarget,
190
+ forwarded: ((ref: TuiPromptRef | undefined) => void) | undefined,
191
+ ref: TuiPromptRef | undefined,
192
+ ): void {
193
+ if (ref) {
194
+ state.promptRef = ref
195
+ state.promptTarget = target
196
+ } else if (samePromptTarget(state.promptTarget, target)) {
197
+ state.promptRef = undefined
198
+ state.promptTarget = undefined
199
+ }
200
+
143
201
  forwarded?.(ref)
144
202
  }
145
203
 
146
- async function writePrompt(api: Api, state: PluginState, input: string, signal: AbortSignal, template?: TuiPromptInfo): Promise<void> {
147
- const promptRef = state.promptRef
204
+ async function clearPrompt(api: Api, state: PluginState, handle: PromptHandle, signal: AbortSignal, template?: TuiPromptInfo): Promise<boolean> {
205
+ if (!isPromptHandleActive(api, state, handle)) return false
206
+
207
+ const promptRef = handle.ref
208
+ if (promptRef) {
209
+ promptRef.set(nextPromptInfo(template ?? promptRef.current, ""))
210
+ return true
211
+ }
212
+
213
+ await api.client.tui.clearPrompt({ directory: handle.directory }, { signal, throwOnError: true } as const)
214
+ return true
215
+ }
216
+
217
+ async function writePrompt(
218
+ api: Api,
219
+ state: PluginState,
220
+ handle: PromptHandle,
221
+ input: string,
222
+ signal: AbortSignal,
223
+ template?: TuiPromptInfo,
224
+ ): Promise<boolean> {
225
+ if (!isPromptHandleActive(api, state, handle)) return false
226
+
227
+ const promptRef = handle.ref
148
228
  if (promptRef) {
149
229
  promptRef.set(nextPromptInfo(template ?? promptRef.current, input))
150
230
  promptRef.focus()
151
- return
231
+ return true
152
232
  }
153
233
 
154
- const directory = api.state.path.directory
155
234
  const requestOptions = { signal, throwOnError: true } as const
156
- await api.client.tui.clearPrompt({ directory }, requestOptions)
235
+ await api.client.tui.clearPrompt({ directory: handle.directory }, requestOptions)
157
236
  if (input) {
158
- await api.client.tui.appendPrompt({ directory, text: input }, requestOptions)
237
+ await api.client.tui.appendPrompt({ directory: handle.directory, text: input }, requestOptions)
159
238
  }
239
+ return true
160
240
  }
161
241
 
162
- async function restorePrompt(api: Api, state: PluginState, prompt: TuiPromptInfo, signal: AbortSignal): Promise<void> {
163
- const promptRef = state.promptRef
242
+ async function restorePrompt(
243
+ api: Api,
244
+ state: PluginState,
245
+ handle: PromptHandle,
246
+ prompt: TuiPromptInfo,
247
+ signal: AbortSignal,
248
+ ): Promise<boolean> {
249
+ if (!isPromptHandleActive(api, state, handle)) return false
250
+
251
+ const promptRef = handle.ref
164
252
  if (promptRef) {
165
253
  promptRef.set(clonePromptInfo(prompt))
166
254
  promptRef.focus()
167
- return
255
+ return true
168
256
  }
169
257
 
170
- await writePrompt(api, state, prompt.input, signal, prompt)
258
+ return writePrompt(api, state, handle, prompt.input, signal, prompt)
171
259
  }
172
260
 
173
261
  function gatherContext(api: Api): string {
@@ -297,7 +385,19 @@ function openEnhanceDialog(
297
385
 
298
386
  if (signal.aborted) return
299
387
 
300
- const initialValue = currentPromptText(state)
388
+ const target = currentPromptTarget(api, state)
389
+ if (!target) {
390
+ api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt enhancement is only available from a prompt view." })
391
+ return
392
+ }
393
+
394
+ const handle: PromptHandle = {
395
+ target,
396
+ directory: api.state.path.directory,
397
+ ref: state.promptRef,
398
+ }
399
+ const originalPrompt = handle.ref ? clonePromptInfo(handle.ref.current) : undefined
400
+ const initialValue = originalPrompt?.input ?? ""
301
401
 
302
402
  api.ui.dialog.replace(() => (
303
403
  <api.ui.DialogPrompt
@@ -313,11 +413,13 @@ function openEnhanceDialog(
313
413
  return
314
414
  }
315
415
 
316
- const originalPrompt = state.promptRef ? clonePromptInfo(state.promptRef.current) : undefined
317
- state.enhancing = true
318
- if (originalPrompt) {
319
- state.promptRef?.set(nextPromptInfo(originalPrompt, ""))
416
+ if (!isPromptHandleActive(api, state, handle)) {
417
+ api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while the dialog was open." })
418
+ api.ui.dialog.clear()
419
+ return
320
420
  }
421
+
422
+ state.enhancing = true
321
423
  api.ui.dialog.clear()
322
424
  api.ui.toast({
323
425
  variant: "info",
@@ -328,10 +430,29 @@ function openEnhanceDialog(
328
430
 
329
431
  void (async () => {
330
432
  try {
433
+ const cleared = await clearPrompt(api, state, handle, signal, originalPrompt)
434
+ if (!cleared) {
435
+ api.ui.toast({
436
+ variant: "warning",
437
+ title: TOAST_TITLE,
438
+ message: "Prompt changed before enhancement started.",
439
+ })
440
+ return
441
+ }
442
+
331
443
  const enhanced = await enhanceWithModel(api, options, input, signal)
332
444
  if (signal.aborted) return
333
445
 
334
- await writePrompt(api, state, enhanced, signal, originalPrompt)
446
+ const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt)
447
+ if (!wrote) {
448
+ api.ui.toast({
449
+ variant: "warning",
450
+ title: TOAST_TITLE,
451
+ message: "Enhanced prompt is ready, but the original prompt is no longer active.",
452
+ })
453
+ return
454
+ }
455
+
335
456
  api.ui.toast({
336
457
  variant: "success",
337
458
  title: "Prompt enhanced",
@@ -341,11 +462,26 @@ function openEnhanceDialog(
341
462
  } catch (error) {
342
463
  if (signal.aborted) return
343
464
 
465
+ let restored = true
344
466
  if (originalPrompt) {
345
- await restorePrompt(api, state, originalPrompt, signal)
467
+ try {
468
+ restored = await restorePrompt(api, state, handle, originalPrompt, signal)
469
+ } catch {
470
+ // Best-effort restore; do not suppress the error toast.
471
+ restored = false
472
+ }
473
+ } else {
474
+ try {
475
+ restored = await writePrompt(api, state, handle, input, signal)
476
+ } catch {
477
+ restored = false
478
+ }
346
479
  }
347
480
 
348
- const message = error instanceof Error ? error.message : "Model enhancement failed."
481
+ const baseMessage = error instanceof Error ? error.message : "Model enhancement failed."
482
+ const message = restored
483
+ ? baseMessage
484
+ : `${baseMessage} Original prompt could not be restored because the active prompt changed.`
349
485
  api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
350
486
  } finally {
351
487
  state.enhancing = false
@@ -364,7 +500,7 @@ const tui: TuiPlugin = async (api, options) => {
364
500
  home_prompt(_ctx, props) {
365
501
  return (
366
502
  <api.ui.Prompt
367
- ref={(ref) => bindPromptRef(state, props.ref, ref)}
503
+ ref={(ref) => bindPromptRef(state, { name: "home", workspaceID: props.workspace_id }, props.ref, ref)}
368
504
  workspaceID={props.workspace_id}
369
505
  right={<api.ui.Slot name="home_prompt_right" workspace_id={props.workspace_id} />}
370
506
  placeholders={HOME_PROMPT_PLACEHOLDERS}
@@ -374,7 +510,7 @@ const tui: TuiPlugin = async (api, options) => {
374
510
  session_prompt(_ctx, props) {
375
511
  return (
376
512
  <api.ui.Prompt
377
- ref={(ref) => bindPromptRef(state, props.ref, ref)}
513
+ ref={(ref) => bindPromptRef(state, { name: "session", sessionID: props.session_id }, props.ref, ref)}
378
514
  sessionID={props.session_id}
379
515
  visible={props.visible}
380
516
  disabled={props.disabled}
@@ -409,6 +545,8 @@ const tui: TuiPlugin = async (api, options) => {
409
545
  api.lifecycle.onDispose(() => {
410
546
  unregister()
411
547
  state.enhancing = false
548
+ state.promptRef = undefined
549
+ state.promptTarget = undefined
412
550
  })
413
551
  }
414
552