dsh-pause-session 0.2.0 → 0.2.1
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/CHANGELOG.md +32 -39
- package/lib/client.js +95 -70
- package/lib/client.js.map +1 -1
- package/lib/index.js +187 -182
- package/lib/index.js.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/remote.d.ts +6 -0
- package/lib/types/composition.d.ts +35 -61
- package/lib/types/index.d.ts +1 -1
- package/lib/types/release-order.d.ts +8 -4
- package/lib/types/resume-activation.d.ts +14 -5
- package/lib/types/resume.d.ts +2 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,23 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
-
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project uses [Semantic Versioning](https://semver.org/).
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses [Semantic Versioning](https://semver.org/).
|
|
7
6
|
|
|
8
|
-
## [0.2.
|
|
7
|
+
## [0.2.1] - 2026-11-11
|
|
9
8
|
|
|
10
9
|
### Changed
|
|
11
10
|
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
- Adapt to DeepSeek Harness `0.2.2-rc.2`. Subagent children now resume through `ctx.subagents.prompt`, so the runtime owns their activation, composition, and tree membership; the plugin no longer loads them as independent Agents.
|
|
12
|
+
- Release ordering reads `listDescendants` depth and reuses the tree walk, so a tree release no longer reads the catalogs twice.
|
|
13
|
+
- The composition seam resolves a root Session's preset only; a child's composition is restored by the subagent runtime.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- The `parentAgent` resume option, `listChildren` catalog reads, and the child-composition branch this plugin used to assemble itself.
|
|
18
|
+
|
|
19
|
+
## [0.2.0] - 2026-11-08
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Both controls now use a native DSH Remote namespace and stream. The plugin registers no slash commands, so a status refresh never appends `command/run`/`command/done` records to a Session.
|
|
24
|
+
- Pause, resume, goal, and decision-list actions travel through the same native namespace on Web and Desktop.
|
|
17
25
|
|
|
18
26
|
### Added
|
|
19
27
|
|
|
20
|
-
- Global progress names every draining session and its running tools, and the
|
|
21
|
-
in-progress action is **Cancel this pause**.
|
|
28
|
+
- Global progress names every draining session and its running tools, and the in-progress action is **Cancel this pause**.
|
|
22
29
|
- The Remote namespace is owner-only.
|
|
23
30
|
- The client bundle uses a build-time codec-builder shim instead of bundling the full Zod runtime.
|
|
24
31
|
- One snapshot is shared by every connected Remote observer, and the snapshot projection shares its tree walks.
|
|
@@ -27,53 +34,39 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
27
34
|
|
|
28
35
|
### Fixed
|
|
29
36
|
|
|
30
|
-
- A park reaches `safe` only when the session-level flush actually ran; an
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- A restart-adopted park is no longer cancelled by the per-session button; it
|
|
34
|
-
stays on the decision path.
|
|
35
|
-
- Stream and triage failures render as unavailable instead of stale rows, and
|
|
36
|
-
triage decisions that the host refuses show the refusal.
|
|
37
|
+
- A park reaches `safe` only when the session-level flush actually ran; an unreadable record and a partially unread tree are reported instead of being counted clean.
|
|
38
|
+
- A restart-adopted park is no longer cancelled by the per-session button; it stays on the decision path.
|
|
39
|
+
- Stream and triage failures render as unavailable instead of stale rows, and triage decisions that the host refuses show the refusal.
|
|
37
40
|
|
|
38
41
|
### Removed
|
|
39
42
|
|
|
40
|
-
- The `/pause`, `/pause-resume`, `/pause-state`, `/pause-goal`, and
|
|
41
|
-
|
|
42
|
-
- The command-era `ReadBurst`/JSON-status-route client machinery and the
|
|
43
|
-
`@deepseek-ai/dsh-commands` dependency.
|
|
43
|
+
- The `/pause`, `/pause-resume`, `/pause-state`, `/pause-goal`, and `/pause-triage*` command surface.
|
|
44
|
+
- The command-era `ReadBurst`/JSON-status-route client machinery and the `@deepseek-ai/dsh-commands` dependency.
|
|
44
45
|
|
|
45
46
|
## [0.1.0] - 2026-11-02
|
|
46
47
|
|
|
47
48
|
### Added
|
|
48
49
|
|
|
49
|
-
- Pause every running DSH session and its subagents at the next model-request
|
|
50
|
-
|
|
51
|
-
- Session-header and whole-app controls, with `Pausing`, `Paused`, and
|
|
52
|
-
`All Paused` states.
|
|
50
|
+
- Pause every running DSH session and its subagents at the next model-request boundary, after the tool already running finishes.
|
|
51
|
+
- Session-header and whole-app controls, with `Pausing`, `Paused`, and `All Paused` states.
|
|
53
52
|
- Durable pause registry that re-holds parked sessions after a DSH restart.
|
|
54
|
-
- Clean resume that restores the recorded Agent preset composition, including
|
|
55
|
-
subagent delegation context and tool restrictions.
|
|
53
|
+
- Clean resume that restores the recorded Agent preset composition, including subagent delegation context and tool restrictions.
|
|
56
54
|
- Goal re-arm toggle for cleanly paused sessions.
|
|
57
|
-
- Decision list for sessions that did not pause cleanly, with continue,
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
user-facing README screenshot.
|
|
61
|
-
- `/pause`, `/pause-resume`, `/pause-all`, `/pause-state`, `/pause-goal`, and
|
|
62
|
-
`/pause-triage*` command surface.
|
|
55
|
+
- Decision list for sessions that did not pause cleanly, with continue, continue-with-note, review-only, leave, and goal actions.
|
|
56
|
+
- Localized English and Chinese UI copy, plugin-manager metadata, and a user-facing README screenshot.
|
|
57
|
+
- `/pause`, `/pause-resume`, `/pause-all`, `/pause-state`, `/pause-goal`, and `/pause-triage*` command surface.
|
|
63
58
|
|
|
64
59
|
### Changed
|
|
65
60
|
|
|
66
|
-
- The package is published as `dsh-pause-session`; the slash-command namespace
|
|
67
|
-
|
|
68
|
-
- One-shot and external subagent routes are reported as unresumable and are not
|
|
69
|
-
registered as local pause records.
|
|
61
|
+
- The package is published as `dsh-pause-session`; the slash-command namespace remains `/pause*`.
|
|
62
|
+
- One-shot and external subagent routes are reported as unresumable and are not registered as local pause records.
|
|
70
63
|
|
|
71
64
|
### Known Limitations
|
|
72
65
|
|
|
73
66
|
- Background jobs and shell tasks outside the agent loop are not stopped.
|
|
74
|
-
- A paused session still appears `running` to parts of DSH's session lifecycle;
|
|
75
|
-
exit, archive, compaction, and `wait_agent` can behave differently while held.
|
|
67
|
+
- A paused session still appears `running` to parts of DSH's session lifecycle; exit, archive, compaction, and `wait_agent` can behave differently while held.
|
|
76
68
|
- External one-shot subagent routes cannot be cold-resumed.
|
|
77
69
|
|
|
70
|
+
[0.2.1]: https://github.com/timho1047/dsh-pause-session/releases/tag/v0.2.1
|
|
78
71
|
[0.2.0]: https://github.com/timho1047/dsh-pause-session/releases/tag/v0.2.0
|
|
79
72
|
[0.1.0]: https://github.com/timho1047/dsh-pause-session/releases/tag/v0.1.0
|
package/lib/client.js
CHANGED
|
@@ -346,9 +346,9 @@ window.__ModuleLoader__.load({
|
|
|
346
346
|
unclean: 0
|
|
347
347
|
};
|
|
348
348
|
if (reading.kind === "unavailable") return {
|
|
349
|
-
visible:
|
|
349
|
+
visible: true,
|
|
350
350
|
enabled: false,
|
|
351
|
-
ariaKey: "unavailable.aria",
|
|
351
|
+
ariaKey: "all.unavailable.aria",
|
|
352
352
|
action: "none",
|
|
353
353
|
detail: "",
|
|
354
354
|
caveats: [],
|
|
@@ -651,47 +651,60 @@ window.__ModuleLoader__.load({
|
|
|
651
651
|
sessions: Object.fromEntries(Object.entries(wire.sessions).map(([id, status]) => [id, status]))
|
|
652
652
|
};
|
|
653
653
|
}
|
|
654
|
+
/** First retry delay after the stream drops, in milliseconds. */
|
|
655
|
+
const RECONNECT_BASE_MS = 300;
|
|
656
|
+
/** Ceiling for the reconnect backoff, in milliseconds. */
|
|
657
|
+
const RECONNECT_MAX_MS = 5e3;
|
|
654
658
|
/**
|
|
655
659
|
* Open the native status stream and expose its latest snapshot to both controls.
|
|
660
|
+
*
|
|
661
|
+
* The stream owns no retry of its own: a carrier can drop it on a mux reconnect,
|
|
662
|
+
* a host reload, or a network blip, and the controls must not stay unavailable
|
|
663
|
+
* until the page is refreshed. This source reconnects with a bounded backoff and
|
|
664
|
+
* republishes the new snapshot; while it is disconnected the last value is
|
|
665
|
+
* discarded, so readers see "unavailable" rather than a stale claim.
|
|
656
666
|
* @param remote - the generated Remote namespace.
|
|
657
667
|
* @param lifetime - client lifetime; aborting it releases the stream.
|
|
658
668
|
* @returns the cached source and its change subscription.
|
|
659
669
|
*/
|
|
660
670
|
function openRemoteSource(remote, lifetime) {
|
|
661
671
|
let latest;
|
|
662
|
-
let
|
|
663
|
-
let
|
|
672
|
+
let attempt = 0;
|
|
673
|
+
let timer;
|
|
674
|
+
/** Readers waiting for the first snapshot of any attempt. */
|
|
675
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
664
676
|
const listeners = /* @__PURE__ */ new Set();
|
|
665
677
|
const snapshotListeners = /* @__PURE__ */ new Set();
|
|
666
678
|
const notify = () => {
|
|
679
|
+
for (const wake of [...waiters]) wake();
|
|
680
|
+
waiters.clear();
|
|
667
681
|
for (const listener of [...listeners]) listener();
|
|
668
|
-
for (const listener of [...snapshotListeners]) listener(
|
|
682
|
+
for (const listener of [...snapshotListeners]) listener(latest);
|
|
669
683
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
684
|
+
const schedule = () => {
|
|
685
|
+
if (lifetime.aborted || timer !== void 0) return;
|
|
686
|
+
const delay = Math.min(RECONNECT_BASE_MS * 2 ** attempt, RECONNECT_MAX_MS);
|
|
687
|
+
attempt += 1;
|
|
688
|
+
timer = setTimeout(() => {
|
|
689
|
+
timer = void 0;
|
|
690
|
+
pump();
|
|
691
|
+
}, delay);
|
|
692
|
+
};
|
|
693
|
+
const pump = async () => {
|
|
694
|
+
if (lifetime.aborted) return;
|
|
674
695
|
try {
|
|
675
696
|
for await (const snapshot of remote.observe(lifetime)) {
|
|
697
|
+
attempt = 0;
|
|
676
698
|
latest = snapshotOf(snapshot);
|
|
677
|
-
first?.();
|
|
678
|
-
first = void 0;
|
|
679
699
|
notify();
|
|
680
700
|
}
|
|
681
|
-
} catch {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
latest = void 0;
|
|
689
|
-
}
|
|
690
|
-
first?.();
|
|
691
|
-
first = void 0;
|
|
692
|
-
notify();
|
|
693
|
-
}
|
|
694
|
-
})();
|
|
701
|
+
} catch {}
|
|
702
|
+
if (lifetime.aborted) return;
|
|
703
|
+
latest = void 0;
|
|
704
|
+
notify();
|
|
705
|
+
schedule();
|
|
706
|
+
};
|
|
707
|
+
pump();
|
|
695
708
|
return {
|
|
696
709
|
onChange: (listener) => {
|
|
697
710
|
listeners.add(listener);
|
|
@@ -706,20 +719,17 @@ window.__ModuleLoader__.load({
|
|
|
706
719
|
};
|
|
707
720
|
},
|
|
708
721
|
read: async (signal) => {
|
|
709
|
-
if (latest === void 0 && !
|
|
710
|
-
const
|
|
711
|
-
|
|
722
|
+
if (latest === void 0 && !lifetime.aborted) await new Promise((resolve) => {
|
|
723
|
+
const wake = () => {
|
|
724
|
+
waiters.delete(wake);
|
|
725
|
+
signal?.removeEventListener("abort", wake);
|
|
712
726
|
resolve();
|
|
713
727
|
};
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
done();
|
|
717
|
-
};
|
|
718
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
719
|
-
if (latest !== void 0 || failed) done();
|
|
728
|
+
waiters.add(wake);
|
|
729
|
+
signal?.addEventListener("abort", wake, { once: true });
|
|
720
730
|
});
|
|
721
731
|
signal?.throwIfAborted();
|
|
722
|
-
return
|
|
732
|
+
return latest;
|
|
723
733
|
}
|
|
724
734
|
};
|
|
725
735
|
}
|
|
@@ -1630,7 +1640,7 @@ window.__ModuleLoader__.load({
|
|
|
1630
1640
|
const reading = props.usePauseGlobal((state) => state.global);
|
|
1631
1641
|
const triage = props.usePauseGlobal((state) => state.triage);
|
|
1632
1642
|
const open = props.usePauseGlobal((state) => state.panelOpen);
|
|
1633
|
-
props.usePauseGlobal((state) => state.error);
|
|
1643
|
+
const error = props.usePauseGlobal((state) => state.error);
|
|
1634
1644
|
const sessions = props.usePauseGlobal((state) => state.sessions);
|
|
1635
1645
|
const address = useSessions(addressingSession);
|
|
1636
1646
|
const view = globalPauseView(reading, sessions);
|
|
@@ -1643,7 +1653,8 @@ window.__ModuleLoader__.load({
|
|
|
1643
1653
|
address,
|
|
1644
1654
|
visible
|
|
1645
1655
|
]);
|
|
1646
|
-
if (!visible || !view.visible
|
|
1656
|
+
if (!visible || !view.visible) return null;
|
|
1657
|
+
const actionable = view.enabled && address !== void 0;
|
|
1647
1658
|
const tip = view.caveats.map((caveat) => caveat.names.length === 0 ? t(caveat.key) : `${t(caveat.key)} (${caveat.names.join(", ")})`).join("\n");
|
|
1648
1659
|
return (0, react_jsx_runtime.jsxs)("span", {
|
|
1649
1660
|
className: button_module_css_default.dspPill,
|
|
@@ -1652,37 +1663,49 @@ window.__ModuleLoader__.load({
|
|
|
1652
1663
|
className: button_module_css_default.dspPillPanel,
|
|
1653
1664
|
role: "dialog",
|
|
1654
1665
|
"aria-label": t("triage.title"),
|
|
1655
|
-
children: [
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1666
|
+
children: [
|
|
1667
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1668
|
+
className: button_module_css_default.dspPillPanelHead,
|
|
1669
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1670
|
+
className: button_module_css_default.dspPillText,
|
|
1671
|
+
children: t("triage.title")
|
|
1672
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1673
|
+
type: "button",
|
|
1674
|
+
className: button_module_css_default.dspPillAction,
|
|
1675
|
+
"aria-label": t("triage.refresh.aria"),
|
|
1676
|
+
onClick: () => {
|
|
1677
|
+
setPanel(true);
|
|
1678
|
+
},
|
|
1679
|
+
children: t("triage.refresh.aria")
|
|
1680
|
+
})]
|
|
1681
|
+
}),
|
|
1682
|
+
error === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1683
|
+
className: button_module_css_default.dspPillError,
|
|
1684
|
+
role: "status",
|
|
1685
|
+
children: error
|
|
1686
|
+
}),
|
|
1687
|
+
view.running === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1688
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1689
|
+
children: view.running
|
|
1690
|
+
}),
|
|
1691
|
+
!list.visible ? (0, react_jsx_runtime.jsx)("p", {
|
|
1692
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1693
|
+
children: t("triage.reading")
|
|
1694
|
+
}) : list.unavailable ? (0, react_jsx_runtime.jsx)("p", {
|
|
1695
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1696
|
+
children: t("triage.unavailable")
|
|
1697
|
+
}) : list.rows.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
1698
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1699
|
+
children: t("triage.empty")
|
|
1700
|
+
}) : (0, react_jsx_runtime.jsx)("ul", {
|
|
1701
|
+
className: button_module_css_default.dspPillRows,
|
|
1702
|
+
children: list.rows.map((row) => (0, react_jsx_runtime.jsx)(TriageSessionRow, {
|
|
1703
|
+
row,
|
|
1704
|
+
decide,
|
|
1705
|
+
t
|
|
1706
|
+
}, row.sessionId))
|
|
1707
|
+
})
|
|
1708
|
+
]
|
|
1686
1709
|
}),
|
|
1687
1710
|
view.unclean === 0 ? null : (0, react_jsx_runtime.jsxs)("button", {
|
|
1688
1711
|
type: "button",
|
|
@@ -1703,10 +1726,10 @@ window.__ModuleLoader__.load({
|
|
|
1703
1726
|
className: view.labelKey === void 0 && view.detail.length === 0 ? `${button_module_css_default.dspPillBtn} ${button_module_css_default.dspPillIcon}` : button_module_css_default.dspPillBtn,
|
|
1704
1727
|
"aria-label": t(view.ariaKey),
|
|
1705
1728
|
"aria-pressed": view.action === "resume-all" || view.action === "cancel-all",
|
|
1706
|
-
disabled: !
|
|
1729
|
+
disabled: !actionable,
|
|
1707
1730
|
...tip.length === 0 ? {} : { title: tip },
|
|
1708
1731
|
onClick: () => {
|
|
1709
|
-
toggle(address);
|
|
1732
|
+
if (address !== void 0) toggle(address);
|
|
1710
1733
|
},
|
|
1711
1734
|
children: [
|
|
1712
1735
|
view.action === "resume-all" || view.action === "cancel-all" ? (0, react_jsx_runtime.jsx)(ResumeIcon, {}) : (0, react_jsx_runtime.jsx)(PauseIcon, {}),
|
|
@@ -1759,6 +1782,7 @@ window.__ModuleLoader__.load({
|
|
|
1759
1782
|
"all.unclean.aria": "有会话未暂停,不能在这里恢复;打开清单逐行处理",
|
|
1760
1783
|
"all.safe": "已暂停",
|
|
1761
1784
|
"all.checking.aria": "正在读取暂停状态",
|
|
1785
|
+
"all.unavailable.aria": "暂停状态暂不可用,正在重新连接",
|
|
1762
1786
|
"all.caveat.jobs": "后台任务仍在运行",
|
|
1763
1787
|
"all.caveat.unresumable": "含无法恢复的子代理",
|
|
1764
1788
|
"all.caveat.stalled": "未暂停,需要你决定",
|
|
@@ -1821,6 +1845,7 @@ window.__ModuleLoader__.load({
|
|
|
1821
1845
|
"all.unclean.aria": "Some sessions are not paused and cannot be resumed here; open the list to decide",
|
|
1822
1846
|
"all.safe": "Paused",
|
|
1823
1847
|
"all.checking.aria": "Reading pause state",
|
|
1848
|
+
"all.unavailable.aria": "Pause state unavailable; reconnecting",
|
|
1824
1849
|
"all.caveat.jobs": "background jobs are still running",
|
|
1825
1850
|
"all.caveat.unresumable": "contains a subagent that cannot be resumed",
|
|
1826
1851
|
"all.caveat.stalled": "did not pause; needs your decision",
|