dsh-one-click-polish 1.2.1 → 1.2.2

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 (3) hide show
  1. package/client.js +83 -5
  2. package/index.js +64 -1
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -41,6 +41,7 @@ window.__ModuleLoader__.load({
41
41
  const PROGRESS = ENDPOINT.replace(/\/polish$/, '/progress')
42
42
  const PILOT = ENDPOINT.replace(/\/polish$/, '/autopilot')
43
43
  const RUNS = ENDPOINT.replace(/\/polish$/, '/runs')
44
+ const VERSION = ENDPOINT.replace(/\/polish$/, '/version')
44
45
 
45
46
  /** The in-flight request, so the panel can cancel it. */
46
47
  let activeAbort = null
@@ -253,8 +254,8 @@ window.__ModuleLoader__.load({
253
254
  'auto.warn.body': '启动后 AI 会自动往这个会话里发送消息:读对话 → 写下一步提示词 → 发给助手,一轮接一轮。它会消耗模型额度,也可能做出你没想到的改动。随时可以中断或结束循环。',
254
255
  'auto.warn.cancel': '再想想',
255
256
  'auto.warn.confirm': '确认启动',
256
- 'auto.interrupt': '跳过这一轮',
257
- 'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
257
+ 'auto.interrupt': '跳过这一轮',
258
+ 'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
258
259
  'auto.stop.hint': '整个自动驾驶停下。之后可以从这里继续。',
259
260
  'auto.stop': '停止自动',
260
261
  'auto.running': '运行中',
@@ -281,6 +282,13 @@ window.__ModuleLoader__.load({
281
282
  'settings.idleWait': '等待助手完成上一轮的上限',
282
283
  'settings.idleWait.hint': '自动驾驶要发下一条时,如果助手还在处理,它会等。超过这个上限就先停下,点「继续」可以接着来。长会话需要更长的时间。',
283
284
  'settings.idleWait.unit': '分钟',
285
+ 'version.current': '当前版本',
286
+ 'version.latest': '已是最新',
287
+ 'version.newest': 'npm 上有',
288
+ 'version.checking': '检查更新中…',
289
+ 'version.unknown': '查不到最新版本',
290
+ 'version.how': '怎么更新',
291
+ 'version.recheck': '重新检查',
284
292
  'settings.explore': '检索预算',
285
293
  'settings.explore.hint': '标准/深度润色时会先查资料。这是查资料总共能用多久、最多查几轮。查完后再改写,两者互不占用。',
286
294
  'settings.explore.unit': '秒',
@@ -412,8 +420,8 @@ window.__ModuleLoader__.load({
412
420
  'auto.warn.body': 'The AI will send messages into this conversation by itself: read the history, write the next prompt, send it, round after round. It spends model quota and may change things you did not expect. You can interrupt or end the loop at any time.',
413
421
  'auto.warn.cancel': 'Not now',
414
422
  'auto.warn.confirm': 'Start',
415
- 'auto.interrupt': 'Skip this round',
416
- 'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
423
+ 'auto.interrupt': 'Skip this round',
424
+ 'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
417
425
  'auto.stop.hint': 'End the autopilot. You can continue from here afterwards.',
418
426
  'auto.stop': 'Stop autopilot',
419
427
  'auto.running': 'Running',
@@ -440,6 +448,13 @@ window.__ModuleLoader__.load({
440
448
  'settings.idleWait': 'How long to wait for the assistant',
441
449
  'settings.idleWait.hint': 'When the loop is ready to send but the assistant is still working, it waits. Past this limit it parks, and Continue picks it up again. Long conversations need more time.',
442
450
  'settings.idleWait.unit': 'minutes',
451
+ 'version.current': 'Version',
452
+ 'version.latest': 'Up to date',
453
+ 'version.newest': 'npm has',
454
+ 'version.checking': 'Checking for updates…',
455
+ 'version.unknown': 'Cannot reach npm',
456
+ 'version.how': 'How to update',
457
+ 'version.recheck': 'Check again',
443
458
  'settings.explore': 'Lookup budget',
444
459
  'settings.explore.hint': 'A standard or deep polish looks things up first. This is how long that may take in total, and how many rounds it may spend. The rewrite runs separately with its own budget.',
445
460
  'settings.explore.unit': 's',
@@ -564,6 +579,21 @@ window.__ModuleLoader__.load({
564
579
  .dsh-op-head{margin-bottom:18px}
565
580
  .dsh-op-h2{margin:0 0 4px;font-size:17px;font-weight:600;line-height:26px;letter-spacing:-.01em}
566
581
  .dsh-op-sub{margin:0;color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:20px}
582
+ /* Version line: this plugin's own update affordance. dsh's plugin page only
583
+ computes installTarget for bundles in its official catalogue, so a third-party
584
+ plugin has no update button there. */
585
+ .dsh-op-version{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:9px;
586
+ font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-secondary)}
587
+ .dsh-op-versionNow{font-variant-numeric:tabular-nums;
588
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;
589
+ background:var(--dsw-alias-bg-base);border-radius:999px;padding:1px 8px;
590
+ color:var(--dsw-alias-label-primary)}
591
+ .dsh-op-versionNew{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
592
+ font-size:11.5px;color:var(--dsw-alias-state-warn-primary)}
593
+ .dsh-op-versionBad{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));opacity:.8}
594
+ /* The busy-wait line: a live counter, so it is monospaced and does not reflow as
595
+ the seconds tick, and it holds a fixed height so the panel does not jump. */
596
+ .dsh-op-waiting{font-variant-numeric:tabular-nums;min-height:17px}
567
597
  .dsh-op-card{display:flex;flex-direction:column;gap:2px;padding:2px 16px;
568
598
  background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);
569
599
  border-radius:14px}
@@ -2266,6 +2296,8 @@ window.__ModuleLoader__.load({
2266
2296
  const [form, setForm] = useState(null) // what the user is editing
2267
2297
  const [efforts, setEfforts] = useState({ state: 'idle', list: [], defaultEffort: null })
2268
2298
  const [saveState, setSaveState] = useState({ state: 'idle', message: '' })
2299
+ // null while the first lookup is in flight; afterwards { current, latest, error }
2300
+ const [version, setVersion] = useState(null)
2269
2301
  const [runs, setRuns] = useState([])
2270
2302
  const [runsPick, setRunsPick] = useState(null)
2271
2303
  const [runEntries, setRunEntries] = useState([])
@@ -2317,6 +2349,24 @@ window.__ModuleLoader__.load({
2317
2349
 
2318
2350
  useEffect(() => { load() }, [load])
2319
2351
 
2352
+ /** Read this plugin's version against npm. Cached on the Host for 10 minutes. */
2353
+ const checkVersion = useCallback(async (force) => {
2354
+ try {
2355
+ const response = await fetch(force === true ? `${VERSION}?refresh=1` : VERSION)
2356
+ const payload = await response.json()
2357
+ if (payload?.ok !== true) throw new Error(payload?.error ?? 'HTTP error')
2358
+ setVersion({
2359
+ current: typeof payload.current === 'string' ? payload.current : '',
2360
+ latest: typeof payload.latest === 'string' ? payload.latest : null,
2361
+ error: typeof payload.error === 'string' ? payload.error : null,
2362
+ })
2363
+ } catch (failure) {
2364
+ setVersion({ current: '', latest: null, error: String(failure?.message ?? failure) })
2365
+ }
2366
+ }, [])
2367
+
2368
+ useEffect(() => { void checkVersion(false) }, [checkVersion])
2369
+
2320
2370
  /** Sessions that have an archived run, newest first. */
2321
2371
  const loadRuns = useCallback(async () => {
2322
2372
  try {
@@ -2462,7 +2512,35 @@ window.__ModuleLoader__.load({
2462
2512
  const pageBody = h('div', { className: 'dsh-op-page' },
2463
2513
  h('div', { className: 'dsh-op-head' },
2464
2514
  h('h2', { className: 'dsh-op-h2' }, t('settings.title')),
2465
- h('p', { className: 'dsh-op-sub' }, t('settings.sub'))),
2515
+ h('p', { className: 'dsh-op-sub' }, t('settings.sub')),
2516
+ // dsh's own plugin page can only offer 更新 for bundles in its official
2517
+ // catalogue, so a third-party plugin gets no update affordance there.
2518
+ // The version line puts that question here instead.
2519
+ h('div', { className: 'dsh-op-version' },
2520
+ h('span', { className: 'dsh-op-versionNow' },
2521
+ `${t('version.current')} ${version.current === '' ? '?' : version.current}`),
2522
+ version === null
2523
+ ? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.checking'))
2524
+ : version.error !== null && version.error !== ''
2525
+ ? h('span', { className: 'dsh-op-versionBad' },
2526
+ `${t('version.unknown')} · ${version.error}`)
2527
+ : version.latest === null
2528
+ ? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.unknown'))
2529
+ : version.latest !== version.current
2530
+ ? h('div', { className: 'dsh-op-versionNew' },
2531
+ h('span', null, `${t('version.newest')} ${version.latest}`),
2532
+ h('a', {
2533
+ className: 'dsh-op-link',
2534
+ href: 'https://www.npmjs.com/package/dsh-one-click-polish',
2535
+ target: '_blank',
2536
+ rel: 'noreferrer',
2537
+ }, t('version.how')),
2538
+ h('button', {
2539
+ type: 'button',
2540
+ className: 'dsh-op-link',
2541
+ onClick: () => { void checkVersion(true) },
2542
+ }, t('version.recheck')))
2543
+ : h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.latest')))),
2466
2544
  h('div', { className: 'dsh-op-card' },
2467
2545
  h('div', { className: 'dsh-op-cardTitle' }, t('settings.group.model')),
2468
2546
  h('div', { className: 'dsh-op-field' },
package/index.js CHANGED
@@ -23,7 +23,8 @@ export const name = 'one-click-polish'
23
23
  export const inject = ['llm', 'agents', 'webServer']
24
24
 
25
25
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
26
- import { join } from 'node:path'
26
+ import { dirname, join } from 'node:path'
27
+ import { fileURLToPath } from 'node:url'
27
28
 
28
29
  const PREFIX = '/plugins/dsh-one-click-polish'
29
30
  const DEFAULT_MAX_INPUT_CHARS = 12000
@@ -466,6 +467,61 @@ async function produceRewrite(ctx, settings, mode, strength, research, input, ag
466
467
  return text
467
468
  }
468
469
 
470
+ /**
471
+ * This plugin's own version, and the newest one on npm.
472
+ *
473
+ * dsh's plugin page only offers an update button for bundles in its official
474
+ * catalogue, because `installTarget` is computed from that catalogue. A third-party
475
+ * plugin therefore has no update affordance there at all, so the question "is there
476
+ * a newer version?" is answered here instead, on the plugin's own settings page.
477
+ */
478
+ const VERSION_TTL_MS = 10 * 60 * 1000
479
+ let versionCache = { at: 0, latest: null, error: null }
480
+
481
+ async function versionInfo() {
482
+ let current = ''
483
+ try {
484
+ // Resolve from this module rather than the process cwd: the Host may run with
485
+ // any working directory, and this file sits next to its own manifest.
486
+ const here = dirname(fileURLToPath(import.meta.url))
487
+ const manifest = JSON.parse(readFileSync(join(here, 'package.json'), 'utf8'))
488
+ current = typeof manifest.version === 'string' ? manifest.version : ''
489
+ } catch { /* a missing manifest just means no version to report */ }
490
+
491
+ if (versionCache.latest !== null && Date.now() - versionCache.at < VERSION_TTL_MS) {
492
+ return { current, latest: versionCache.latest, error: null, checked: versionCache.at }
493
+ }
494
+
495
+ // The registry is the only source of truth for "what is published"; a failed
496
+ // lookup must not look like "you are up to date".
497
+ let latest = null
498
+ let error = null
499
+ try {
500
+ const controller = new AbortController()
501
+ const timer = setTimeout(() => controller.abort(), 8000)
502
+ const response = await fetch('https://registry.npmjs.org/dsh-one-click-polish/latest', {
503
+ signal: controller.signal,
504
+ headers: { accept: 'application/json' },
505
+ })
506
+ clearTimeout(timer)
507
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
508
+ const payload = await response.json()
509
+ latest = typeof payload?.version === 'string' ? payload.version : null
510
+ } catch (failure) {
511
+ error = textOf(failure)
512
+ }
513
+ versionCache = { at: Date.now(), latest, error }
514
+ return { current, latest, error, checked: Date.now() }
515
+ }
516
+
517
+ async function versionRoute(req, res) {
518
+ // `refresh=1` drops the TTL cache: the panel offers a recheck, and a button that
519
+ // silently answers from cache would look broken.
520
+ const url = new URL(req.url ?? '/', 'http://localhost')
521
+ if (url.searchParams.get('refresh') === '1') versionCache.at = 0
522
+ sendJson(res, 200, { ok: true, ...(await versionInfo()) })
523
+ }
524
+
469
525
  async function statusRoute(ctx, req, res) {
470
526
  const url = new URL(req.url ?? '/', 'http://localhost')
471
527
  const sessionId = url.searchParams.get('sessionId') ?? undefined
@@ -2466,6 +2522,13 @@ export function apply(ctx, config) {
2466
2522
  })
2467
2523
 
2468
2524
 
2525
+ ctx.effect(() => server.register({
2526
+ kind: 'exact',
2527
+ path: `${PREFIX}/version`,
2528
+ handler: (req, res) => versionRoute(req, res)
2529
+ .catch(error => sendJson(res, 500, { ok: false, error: textOf(error) })),
2530
+ }), 'one-click-polish: version route')
2531
+
2469
2532
  ctx.effect(() => server.register({
2470
2533
  kind: 'exact',
2471
2534
  path: `${PREFIX}/status`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-one-click-polish",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "One-click polish, expand and autopilot for the DeepSeek Harness composer: rewrite the draft with the model the session is switched to, expand a one-line wish into a task brief, or let an AI drive a goal round after round.",
5
5
  "keywords": [
6
6
  "dsh",