@michengai/dsh-archive-manager 0.1.17 → 0.1.19

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 CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 0.1.19 — 2026-08-30
8
+
9
+ - Fixed the workspace-row **+** action on DSH `v0.1.2-alpha.1` by resolving the `uiWorkspace` service when the action runs, removing its dependency on plugin load order and avoiding the removed legacy `startSession` API.
10
+
11
+ Published package: [`@michengai/dsh-archive-manager@0.1.19`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.19).
12
+
13
+ ## 0.1.18 — 2026-08-28
14
+
15
+ - Added client compatibility for the split store layout in DSH `v0.1.2-alpha.1` while preserving the legacy runtime fallback used by `v0.1.1-rc.2`.
16
+ - Restored the alpha Workspace navigation service required by the sidebar, conversation view, and directory picker when Archive Manager replaces the stock Workspace UI.
17
+ - Bound observable stores at the Settings boundary and kept subagent-lineage indexing local, preventing archived-chat startup and rendering failures on the alpha release.
18
+
19
+ Published package: [`@michengai/dsh-archive-manager@0.1.18`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.18).
20
+
7
21
  ## 0.1.17 — 2026-08-28
8
22
 
9
23
  - Enabled npm Trusted Publishing through the repository's GitHub Actions release workflow.
@@ -4,6 +4,20 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.19 — 2026-08-30
8
+
9
+ - 修复 DSH `v0.1.2-alpha.1` 中工作区行 **+** 操作:在操作执行时再获取 `uiWorkspace` 服务,避免受插件加载顺序影响,也不再落入已移除的旧版 `startSession` API。
10
+
11
+ 发布包:[`@michengai/dsh-archive-manager@0.1.19`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.19)。
12
+
13
+ ## 0.1.18 — 2026-08-28
14
+
15
+ - 兼容 DSH `v0.1.2-alpha.1` 拆分后的客户端 Store 布局,同时保留 `v0.1.1-rc.2` 使用的旧版 Runtime 回退路径。
16
+ - 在归档管理器替换官方工作区 UI 后补齐 alpha 版侧栏、会话视图和目录选择器依赖的工作区导航服务。
17
+ - 在设置页注入边界绑定可观察 Store,并将子代理谱系索引保留在插件内部,避免 alpha 版启动和归档页渲染失败。
18
+
19
+ 发布包:[`@michengai/dsh-archive-manager@0.1.18`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.18)。
20
+
7
21
  ## 0.1.17 — 2026-08-28
8
22
 
9
23
  - 启用仓库 GitHub Actions 发布工作流的 npm Trusted Publishing。
package/lib/client.js CHANGED
@@ -4,7 +4,16 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
7
+ let _deepseek_ai_dsh_client_store;
8
+ let hasSplitClientStore = true;
9
+ try {
10
+ _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
11
+ } catch {
12
+ // DSH <= 0.1.1 owns the store engine in client-runtime; 0.1.2+
13
+ // seeds the split client-store package directly into the module table.
14
+ hasSplitClientStore = false;
15
+ _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-runtime/client");
16
+ }
8
17
  let react_jsx_runtime = require("react/jsx-runtime");
9
18
  let react = require("react");
10
19
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
@@ -187,6 +196,38 @@ window.__ModuleLoader__.load({
187
196
  document.head.appendChild(tag);
188
197
  }
189
198
  //#endregion
199
+ //#region lib/types/client/subagent-lineage.js
200
+ /**
201
+ * Index uninterrupted subagent descendants under each ancestor.
202
+ * DSH 0.1.2 made this a UI-package-private projection, so the archive
203
+ * manager keeps the small pure helper locally instead of importing the
204
+ * removed client-runtime package.
205
+ * @param summaries - Session summaries keyed by id.
206
+ * @returns descendant totals keyed by possible parent id.
207
+ */
208
+ function indexSubagentDescendants(summaries) {
209
+ const indexed = /* @__PURE__ */ new Map();
210
+ for (const descendant of Object.values(summaries)) {
211
+ if (descendant.origin !== "subagent") continue;
212
+ const seen = /* @__PURE__ */ new Set();
213
+ let current = descendant;
214
+ while (current?.origin === "subagent" && current.parentId !== void 0 && !seen.has(current.id)) {
215
+ seen.add(current.id);
216
+ const aggregate = indexed.get(current.parentId);
217
+ if (aggregate === void 0) indexed.set(current.parentId, {
218
+ count: 1,
219
+ runningCount: descendant.running ? 1 : 0
220
+ });
221
+ else {
222
+ aggregate.count += 1;
223
+ if (descendant.running) aggregate.runningCount += 1;
224
+ }
225
+ current = summaries[current.parentId];
226
+ }
227
+ }
228
+ return indexed;
229
+ }
230
+ //#endregion
190
231
  //#region lib/types/client/stores.js
191
232
  /**
192
233
  * The workspace browser's viewing store: the session-list grouping mode,
@@ -202,7 +243,7 @@ window.__ModuleLoader__.load({
202
243
  * @returns the store handle (spec + type + identity + factory in one).
203
244
  */
204
245
  function createWorkspaceViewStore() {
205
- return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
246
+ return (0, _deepseek_ai_dsh_client_store.defineStore)({
206
247
  init: () => ({
207
248
  groupBy: "workspace",
208
249
  orderBy: "updated",
@@ -402,7 +443,7 @@ window.__ModuleLoader__.load({
402
443
  function deriveGroups(list, workspaces, archivedSessionIds, view) {
403
444
  const archived = new Set(archivedSessionIds);
404
445
  const expandedGroups = new Set(view.expandedGroups);
405
- const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
446
+ const descendants = indexSubagentDescendants(list.byId);
406
447
  const currentGroup = list.current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(list.current))?.workspaceId ?? "";
407
448
  const groups = [];
408
449
  for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder, view.showArchived)) {
@@ -435,7 +476,7 @@ window.__ModuleLoader__.load({
435
476
  */
436
477
  function deriveFlat(list, archivedSessionIds, showArchived) {
437
478
  const archived = new Set(archivedSessionIds);
438
- const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
479
+ const descendants = indexSubagentDescendants(list.byId);
439
480
  const rows = [];
440
481
  for (const id of list.ids) {
441
482
  const s = list.byId[id];
@@ -466,7 +507,7 @@ window.__ModuleLoader__.load({
466
507
  hasMore: false
467
508
  };
468
509
  const archived = new Set(archivedSessionIds);
469
- const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
510
+ const descendants = indexSubagentDescendants(list.byId);
470
511
  const workspaceBySession = /* @__PURE__ */ new Map();
471
512
  for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
472
513
  const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
@@ -3242,6 +3283,158 @@ window.__ModuleLoader__.load({
3242
3283
  "remote",
3243
3284
  "typert"
3244
3285
  ];
3286
+ /** Preserve the receiver required by alpha-era observable stores. */
3287
+ function bindObservable(source) {
3288
+ return {
3289
+ getSnapshot: source.getSnapshot.bind(source),
3290
+ subscribe: source.subscribe.bind(source)
3291
+ };
3292
+ }
3293
+ class DirectoryBrowseError extends Error {
3294
+ constructor(rpcError) {
3295
+ super(`directory browse failed: ${rpcError.code}: ${rpcError.message}`);
3296
+ this.name = "DirectoryBrowseError";
3297
+ this.rpcError = rpcError;
3298
+ }
3299
+ }
3300
+ /**
3301
+ * Provide the Workspace navigation capability introduced by DSH 0.1.2.
3302
+ * The archive manager replaces the stock ui-workspace row, so it must also
3303
+ * preserve the service that alpha-era sidebar/conversation plugins wait for.
3304
+ * Older hosts do not consume this service, making the provider harmless there.
3305
+ * @param ctx - client root context.
3306
+ * @returns disposer for the compatibility service, or a no-op when supplied.
3307
+ */
3308
+ function provideUiWorkspace(ctx) {
3309
+ if (ctx.get("uiWorkspace") !== void 0) return () => {};
3310
+ const sessions = ctx.sessions;
3311
+ const workspaces = ctx.workspaces;
3312
+ const connecting = new Map();
3313
+ const recentWorkspace = () => {
3314
+ const workspaceState = workspaces.list.getSnapshot();
3315
+ const sessionState = sessions.list.getSnapshot();
3316
+ let selected;
3317
+ let selectedTime = Number.NEGATIVE_INFINITY;
3318
+ for (const workspace of workspaceState.items ?? []) {
3319
+ let latest = Number.NEGATIVE_INFINITY;
3320
+ for (const sessionId of workspace.sessionIds) {
3321
+ const session = sessionState.byId[sessionId];
3322
+ if (session !== void 0) latest = Math.max(latest, session.updatedAt);
3323
+ }
3324
+ if (latest === Number.NEGATIVE_INFINITY) latest = Date.parse(workspace.createdAt);
3325
+ if (selected === void 0 || latest > selectedTime) {
3326
+ selected = workspace.workspaceId;
3327
+ selectedTime = latest;
3328
+ }
3329
+ }
3330
+ return selected;
3331
+ };
3332
+ const directoryPicker = () => ctx.get("remote.directoryPicker") ?? ctx.get("remote")?.directoryPicker;
3333
+ const service = {
3334
+ connectWorkspace(workspaceId) {
3335
+ const workspaceState = workspaces.list.getSnapshot();
3336
+ const workspace = (workspaceState.items ?? []).find((item) => item.workspaceId === workspaceId);
3337
+ if (workspace === void 0) return Promise.reject(new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`));
3338
+ const inflight = connecting.get(workspaceId);
3339
+ if (inflight !== void 0) return inflight;
3340
+ const sessionState = sessions.list.getSnapshot();
3341
+ const archived = new Set(workspaceState.archivedSessionIds ?? []);
3342
+ for (const id of sessionState.ids) {
3343
+ const summary = sessionState.byId[id];
3344
+ if (summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(summary.id) && !archived.has(summary.id)) return Promise.resolve(summary.id);
3345
+ }
3346
+ const attempt = sessions.create({ workspaceId }).finally(() => {
3347
+ connecting.delete(workspaceId);
3348
+ });
3349
+ connecting.set(workspaceId, attempt);
3350
+ return attempt;
3351
+ },
3352
+ startSession(workspaceId) {
3353
+ const workspaceState = workspaces.list.getSnapshot();
3354
+ const sessionState = sessions.list.getSnapshot();
3355
+ const currentWorkspaceId = sessionState.current === void 0 ? void 0 : (workspaceState.items ?? []).find((item) => item.sessionIds.includes(sessionState.current))?.workspaceId;
3356
+ const recent = workspaceState.phase === "ready" && sessionState.phase === "ready" ? recentWorkspace() : void 0;
3357
+ const target = workspaceId ?? currentWorkspaceId ?? recent;
3358
+ if (target === void 0) {
3359
+ sessions.clear();
3360
+ return;
3361
+ }
3362
+ void service.connectWorkspace(target).then((sessionId) => {
3363
+ sessions.open(sessionId);
3364
+ }, (reason) => {
3365
+ console.warn("new session failed:", reason);
3366
+ });
3367
+ },
3368
+ archiveSession(sessionId) {
3369
+ return workspaces.archiveSession(sessionId);
3370
+ },
3371
+ async pickDirectory() {
3372
+ const picker = directoryPicker();
3373
+ if (picker === void 0) throw new Error("directory picker is unavailable");
3374
+ const result = await picker.pick();
3375
+ if (!result.ok) throw new Error(`directory picker failed: ${result.error.message}`);
3376
+ return result.value;
3377
+ },
3378
+ async listDirectory(path, signal) {
3379
+ const picker = directoryPicker();
3380
+ if (picker === void 0) throw new Error("directory picker is unavailable");
3381
+ const result = await picker.list(path, signal);
3382
+ if (!result.ok) throw new DirectoryBrowseError(result.error);
3383
+ return result.value;
3384
+ },
3385
+ async createDirectory(path, name) {
3386
+ const picker = directoryPicker();
3387
+ if (picker === void 0) throw new Error("directory picker is unavailable");
3388
+ const result = await picker.createDirectory(path, name);
3389
+ if (!result.ok) throw new DirectoryBrowseError(result.error);
3390
+ return result.value;
3391
+ }
3392
+ };
3393
+ const dispose = ctx.provide("uiWorkspace", service);
3394
+ ctx.slots.provideRoot?.({ hooks: { workspaces: workspaces.list } });
3395
+ ctx.effect(() => {
3396
+ let initial = "waiting";
3397
+ let disposed = false;
3398
+ const reconcile = () => {
3399
+ if (disposed) return;
3400
+ const workspaceState = workspaces.list.getSnapshot();
3401
+ const sessionState = sessions.list.getSnapshot();
3402
+ if (sessionState.current !== void 0 && (workspaceState.archivedSessionIds ?? []).includes(sessionState.current)) {
3403
+ sessions.clear();
3404
+ return;
3405
+ }
3406
+ if (initial !== "waiting" || workspaceState.phase !== "ready" || sessionState.phase !== "ready") return;
3407
+ if (sessionState.current !== void 0) {
3408
+ initial = "done";
3409
+ return;
3410
+ }
3411
+ const target = recentWorkspace();
3412
+ if (target === void 0) {
3413
+ initial = "done";
3414
+ return;
3415
+ }
3416
+ initial = "connecting";
3417
+ void service.connectWorkspace(target).then((sessionId) => {
3418
+ if (disposed) return;
3419
+ if (sessions.list.getSnapshot().current === void 0) sessions.open(sessionId);
3420
+ initial = "done";
3421
+ }, (reason) => {
3422
+ if (disposed) return;
3423
+ initial = "waiting";
3424
+ console.warn("initial workspace selection failed:", reason);
3425
+ });
3426
+ };
3427
+ const disposeWorkspaces = workspaces.list.subscribe(reconcile);
3428
+ const disposeSessions = sessions.list.subscribe(reconcile);
3429
+ reconcile();
3430
+ return () => {
3431
+ disposed = true;
3432
+ disposeSessions();
3433
+ disposeWorkspaces();
3434
+ };
3435
+ }, "dsh-archive-manager: uiWorkspace navigation policy");
3436
+ return dispose;
3437
+ }
3245
3438
  /**
3246
3439
  * Plugin body: mount the archive-manager Remote contribution, then
3247
3440
  * register the workspace browser. `$mount` must complete before the
@@ -3255,8 +3448,10 @@ window.__ModuleLoader__.load({
3255
3448
  const remote = ctx.get("remote");
3256
3449
  let disposeRemote = () => {};
3257
3450
  if (remote !== void 0) disposeRemote = await remote.$mount(ARCHIVE_MANAGER_REMOTE);
3451
+ const disposeUiWorkspace = hasSplitClientStore ? provideUiWorkspace(ctx) : () => {};
3258
3452
  applyWorkspaceBrowser(ctx);
3259
3453
  return async () => {
3454
+ disposeUiWorkspace();
3260
3455
  await disposeRemote();
3261
3456
  };
3262
3457
  }
@@ -3271,6 +3466,10 @@ window.__ModuleLoader__.load({
3271
3466
  zh,
3272
3467
  en
3273
3468
  }), "dsh-archive-manager: dictionaries");
3469
+ // Resolve the navigation service lazily: this plugin provides the fallback
3470
+ // implementation during its own apply, and cordis strict ctx.get only
3471
+ // returns a service once the providing fiber is ACTIVE.
3472
+ const uiWorkspaceAt = () => ctx.get("uiWorkspace");
3274
3473
  const searchSessions = async (query, signal) => {
3275
3474
  const result = await ctx.sessions.search(query, signal);
3276
3475
  if (!result.ok) throw new Error(result.error.message);
@@ -3317,7 +3516,9 @@ window.__ModuleLoader__.load({
3317
3516
  };
3318
3517
  const browserInjected = () => ({
3319
3518
  startSession: (workspaceId) => {
3320
- ctx.workspaces.startSession(workspaceId);
3519
+ const uiWorkspace = uiWorkspaceAt();
3520
+ if (uiWorkspace !== void 0) uiWorkspace.startSession(workspaceId);
3521
+ else ctx.workspaces.startSession(workspaceId);
3321
3522
  },
3322
3523
  open: (sessionId) => {
3323
3524
  ctx.sessions.open(sessionId);
@@ -3387,8 +3588,8 @@ window.__ModuleLoader__.load({
3387
3588
  icon: "archive",
3388
3589
  locale: NS,
3389
3590
  inject: () => ({
3390
- sessionStore: ctx.sessions.list,
3391
- workspaceStore: ctx.workspaces.list,
3591
+ sessionStore: bindObservable(ctx.sessions.list),
3592
+ workspaceStore: bindObservable(ctx.workspaces.list),
3392
3593
  unarchiveSession,
3393
3594
  deleteSession,
3394
3595
  unarchiveSessions,
@@ -3403,6 +3604,7 @@ window.__ModuleLoader__.load({
3403
3604
  exports.__test = {
3404
3605
  displayTitle,
3405
3606
  sessionVisible,
3607
+ indexSubagentDescendants,
3406
3608
  isUnknownSessionError,
3407
3609
  deriveGroups,
3408
3610
  deriveFlat,
@@ -3412,6 +3614,9 @@ window.__ModuleLoader__.load({
3412
3614
  deriveArchivedBatchIds,
3413
3615
  archivedDeleteFeedback,
3414
3616
  createWorkspaceViewStore,
3617
+ bindObservable,
3618
+ hasSplitClientStore,
3619
+ provideUiWorkspace,
3415
3620
  groupByWorkspace,
3416
3621
  byRecency,
3417
3622
  ARCHIVE_MANAGER_REMOTE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.21.0",
@@ -47,6 +47,11 @@
47
47
  "@deepseek-ai/dsh-typert-registry": ">=0.1.0-rc.5 <0.2.0",
48
48
  "@deepseek-ai/dsh-workspace": ">=0.1.0-rc.5 <0.2.0"
49
49
  },
50
+ "peerDependenciesMeta": {
51
+ "@deepseek-ai/dsh-client-runtime": {
52
+ "optional": true
53
+ }
54
+ },
50
55
  "devDependencies": {
51
56
  "@deepseek-ai/cordis": "4.0.1",
52
57
  "@deepseek-ai/dsh-api-gateway": "0.1.0-rc.6",
@@ -69,7 +74,6 @@
69
74
  "client": {
70
75
  "inject": [
71
76
  "@deepseek-ai/dsh-client-locale",
72
- "@deepseek-ai/dsh-client-runtime",
73
77
  "@deepseek-ai/dsh-client-ui-conversation",
74
78
  "@deepseek-ai/dsh-client-ui-sidebar",
75
79
  "@deepseek-ai/dsh-typert-registry",