dsh-better-workspace 0.32.0 → 0.32.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.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-better-workspace",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "main": "./src/index.js",
5
5
  "description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping; the New Session workspace picker shows the same tree.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-workspace",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树(同一份视图选项即时同步);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default), and the same tree in the New Session workspace picker; add-workspace rides the official directory flow.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,7 +30,8 @@
30
30
  "@deepseek-ai/dsh-client-locale",
31
31
  "@deepseek-ai/dsh-client-ui-slots",
32
32
  "@deepseek-ai/dsh-client-ui-conversation",
33
- "@deepseek-ai/dsh-client-modules"
33
+ "@deepseek-ai/dsh-client-modules",
34
+ "@deepseek-ai/dsh-api-remotes"
34
35
  ],
35
36
  "platform": "web"
36
37
  }
package/src/client.js CHANGED
@@ -6937,9 +6937,14 @@ window.__ModuleLoader__.load({
6937
6937
  sidebarFlowEntry,
6938
6938
  shippedBrowserFace, scheduleSeatReader,
6939
6939
  officialT,
6940
- presetFace,
6940
+ presetFaceOf,
6941
6941
  t,
6942
6942
  } = props
6943
+ // v0.32.2: resolve the stable getter ONCE per render (see
6944
+ // browserInjected for why this is a function and not a value prop).
6945
+ // Every consumer below keeps reading this local, so a late-arriving
6946
+ // agent-preset service is picked up by the next render's menu build.
6947
+ const presetFace = typeof presetFaceOf === 'function' ? presetFaceOf() : null
6943
6948
 
6944
6949
  if (typeof useWorkspaces !== 'function' || typeof useSessions !== 'function') {
6945
6950
  console.error('[dsh-better-workspace] standard snapshot hooks missing; browser renders nothing')
@@ -9360,53 +9365,99 @@ window.__ModuleLoader__.load({
9360
9365
  let officialT = null
9361
9366
  try { officialT = ctx.locale.bind('workspace') } catch { officialT = null }
9362
9367
 
9363
- // v0.32.0: the agent-preset face — {list, select} over the same remote
9364
- // channels the official hero preset chip uses. Probed defensively: the
9365
- // 'remote' service may be missing on older hosts, and a missing or
9366
- // reshaped surface simply hides the menu row (presetFace === null).
9368
+ // v0.32.2: the agent-preset face — {list, select} over the same remote
9369
+ // channels the official hero preset chip uses. Root cause of the hidden
9370
+ // menu row, straight from the dsh 0.2.1 source (cordis 4.0.5): 'remote'
9371
+ // is a fiber service provided by the api-gateway bundle, and
9372
+ // 'remote.agentPresets' is a NAMESPACE service installed by api-remotes
9373
+ // ($mount) on its own child fiber. `ctx.remote` resolves through the
9374
+ // property chain only along the reading fiber's ancestry — v0.32.1
9375
+ // probed it from a fiber outside that subtree, the chain threw, and the
9376
+ // row hid; v0.32.0's strict ctx.get ran before the gateway went ACTIVE
9377
+ // and read undefined. The fix keeps NO hard inject (the settingsScope
9378
+ // lesson: a service the host lacks would pend this bundle forever):
9379
+ // probe synchronously for the common case, then WAIT with a soft
9380
+ // ctx.inject child fiber that fires once the two services go ACTIVE —
9381
+ // or pends harmlessly forever on hosts without them. The face reaches
9382
+ // the sidebar as a stable GETTER prop (see browserInjected) because the
9383
+ // slot renderer caches the inject result per entry and would otherwise
9384
+ // bake in a null until the entry re-registers.
9367
9385
  let presetFace = null
9368
- try {
9369
- const remote = ctx.get('remote')
9370
- const agentPresets = remote && remote.agentPresets
9371
- if (agentPresets && typeof agentPresets.list === 'function' && typeof agentPresets.select === 'function') {
9372
- presetFace = {
9373
- // readRoster mirror: a gateway hiccup
9374
- // ('gateway/invocation-unavailable') maps to an EMPTY roster,
9375
- // exactly like the official selector does; every other failure
9376
- // surfaces with its message.
9377
- list: async () => {
9378
- try {
9379
- const result = await agentPresets.list()
9380
- if (result && result.ok) return { ok: true, presets: (result.value && result.value.presets) || [] }
9381
- const code = result && result.error ? result.error.code : undefined
9382
- if (code === 'gateway/invocation-unavailable') return { ok: true, presets: [] }
9383
- return { ok: false, error: result && result.error ? result.error.message : 'preset roster read failed' }
9384
- } catch (error) {
9385
- return { ok: false, error: error instanceof Error ? error.message : String(error) }
9386
- }
9387
- },
9388
- // seat-store apply() mirror: a refusal carries the host's reason
9389
- // (error.details.reason when present); the outer call can throw,
9390
- // so a throw folds into the same {ok:false} shape.
9391
- select: async (sessionId, presetId) => {
9392
- try {
9393
- const result = await agentPresets.select(sessionId, presetId)
9394
- if (result && result.ok) return { ok: true }
9395
- const details = result && result.error ? result.error.details : undefined
9396
- const reason = details && typeof details.reason === 'string'
9397
- ? details.reason
9398
- : (result && result.error ? result.error.message : 'preset select failed')
9399
- return { ok: false, refusal: reason }
9400
- } catch (error) {
9401
- return { ok: false, refusal: error instanceof Error ? error.message : String(error) }
9402
- }
9403
- },
9386
+ const presetFaceReady = (candidate) => candidate
9387
+ && typeof candidate.list === 'function' && typeof candidate.select === 'function'
9388
+ const buildPresetFace = (agentPresets) => ({
9389
+ // readRoster mirror: a gateway hiccup
9390
+ // ('gateway/invocation-unavailable') maps to an EMPTY roster,
9391
+ // exactly like the official selector does; every other failure
9392
+ // surfaces with its message.
9393
+ list: async () => {
9394
+ try {
9395
+ const result = await agentPresets.list()
9396
+ if (result && result.ok) return { ok: true, presets: (result.value && result.value.presets) || [] }
9397
+ const code = result && result.error ? result.error.code : undefined
9398
+ if (code === 'gateway/invocation-unavailable') return { ok: true, presets: [] }
9399
+ return { ok: false, error: result && result.error ? result.error.message : 'preset roster read failed' }
9400
+ } catch (error) {
9401
+ return { ok: false, error: error instanceof Error ? error.message : String(error) }
9402
+ }
9403
+ },
9404
+ // seat-store apply() mirror: a refusal carries the host's reason
9405
+ // (error.details.reason when present); the outer call can throw,
9406
+ // so a throw folds into the same {ok:false} shape.
9407
+ select: async (sessionId, presetId) => {
9408
+ try {
9409
+ const result = await agentPresets.select(sessionId, presetId)
9410
+ if (result && result.ok) return { ok: true }
9411
+ const details = result && result.error ? result.error.details : undefined
9412
+ const reason = details && typeof details.reason === 'string'
9413
+ ? details.reason
9414
+ : (result && result.error ? result.error.message : 'preset select failed')
9415
+ return { ok: false, refusal: reason }
9416
+ } catch (error) {
9417
+ return { ok: false, refusal: error instanceof Error ? error.message : String(error) }
9404
9418
  }
9419
+ },
9420
+ })
9421
+ try {
9422
+ let agentPresets = null
9423
+ try {
9424
+ const remote = ctx.remote
9425
+ agentPresets = remote && remote.agentPresets
9426
+ } catch { /* the property chain needs the service on this fiber's path */ }
9427
+ if (!presetFaceReady(agentPresets)) {
9428
+ try {
9429
+ const remote = ctx.get('remote')
9430
+ agentPresets = remote && remote.agentPresets
9431
+ } catch { /* strict get yields nothing while the provider pends */ }
9405
9432
  }
9433
+ if (presetFaceReady(agentPresets)) presetFace = buildPresetFace(agentPresets)
9406
9434
  } catch (presetFaceError) {
9407
9435
  console.warn('[dsh-better-workspace] agent-preset face unavailable', presetFaceError)
9408
9436
  presetFace = null
9409
9437
  }
9438
+ if (!presetFace && ctx && typeof ctx.inject === 'function') {
9439
+ // Late arrival: a soft child fiber that fires when the 'remote' +
9440
+ // 'remote.agentPresets' services go ACTIVE (the official
9441
+ // ui-agent-preset declares exactly these two). On hosts that never
9442
+ // mount them the fiber just pends — no effect, no error. The
9443
+ // namespace reads by its strict service name off the child fiber,
9444
+ // where the inject mechanism has cached it.
9445
+ try {
9446
+ ctx.inject(['remote', 'remote.agentPresets'], (fctx) => {
9447
+ try {
9448
+ const agentPresets = fctx && fctx['remote.agentPresets']
9449
+ if (presetFaceReady(agentPresets)) {
9450
+ presetFace = buildPresetFace(agentPresets)
9451
+ console.info('[dsh-better-workspace] agent-preset face ready (late arrival)')
9452
+ }
9453
+ } catch (lateError) {
9454
+ console.warn('[dsh-better-workspace] agent-preset late face failed', lateError)
9455
+ }
9456
+ })
9457
+ } catch (injectError) {
9458
+ console.warn('[dsh-better-workspace] agent-preset soft inject failed', injectError)
9459
+ }
9460
+ }
9410
9461
 
9411
9462
  if (ctx.locale && typeof ctx.locale.register === 'function') {
9412
9463
  ctx.effect(() => {
@@ -9461,10 +9512,15 @@ window.__ModuleLoader__.load({
9461
9512
  // Official 'workspace' dictionary translate (menu copy follows upstream);
9462
9513
  // null when the bind failed — the menu then uses the aligned bw copy.
9463
9514
  officialT,
9464
- // v0.32.0: {list, select} over the agent-preset remotes — the same
9465
- // two channels the official hero chip uses; null when the host lacks
9466
- // the service, which simply hides the workspace-preset menu row.
9467
- presetFace,
9515
+ // v0.32.2: the agent-preset face rides a stable GETTER, not a value.
9516
+ // The slot renderer caches the inject result per StoredEntry
9517
+ // (ui-renderer scoped-slots rootInjectCache), so a value prop would
9518
+ // bake in whatever presetFace was at first render — null whenever the
9519
+ // remote services arrived after this face was cached, permanently
9520
+ // hiding the workspace-preset menu row. The getter reads the apply
9521
+ // closure live, so every render and every menu build re-asks; hosts
9522
+ // without the service keep returning null, which simply hides the row.
9523
+ presetFaceOf: () => presetFace,
9468
9524
  // The shipped sidebar's OWN face (dsh 0.1.7-rc.2): the shortcut request
9469
9525
  // channel and the catalog of effective keybindings. Both belong to the
9470
9526
  // entry this seat shadows, so they are read off the ledger and consumed
@@ -9701,6 +9757,12 @@ window.__ModuleLoader__.load({
9701
9757
 
9702
9758
  return {
9703
9759
  name: 'dsh-better-workspace',
9760
+ // v0.32.2: the list stays at the five era-guaranteed services (the
9761
+ // settingsScope lesson — a hard inject of anything a host may lack
9762
+ // pends this whole bundle forever). 'remote' + 'remote.agentPresets'
9763
+ // are acquired SOFTLY inside apply: a synchronous probe plus a
9764
+ // ctx.inject child fiber that waits for their ACTIVE transition
9765
+ // (see the agent-preset face block above).
9704
9766
  inject: ['slots', 'sessions', 'workspaces', 'locale', 'uiWorkspace'],
9705
9767
  apply,
9706
9768
  }