dsh-ws-recency 0.11.3 → 0.11.4

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
@@ -130,7 +130,7 @@ dsh plugin --profile <profile> add github:chyxhtc/dsh-ws-recency
130
130
  或固定版本的预构建包:
131
131
 
132
132
  ```powershell
133
- dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.11.3/dsh-ws-recency-0.11.3.tgz
133
+ dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.11.4/dsh-ws-recency-0.11.4.tgz
134
134
  ```
135
135
 
136
136
  装完把 `dsh-ws-recency` 加进 `~/.hypers-work/profiles/<profile>/package.json` 的 `dsh.profile.bundles`,重启应用。
package/client.js CHANGED
@@ -213,6 +213,7 @@
213
213
  "move.working": "移动中…(会先停止会话)",
214
214
  "move.done": "已移动到「{workspace}」。若列表没刷新,切换一下工作区或重启应用。",
215
215
  "move.doneLive": "\n\n这个会话刚才是打开状态:建议重启应用后再继续对话,避免它沿用旧的目录。",
216
+ "move.repaired": "已补登记到「{workspace}」:上一次移动只搬了日志、没登记工作区,这次把它补上了。",
216
217
  "move.failed": "移动失败:",
217
218
  "stats.chart": "消耗分布",
218
219
  "stats.details.show": "显示明细 ▾",
@@ -267,6 +268,7 @@
267
268
  "move.working": "Moving\u2026 (the session is stopped first)",
268
269
  "move.done": "Moved to \"{workspace}\". If the list has not refreshed, switch workspaces or restart the app.",
269
270
  "move.doneLive": "\n\nThe session was open: restart the app before continuing it so it does not keep using the old directory.",
271
+ "move.repaired": "Registered in \"{workspace}\": the previous attempt moved the log without the bookkeeping, and this repaired it.",
270
272
  "move.failed": "Move failed: ",
271
273
  "stats.chart": "Where the tokens went",
272
274
  "stats.details.show": "Show details",
@@ -1448,6 +1450,10 @@
1448
1450
  })
1449
1451
  .then(function (result) {
1450
1452
  if (result.json !== null && result.json.ok === true) {
1453
+ if (result.json.repaired === true) {
1454
+ body.textContent = t("move.repaired", { workspace: label });
1455
+ return;
1456
+ }
1451
1457
  body.textContent = t("move.done", { workspace: label });
1452
1458
  if (result.json.live === true) body.textContent += t("move.doneLive");
1453
1459
  return;
package/index.js CHANGED
@@ -469,7 +469,28 @@ export async function moveSessionToWorkspace(ctx, sessionId, workspaceId) {
469
469
  ?? workspaces.find((workspace) => workspace.path === workspaceId);
470
470
  if (target === undefined) return { ok: false, error: "workspace not found" };
471
471
  const targetPath = target.path;
472
- if (header.cwd === targetPath) return { ok: false, error: "already in that workspace" };
472
+ if (header.cwd === targetPath) {
473
+ /* Already pointed at the target: a previous attempt moved the log but died
474
+ before the bookkeeping (the app's in-memory header refused it). Repair the
475
+ membership instead of failing, so such a session is not left unlisted. */
476
+ try {
477
+ await target.attachSession(sessionId);
478
+ } catch (error) {
479
+ return { ok: false, error: `session is already in that directory but the workspace refused it: ${error instanceof Error ? error.message : String(error)}` };
480
+ }
481
+ const stale = [];
482
+ for (const workspace of workspaces) {
483
+ if (!Array.isArray(workspace.sessionIds) || !workspace.sessionIds.includes(sessionId)) continue;
484
+ if (workspace.id === target.id) continue;
485
+ try {
486
+ await workspace.detachSession(sessionId);
487
+ stale.push(workspace.id);
488
+ } catch {
489
+ /* a stale membership that filters itself out anyway */
490
+ }
491
+ }
492
+ return { ok: true, repaired: true, workspace: target.id, path: targetPath, detached: stale };
493
+ }
473
494
 
474
495
  let targetDirectory;
475
496
  try {
@@ -550,9 +571,10 @@ export async function moveSessionToWorkspace(ctx, sessionId, workspaceId) {
550
571
  has the session open. The registry validates against its own view, so both the
551
572
  cached header and the live one have to follow - otherwise an open session is
552
573
  refused with its old directory. */
574
+ let wasLive = false;
575
+ try {
553
576
  const movedHeader = { ...header, cwd: targetPath };
554
577
  /* Cordis throws on an undeclared service, so the probe is guarded. */
555
- let wasLive = false;
556
578
  try {
557
579
  wasLive = typeof ctx.sessions?.get === "function" && ctx.sessions.get(sessionId) !== undefined;
558
580
  } catch {
@@ -583,6 +605,10 @@ export async function moveSessionToWorkspace(ctx, sessionId, workspaceId) {
583
605
  await rollback();
584
606
  return { ok: false, error: `the app still holds this session's old directory (${seenCwd}); restart the app and move it again` };
585
607
  }
608
+ } catch (error) {
609
+ await rollback();
610
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
611
+ }
586
612
 
587
613
  /* Attach first, detach second: a refused attach must not orphan the session. */
588
614
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ws-recency",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Sidebar session management for DeepSeek Harness: workspace groups sorted by latest activity, stale history folded, a global pinned section, follow-up marks, session delete into a recoverable trash, per-session token statistics split by tool, and moving a session to another workspace.",
5
5
  "license": "MIT",
6
6
  "author": "chyxhtc",