dsh-autorestart 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -7,7 +7,7 @@ Restart DeepSeek Harness without losing the work that is currently running.
7
7
  [![dsh-plugin](https://img.shields.io/github/topics/dsh-plugin?style=flat-square)](https://github.com/topics/dsh-plugin)
8
8
  [![language](https://img.shields.io/badge/language-JavaScript-yellow?style=flat-square)](https://github.com/Eason-22/dsh-autorestart)
9
9
 
10
- Chinese version: [README.zh.md](README.zh.md)
10
+ Chinese version: [README.zh.md](docs/README.zh.md)
11
11
 
12
12
  ## What it does
13
13
 
package/client.js CHANGED
@@ -93,6 +93,7 @@ window.__ModuleLoader__.load({
93
93
  parkBusy: '停泊中…',
94
94
  parkError: '停泊失败:{message}',
95
95
  tooltip: '等待其他会话空闲后自动重启;重启后会自动继续本会话进度',
96
+ tooltipBusy: '等待其他会话空闲后自动重启(计数含“排队但还没开始”的任务);重启后本会话会自动继续进度',
96
97
  tooltipWithError: '等待其他会话空闲后自动重启;重启后会自动继续本会话进度。{message}',
97
98
  dotAria: '待重启',
98
99
  panelHeading: '待重启',
@@ -127,6 +128,8 @@ window.__ModuleLoader__.load({
127
128
  parkError: 'Park failed: {message}',
128
129
  tooltip:
129
130
  'Waits for the other sessions to become idle, then restarts automatically; this session resumes its progress after the restart.',
131
+ tooltipBusy:
132
+ 'Waits for the other sessions to become idle, then restarts automatically (the count includes work that is queued but not started yet); this session resumes its progress after the restart.',
130
133
  tooltipWithError:
131
134
  'Waits for the other sessions to become idle, then restarts automatically; this session resumes its progress after the restart. {message}',
132
135
  dotAria: 'Pending restart',
@@ -492,13 +495,11 @@ window.__ModuleLoader__.load({
492
495
  const sessionId = props && props.sessionId !== undefined ? props.sessionId : null
493
496
  const hasSession = sessionId !== null && sessionId !== ''
494
497
  const info = useSharedInfo(hasSession)
495
- const [open, setOpen] = useState(false)
496
498
  // The session id of the goal that is currently being parked, or null.
497
499
  const [parking, setParking] = useState(null)
498
500
  // The message of the last failed park request (the Host's own message).
501
+ // It is shown in the pill's tooltip: the pill has no details panel.
499
502
  const [parkError, setParkError] = useState('')
500
- const [, setTick] = useState(0)
501
- const wrapperRef = useRef(null)
502
503
  const t = translate ?? ((name) => name)
503
504
 
504
505
  const pending = Boolean(info && info.pendingRestart === true)
@@ -506,43 +507,14 @@ window.__ModuleLoader__.load({
506
507
  // happens as soon as this poll cycle ends.
507
508
  const pulsing = pending && Boolean(info.allowRestart === true) && !prefersReducedMotion()
508
509
 
509
- // The panel never outlives the pending state.
510
+ // A park error never outlives the pending state.
510
511
  useEffect(() => {
511
512
  if (!pending) {
512
- setOpen(false)
513
513
  setParkError('')
514
514
  setParking(null)
515
515
  }
516
516
  }, [pending])
517
517
 
518
- // Clicking outside the panel or pressing Escape closes it. Both
519
- // listeners are removed when the panel closes and on unmount.
520
- useEffect(() => {
521
- if (!open) return undefined
522
- const onPointerDown = (event) => {
523
- const node = wrapperRef.current
524
- if (node && event && event.target && typeof node.contains === 'function'
525
- && node.contains(event.target)) return
526
- setOpen(false)
527
- }
528
- const onKeyDown = (event) => {
529
- if (event && event.key === 'Escape') setOpen(false)
530
- }
531
- document.addEventListener('mousedown', onPointerDown, true)
532
- document.addEventListener('keydown', onKeyDown, true)
533
- return () => {
534
- document.removeEventListener('mousedown', onPointerDown, true)
535
- document.removeEventListener('keydown', onKeyDown, true)
536
- }
537
- }, [open])
538
-
539
- // A one-second tick keeps the "waiting for" duration in the panel fresh.
540
- useEffect(() => {
541
- if (!open) return undefined
542
- const timer = setInterval(() => setTick((value) => value + 1), 1000)
543
- return () => clearInterval(timer)
544
- }, [open])
545
-
546
518
  if (!info || info.pendingRestart !== true) return null
547
519
 
548
520
  const waiters = Array.isArray(info.waiters) ? info.waiters : []
@@ -551,15 +523,22 @@ window.__ModuleLoader__.load({
551
523
  if (!waiting) return null
552
524
 
553
525
  const blockedBy = Array.isArray(info.blockedBy) ? info.blockedBy : []
554
- const others = blockedBy.filter((entry) => entry && String(entry.sessionId) !== key)
526
+ // One entry per SESSION: a conversation that is running *and* still has
527
+ // queued work is one conversation, so the pill never counts it twice.
528
+ const seenSessions = new Set()
529
+ const others = blockedBy.filter((entry) => {
530
+ if (!entry) return false
531
+ const id = String(entry.sessionId)
532
+ if (id === key || seenSessions.has(id)) return false
533
+ seenSessions.add(id)
534
+ return true
535
+ })
555
536
  // A goal-driven blocker: an armed goal that keeps driving rounds by
556
537
  // itself (`goal-active`, parkable) or a goal whose round budget is used
557
538
  // up (`goal-exhausted`, not parkable at all).
558
539
  const goalActive = others.filter((entry) => String(entry.reason) === 'goal-active')
559
540
  const goalExhausted = others.filter((entry) => String(entry.reason) === 'goal-exhausted')
560
541
  const otherBusy = others.length - goalActive.length - goalExhausted.length
561
- // The goals the Host already parked: they continue after the restart.
562
- const parkedGoals = Array.isArray(info.parkedGoals) ? info.parkedGoals : []
563
542
 
564
543
  const status = goalActive.length > 0
565
544
  ? t('goalActive')
@@ -571,16 +550,6 @@ window.__ModuleLoader__.load({
571
550
 
572
551
  if (pulsing) ensurePulseStyle()
573
552
 
574
- const toggle = () => setOpen((value) => !value)
575
- const onKeyDown = (event) => {
576
- if (!event) return
577
- const pressed = event.key
578
- if (pressed === 'Enter' || pressed === ' ' || pressed === 'Spacebar') {
579
- event.preventDefault()
580
- toggle()
581
- }
582
- }
583
-
584
553
  /**
585
554
  * Park the goal of one blocking session and ask for the restart now.
586
555
  *
@@ -612,7 +581,6 @@ window.__ModuleLoader__.load({
612
581
  ? (body.error || body.message || '')
613
582
  : ''
614
583
  setParkError(fill(t('parkError'), { message: message || String(response.status) }))
615
- setOpen(true)
616
584
  return
617
585
  }
618
586
  if (body && Array.isArray(body.reported)) {
@@ -621,96 +589,18 @@ window.__ModuleLoader__.load({
621
589
  )
622
590
  if (reported) {
623
591
  setParkError(fill(t('parkError'), { message: String(reported.reason || 'not-parked') }))
624
- setOpen(true)
625
592
  }
626
593
  }
627
594
  } catch (error) {
628
595
  setParkError(fill(t('parkError'), { message: describeError(error) }))
629
- setOpen(true)
630
596
  } finally {
631
597
  setParking(null)
632
598
  }
633
599
  }
634
600
 
635
- const pendingSince = stateStore.pendingSince
636
- const waitingMs = pendingSince === null || pendingSince === undefined
637
- ? 0
638
- : Math.max(0, Date.now() - pendingSince)
639
-
640
- const panelLines = [
641
- h('div', { key: 'heading', style: PANEL_HEADING_STYLE }, t('panelHeading')),
642
- h(
643
- 'div',
644
- { key: 'state', style: PANEL_LINE_STYLE },
645
- goalActive.length > 0
646
- ? t('panelStateGoalActive')
647
- : goalExhausted.length > 0
648
- ? t('panelStateGoalExhausted')
649
- : otherBusy > 0
650
- ? t('panelStateBusy')
651
- : t('panelStateIdle'),
652
- ),
653
- ]
654
- if (parkError) {
655
- panelLines.push(h('div', { key: 'error', style: PANEL_ERROR_STYLE }, parkError))
656
- }
657
- others.forEach((entry, index) => {
658
- const title = entry && typeof entry.title === 'string' ? entry.title.trim() : ''
659
- const reason = entry && typeof entry.reason === 'string' ? entry.reason : ''
660
- const name = title || String(entry && entry.sessionId)
661
- if (reason === 'goal-active') {
662
- panelLines.push(h(
663
- 'div',
664
- { key: 'blocked-' + index, style: PANEL_LINE_STYLE },
665
- fill(t('panelGoalActiveLine'), { title: name }),
666
- ))
667
- return
668
- }
669
- if (reason === 'goal-exhausted') {
670
- panelLines.push(h(
671
- 'div',
672
- { key: 'blocked-' + index, style: PANEL_LINE_STYLE },
673
- fill(t('panelGoalExhaustedLine'), { title: name }),
674
- ))
675
- return
676
- }
677
- panelLines.push(h(
678
- 'div',
679
- { key: 'blocked-' + index, style: PANEL_LINE_STYLE },
680
- title ? fill(t('panelBlockedTitle'), { title }) : t('panelBlockedGeneric'),
681
- ))
682
- })
683
- if (parkedGoals.length > 0) {
684
- panelLines.push(h(
685
- 'div',
686
- { key: 'parked-title', style: PANEL_MUTED_STYLE },
687
- t('panelParkedTitle'),
688
- ))
689
- parkedGoals.forEach((entry, index) => {
690
- const parkedSession = entry && entry.sessionId != null ? String(entry.sessionId) : ''
691
- const parkedGoal = entry && entry.goalId != null ? String(entry.goalId) : '-'
692
- panelLines.push(h(
693
- 'div',
694
- { key: 'parked-' + index, style: PANEL_LINE_STYLE },
695
- fill(t('panelParkedGoal'), { session: parkedSession, goal: parkedGoal }),
696
- ))
697
- })
698
- }
699
- panelLines.push(h(
700
- 'div',
701
- { key: 'waiting', style: PANEL_LINE_STYLE },
702
- fill(t('panelWaiting'), { duration: formatWaiting(waitingMs, t) }),
703
- ))
704
- panelLines.push(h(
705
- 'div',
706
- { key: 'resume', style: PANEL_MUTED_STYLE },
707
- t('panelResume'),
708
- ))
709
-
710
601
  return h(
711
602
  'div',
712
603
  {
713
- ref: wrapperRef,
714
604
  style: WRAPPER_STYLE,
715
605
  role: 'status',
716
606
  'aria-live': 'polite',
@@ -721,13 +611,7 @@ window.__ModuleLoader__.load({
721
611
  style: PILL_STYLE,
722
612
  title: parkError
723
613
  ? fill(t('tooltipWithError'), { message: parkError })
724
- : t('tooltip'),
725
- role: 'button',
726
- tabIndex: 0,
727
- 'aria-haspopup': 'dialog',
728
- 'aria-expanded': open ? 'true' : 'false',
729
- onClick: toggle,
730
- onKeyDown,
614
+ : (otherBusy > 0 ? t('tooltipBusy') : t('tooltip')),
731
615
  },
732
616
  h('span', { style: dotStyle(DOT_STYLE, pulsing), 'aria-hidden': 'true' }),
733
617
  h('span', { style: TEXT_STYLE }, h('b', null, t('label')), status),
@@ -737,7 +621,6 @@ window.__ModuleLoader__.load({
737
621
  const target = String(entry.sessionId)
738
622
  const busy = parking === target
739
623
  const onParkClick = (event) => {
740
- // The button must not toggle the details panel of the pill.
741
624
  if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
742
625
  void parkAndRestart(target)
743
626
  }
@@ -745,7 +628,6 @@ window.__ModuleLoader__.load({
745
628
  if (!event) return
746
629
  const pressed = event.key
747
630
  if (pressed === 'Enter' || pressed === ' ' || pressed === 'Spacebar') {
748
- // Same here: the pill's own Enter / Space handler must not run.
749
631
  if (typeof event.stopPropagation === 'function') event.stopPropagation()
750
632
  }
751
633
  }
@@ -764,25 +646,7 @@ window.__ModuleLoader__.load({
764
646
  busy ? t('parkBusy') : t('parkButton'),
765
647
  )
766
648
  }),
767
- parkedGoals.length > 0
768
- ? h(
769
- 'span',
770
- { style: PARKED_LINE_STYLE },
771
- fill(t('parkedGoals'), { n: parkedGoals.length }),
772
- )
773
- : null,
774
649
  ),
775
- open
776
- ? h(
777
- 'div',
778
- {
779
- style: PANEL_STYLE,
780
- role: 'dialog',
781
- 'aria-label': t('panelHeading'),
782
- },
783
- panelLines,
784
- )
785
- : null,
786
650
  )
787
651
  }
788
652
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "dsh-autorestart",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "DeepSeek Harness plugin: restart without losing work. Waits until every conversation is idle, restarts the host, then automatically resumes the interrupted sessions. AI-generated, not human-reviewed: use with caution.",
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "assets/*.png",
38
38
  "screenshots.json",
39
39
  "README.md",
40
- "README.zh.md"
40
+ "docs/*.md"
41
41
  ],
42
42
  "dsh": {
43
43
  "bundle": {
File without changes