@deepseek-ai/dsh-client-ui-session 0.1.5-rc.2 → 0.1.6-alpha.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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-session/README.md
5
- README.md: 75fd792866afc29c6c99fd0879588e80b75b22dc
6
- README.zh.md: bad90a7602bf097a3ba24e7a39c7cb8e5776d141
5
+ README.md: 4fff368e58d7b422ba6948be78e543c31f5cef59
6
+ README.zh.md: db37539b59231bfef50f3bdcc33aeae94e881a44
package/README.md CHANGED
@@ -8,7 +8,7 @@ English | [中文](README.zh.md)
8
8
 
9
9
  ## Summary
10
10
 
11
- React and Slot adapter for Session Controller state. It contributes Session list and pending-interaction hooks at root scope, materializes per-Session hooks and props, and owns the standard `SessionProvider` rendering behavior without taking ownership of Session transport or lifecycle state. Use it when a browser feature needs Session state through standard React props and hooks.
11
+ Use this package to expose Session catalog, retain information, and unified UI status through standard Slot hooks. It materializes per-`SessionBinding` hooks and props, while `SessionProvider` can inherit an outer binding or bind an explicit `SessionReference`. It owns process-local pending-interaction and completion-reminder policy without owning Controller transport, history, or references.
12
12
 
13
13
  ## Table of Contents
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@ kind: "package-reference"
8
8
 
9
9
  ## 概述
10
10
 
11
- 面向 Session Controller 状态的 React 与 Slot adapter。本包在 root scope 提供 Session list 和 pending-interaction hook,物化逐 Session hook 与 prop,并拥有标准 `SessionProvider` 渲染行为,但不接管 Session transport 或 lifecycle 状态。当浏览器功能需要通过标准 React prop 和 hook 读取 Session 状态时,请使用它。
11
+ 使用本包可通过标准 Slot 钩子公开 Session catalog、retain 信息与统一 UI 状态。它按 `SessionBinding` 物化钩子和 prop,而 `SessionProvider` 可以继承外围 binding 或绑定显式 `SessionReference`。它拥有进程本地的 pending-interaction 与完成提醒策略,但不拥有 Controller transport、历史或 reference。
12
12
 
13
13
  ## 目录
14
14
 
@@ -31,7 +31,7 @@ kind: "package-reference"
31
31
 
32
32
  <a id="known-limitations-and-deferred-work"></a>
33
33
 
34
- - **Pending interaction 是进程本地投影**——浏览器重连后,所属 Remote waterfall 必须重放仍未完成的请求。
34
+ - **Pending interaction 是进程本地投影**——浏览器重连后,所属 Remote waterfall(瀑布式事件)必须重放仍未完成的请求。
35
35
 
36
36
 
37
37
  <a id="dev-note"></a>
@@ -44,4 +44,4 @@ kind: "package-reference"
44
44
 
45
45
  </details>
46
46
 
47
- **运行时不变式:** 不发布伴生入口。adapter materialization 路径已经强制 Session binding 一致。
47
+ **运行时不变式:** 不发布伴生入口。适配器 materialization 路径已经强制 Session 绑定一致。
package/lib/client.js CHANGED
@@ -8,19 +8,79 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
9
9
  let _deepseek_ai_dsh_client_ui_slots = require("@deepseek-ai/dsh-client-ui-slots");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
- let react = require("react");
11
+ //#region ../../util/values/src/index.ts
12
+ /**
13
+ * Weak-key lookup with a strongly retained iterable set of associated values.
14
+ *
15
+ * Each value must belong to only one key. The container performs no automatic
16
+ * cleanup; owners delete associations or clear the container at lifecycle end.
17
+ */
18
+ var WeakMapWithValues = class {
19
+ keys = /* @__PURE__ */ new WeakMap();
20
+ valueSet = /* @__PURE__ */ new Set();
21
+ /** Live strongly retained values in insertion order. */
22
+ values = this.valueSet;
23
+ /**
24
+ * Read the value associated with a key.
25
+ * @param key - weakly held lookup key.
26
+ * @returns the associated value, or absence.
27
+ */
28
+ get(key) {
29
+ return this.keys.get(key);
30
+ }
31
+ /**
32
+ * Test whether a key has an association.
33
+ * @param key - weakly held lookup key.
34
+ * @returns whether the key is present.
35
+ */
36
+ has(key) {
37
+ return this.keys.has(key);
38
+ }
39
+ /**
40
+ * Associate one key with one caller-unique value.
41
+ * @param key - weakly held lookup key.
42
+ * @param value - strongly retained value that belongs to no other key.
43
+ * @returns this container.
44
+ */
45
+ set(key, value) {
46
+ if (this.keys.has(key)) {
47
+ const previous = this.keys.get(key);
48
+ if (previous === value) return this;
49
+ this.valueSet.delete(previous);
50
+ }
51
+ this.keys.set(key, value);
52
+ this.valueSet.add(value);
53
+ return this;
54
+ }
55
+ /**
56
+ * Remove one association and its strongly retained value.
57
+ * @param key - weakly held lookup key.
58
+ * @returns whether an association was removed.
59
+ */
60
+ delete(key) {
61
+ if (!this.keys.has(key)) return false;
62
+ const value = this.keys.get(key);
63
+ const deleted = this.keys.delete(key);
64
+ this.valueSet.delete(value);
65
+ return deleted;
66
+ }
67
+ /** Remove every association and strongly retained value. */
68
+ clear() {
69
+ this.keys = /* @__PURE__ */ new WeakMap();
70
+ this.valueSet.clear();
71
+ }
72
+ };
73
+ //#endregion
12
74
  //#region lib/types/client/session-provider.js
13
- /** Session-owned rendering semantics for the standard SessionProvider seat. */
14
75
  /**
15
76
  * Render the selected Session body or its empty branch.
16
77
  * @param binding - current Session scope binding.
17
78
  * @param props - standard Session area render props.
18
- * @returns the selected Session subtree, keyed by Session identity.
79
+ * @returns the selected Session subtree.
19
80
  */
20
81
  function renderSessionArea(binding, { empty, children }) {
21
- const sessionId = binding.key;
22
- if (sessionId === void 0) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: empty?.() ?? null });
23
- return (0, react_jsx_runtime.jsx)(react.Fragment, { children }, sessionId);
82
+ if (binding.key === void 0) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: empty?.() ?? null });
83
+ return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
24
84
  }
25
85
  //#endregion
26
86
  //#region lib/types/client/index.js
@@ -72,20 +132,25 @@ window.__ModuleLoader__.load({
72
132
  var UiSession = class extends _deepseek_ai_cordis.Service {
73
133
  sessions;
74
134
  descriptors = [BUILTIN_SOURCE];
75
- bindings = /* @__PURE__ */ new Map();
135
+ bindings = new WeakMapWithValues();
76
136
  absent;
77
- currentBinding;
78
- currentListeners = /* @__PURE__ */ new Set();
137
+ current;
79
138
  pendingDomains = [];
80
139
  pendingSnapshot = /* @__PURE__ */ new Map();
81
- pendingListeners = /* @__PURE__ */ new Set();
82
- /** Root source of pending UI interactions, independent from Controller snapshots. */
83
- pendingInteractions = {
84
- getSnapshot: () => this.pendingSnapshot,
140
+ running = /* @__PURE__ */ new Map();
141
+ completionUnread = /* @__PURE__ */ new Set();
142
+ statusSnapshot = /* @__PURE__ */ new Map();
143
+ statusListeners = /* @__PURE__ */ new Set();
144
+ mainRetainId;
145
+ disposeMainRetain = () => {};
146
+ active = true;
147
+ /** Root source combining running, pending-interaction, and completion-reminder facts. */
148
+ sessionStatus = {
149
+ getSnapshot: () => this.statusSnapshot,
85
150
  subscribe: (listener) => {
86
- this.pendingListeners.add(listener);
151
+ this.statusListeners.add(listener);
87
152
  return () => {
88
- this.pendingListeners.delete(listener);
153
+ this.statusListeners.delete(listener);
89
154
  };
90
155
  }
91
156
  };
@@ -98,34 +163,51 @@ window.__ModuleLoader__.load({
98
163
  constructor(ctx, sessions) {
99
164
  super(ctx, "uiSession");
100
165
  this.sessions = sessions;
101
- this.absent = this.materializeAbsent();
102
- this.currentBinding = this.resolveCurrent();
166
+ this.absent = createBindingSource(this.materializeAbsent());
167
+ this.current = createBindingSource(this.absent.value);
103
168
  this.adapter = {
104
- current: {
105
- getSnapshot: () => this.currentBinding,
106
- subscribe: (listener) => {
107
- this.currentListeners.add(listener);
108
- return () => {
109
- this.currentListeners.delete(listener);
110
- };
111
- }
112
- },
113
- resolve: (key) => this.resolve(key),
169
+ current: this.current,
170
+ bindingSource: (target) => this.bindingSource(target),
114
171
  renderArea: renderSessionArea
115
172
  };
116
173
  ctx.effect(() => {
117
174
  const disposeList = sessions.list.subscribe(() => {
118
- this.publishCurrent();
175
+ this.publishMain();
119
176
  });
177
+ const disposeStatus = sessions.list.subscribe(() => {
178
+ this.reconcileStatus();
179
+ });
180
+ const disposeRemoteStatus = ctx.remote.$on("api-session/status", (sessionId, running) => {
181
+ this.observeRunning(sessionId, running);
182
+ });
183
+ this.publishMain();
184
+ this.reconcileStatus();
120
185
  return () => {
186
+ this.active = false;
121
187
  disposeList();
122
- const records = [...this.bindings.values()];
188
+ disposeStatus();
189
+ disposeRemoteStatus();
190
+ this.disposeMainRetain();
191
+ const records = [...this.bindings.values];
123
192
  this.bindings.clear();
124
193
  for (const record of records) record.release();
125
194
  };
126
195
  }, "ui-session: Session binding projection");
127
196
  }
128
197
  /**
198
+ * Resolve a stable renderer source for an owned Session reference or explicit absence.
199
+ * @param reference - active reference supplied by the Provider owner, or absence.
200
+ * @returns the binding source, which falls back to the absent projection when its generation ends.
201
+ * @throws when the reference does not belong to the active Controller generation.
202
+ */
203
+ bindingSource(reference) {
204
+ if (!this.active) return this.absent;
205
+ if (reference === void 0) return this.absent;
206
+ const owner = reference.binding;
207
+ if (this.sessions.binding(reference.sessionId) !== owner) throw new Error("ui-session: Session reference is not active in this Controller");
208
+ return this.sourceFor(owner);
209
+ }
210
+ /**
129
211
  * Register one Session-scoped standard-source contribution.
130
212
  * @param descriptor - static member roster and per-binding resolver.
131
213
  * @returns disposer owned by the caller's Cordis fiber.
@@ -177,38 +259,42 @@ window.__ModuleLoader__.load({
177
259
  }
178
260
  rebuildBindings() {
179
261
  const absent = this.materializeAbsent();
180
- const bindings = /* @__PURE__ */ new Map();
181
- try {
182
- for (const [sessionId, cached] of this.bindings) bindings.set(sessionId, this.createMaterializedBinding(cached.owner));
183
- } catch (error) {
184
- for (const record of bindings.values()) record.release();
185
- throw error;
186
- }
187
- const previous = this.bindings;
188
- this.absent = absent;
189
- this.bindings = bindings;
190
- for (const record of previous.values()) record.release();
191
- this.publishCurrent();
262
+ const updates = [...this.bindings.values].map((record) => ({
263
+ source: record.source,
264
+ value: this.materialize(record.owner)
265
+ }));
266
+ this.absent.value = absent;
267
+ for (const { source, value } of updates) source.value = value;
268
+ (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.absent.listeners, "[ui-session] absent binding");
269
+ for (const { source } of updates) (0, _deepseek_ai_dsh_client_store.notifySubscribers)(source.listeners, "[ui-session] Session binding");
270
+ this.publishMain();
192
271
  }
193
- resolve(key) {
194
- const owner = this.sessions.binding(key);
195
- if (owner === void 0) return void 0;
196
- const cached = this.bindings.get(key);
197
- if (cached?.owner === owner) return cached.value;
272
+ sourceFor(owner) {
273
+ const cached = this.bindings.get(owner);
274
+ if (cached !== void 0) return cached.source;
198
275
  const record = this.createMaterializedBinding(owner);
199
- this.bindings.set(key, record);
200
- cached?.release();
201
- return record.value;
276
+ this.bindings.set(owner, record);
277
+ return record.source;
202
278
  }
203
- resolveCurrent() {
204
- const current = this.sessions.list.getSnapshot().current;
205
- return current === void 0 ? this.absent : this.resolve(current) ?? this.absent;
279
+ publishMain() {
280
+ if (!this.active) return;
281
+ const byId = this.sessions.list.getSnapshot().byId;
282
+ const currentId = this.current.value.key;
283
+ const nextId = currentId !== void 0 && (this.sessions.retainInfo(currentId).getSnapshot().retainedBy.mainView ?? 0) > 0 ? currentId : Object.values(byId).find((candidate) => (candidate.retainedBy.mainView ?? 0) > 0)?.id;
284
+ this.watchMainRetention(nextId);
285
+ const owner = nextId === void 0 ? void 0 : this.sessions.binding(nextId);
286
+ const value = owner === void 0 ? this.absent.value : this.sourceFor(owner).value;
287
+ if (this.current.value === value) return;
288
+ this.current.value = value;
289
+ (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.current.listeners, "[ui-session] main binding");
206
290
  }
207
- publishCurrent() {
208
- const next = this.resolveCurrent();
209
- if (next === this.currentBinding) return;
210
- this.currentBinding = next;
211
- (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.currentListeners, "[ui-session] current binding");
291
+ watchMainRetention(sessionId) {
292
+ if (sessionId === this.mainRetainId) return;
293
+ this.disposeMainRetain();
294
+ this.mainRetainId = sessionId;
295
+ this.disposeMainRetain = sessionId === void 0 ? () => {} : this.sessions.retainInfo(sessionId).subscribe(() => {
296
+ this.publishMain();
297
+ });
212
298
  }
213
299
  publishPendingInteractions() {
214
300
  const next = /* @__PURE__ */ new Map();
@@ -223,20 +309,67 @@ window.__ModuleLoader__.load({
223
309
  const projected = new Map([...next].map(([sessionId, value]) => [sessionId, value.interaction]));
224
310
  if (samePendingInteractions(this.pendingSnapshot, projected)) return;
225
311
  this.pendingSnapshot = projected;
226
- (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.pendingListeners, "[ui-session] pending interactions");
312
+ this.publishStatus();
313
+ }
314
+ observeRunning(sessionId, running) {
315
+ const previous = this.running.get(sessionId);
316
+ const beforeBaseline = this.sessions.list.getSnapshot().phase === "pending";
317
+ this.running.set(sessionId, running);
318
+ if (running) this.completionUnread.delete(sessionId);
319
+ else if ((previous === true || previous === void 0 && beforeBaseline) && !this.isMain(sessionId)) this.completionUnread.add(sessionId);
320
+ this.publishStatus();
321
+ }
322
+ reconcileStatus() {
323
+ const list = this.sessions.list.getSnapshot();
324
+ const present = new Set(Object.keys(list.byId));
325
+ for (const id of present) {
326
+ const row = list.byId[id];
327
+ if (row === void 0) continue;
328
+ const previous = this.running.get(id);
329
+ if (previous === void 0) this.running.set(id, row.running);
330
+ else if (previous !== row.running) this.observeRunning(id, row.running);
331
+ if ((row.retainedBy.mainView ?? 0) > 0) this.completionUnread.delete(id);
332
+ }
333
+ if (list.phase === "ready") for (const id of this.running.keys()) {
334
+ if (present.has(id)) continue;
335
+ this.running.delete(id);
336
+ this.completionUnread.delete(id);
337
+ }
338
+ this.publishStatus();
339
+ }
340
+ isMain(sessionId) {
341
+ return (this.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ?? 0) > 0;
342
+ }
343
+ publishStatus() {
344
+ const ids = new Set([
345
+ ...Object.keys(this.sessions.list.getSnapshot().byId),
346
+ ...this.running.keys(),
347
+ ...this.pendingSnapshot.keys(),
348
+ ...this.completionUnread
349
+ ]);
350
+ const next = /* @__PURE__ */ new Map();
351
+ for (const id of ids) next.set(id, {
352
+ running: this.running.get(id),
353
+ pendingInteraction: this.pendingSnapshot.get(id),
354
+ completionUnread: this.completionUnread.has(id)
355
+ });
356
+ if (sameSessionStatus(this.statusSnapshot, next)) return;
357
+ this.statusSnapshot = next;
358
+ (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.statusListeners, "[ui-session] Session status");
227
359
  }
228
360
  createMaterializedBinding(owner) {
229
361
  const value = this.materialize(owner);
362
+ this.ctx.slots.bindStoreScope(value);
363
+ const source = createBindingSource(value);
230
364
  const releaseEffect = owner.ctx.effect(() => () => {
231
- if (this.bindings.get(owner.sessionId) !== record) return;
232
- this.bindings.delete(owner.sessionId);
233
- if (this.currentBinding !== value) return;
234
- this.currentBinding = this.absent;
235
- (0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.currentListeners, "[ui-session] current binding");
365
+ if (this.bindings.get(owner) === record) this.bindings.delete(owner);
366
+ source.value = this.absent.value;
367
+ (0, _deepseek_ai_dsh_client_store.notifySubscribers)(source.listeners, "[ui-session] Session binding");
368
+ this.publishMain();
236
369
  }, `ui-session: binding ${owner.sessionId}`);
237
370
  const record = {
238
371
  owner,
239
- value,
372
+ source,
240
373
  release: () => {
241
374
  releaseEffect();
242
375
  }
@@ -255,15 +388,13 @@ window.__ModuleLoader__.load({
255
388
  copyDeclared("keyed hook", keyedHooks, descriptor.keyedHooks, contribution.keyedHooks, finalProps);
256
389
  copyDeclared("prop", props, descriptor.props, contribution.props, finalProps);
257
390
  }
258
- const value = {
391
+ return {
259
392
  key: binding.sessionId,
260
393
  ctx: binding.ctx,
261
394
  hooks,
262
395
  keyedHooks,
263
396
  props
264
397
  };
265
- this.ctx.slots.bindStoreScope(value);
266
- return value;
267
398
  }
268
399
  materializeAbsent() {
269
400
  const hooks = {};
@@ -283,6 +414,20 @@ window.__ModuleLoader__.load({
283
414
  };
284
415
  }
285
416
  };
417
+ function createBindingSource(value) {
418
+ const source = {
419
+ value,
420
+ listeners: /* @__PURE__ */ new Set(),
421
+ getSnapshot: () => source.value,
422
+ subscribe: (listener) => {
423
+ source.listeners.add(listener);
424
+ return () => {
425
+ source.listeners.delete(listener);
426
+ };
427
+ }
428
+ };
429
+ return source;
430
+ }
286
431
  function validateContribution(descriptor, contribution) {
287
432
  rejectUndeclared("hook", descriptor.hooks, contribution.hooks);
288
433
  rejectUndeclared("keyed hook", descriptor.keyedHooks, contribution.keyedHooks);
@@ -311,19 +456,34 @@ window.__ModuleLoader__.load({
311
456
  finalProps.add(propName);
312
457
  }
313
458
  /** Required Controller and renderer services. */
314
- const inject = ["sessions", "slots"];
459
+ const inject = [
460
+ "sessions",
461
+ "slots",
462
+ "remote"
463
+ ];
315
464
  /**
316
465
  * Install the Session root source and scoped adapter.
317
466
  * @param ctx - Client Cordis context.
318
467
  */
319
468
  function apply(ctx) {
320
469
  const service = new UiSession(ctx, ctx.sessions);
321
- ctx.slots.provideRoot({ hooks: {
322
- sessions: ctx.sessions.list,
323
- sessionPendingInteraction: service.pendingInteractions
324
- } });
470
+ ctx.slots.provideRoot({
471
+ hooks: {
472
+ sessions: ctx.sessions.list,
473
+ sessionStatus: service.sessionStatus
474
+ },
475
+ keyedHooks: { sessionRetainInfo: (key) => ctx.sessions.retainInfo(key) }
476
+ });
325
477
  ctx.slots.installScope("session", service.adapter);
326
478
  }
479
+ function sameSessionStatus(left, right) {
480
+ if (left.size !== right.size) return false;
481
+ for (const [id, status] of left) {
482
+ const candidate = right.get(id);
483
+ if (candidate === void 0 || candidate.running !== status.running || candidate.pendingInteraction !== status.pendingInteraction || candidate.completionUnread !== status.completionUnread) return false;
484
+ }
485
+ return true;
486
+ }
327
487
  function samePendingInteractions(left, right) {
328
488
  if (left.size !== right.size) return false;
329
489
  for (const [sessionId, interaction] of left) if (right.get(sessionId) !== interaction) return false;
@@ -1,8 +1,8 @@
1
1
  /** Session Controller adapter for React selector hooks and Slot scope data. */
2
2
  import { Service, type Context } from '@deepseek-ai/cordis';
3
- import type { ISessions, SessionBinding, SessionListState, SessionSnapshot, UseProjection } from '@deepseek-ai/dsh-api-session-controller/client';
3
+ import type { ISessions, SessionBinding, SessionListState, SessionReference, SessionRetainInfo, SessionSnapshot, UseProjection } from '@deepseek-ai/dsh-api-session-controller/client';
4
4
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
5
- import type { HostObservable, KeyedStandardSource, MaybeSnapshotSelectorHook, SlotScopeAdapter, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
5
+ import type { HostObservable, KeyedStandardSource, MaybeSnapshotSelectorHook, SlotScopeAdapter, SnapshotSelectorHook, StandardSourceBinding } from '@deepseek-ai/dsh-client-ui-slots';
6
6
  /** Selector hook over the Session Controller list and current selection. */
7
7
  export type UseSessions = SnapshotSelectorHook<SessionListState>;
8
8
  /** Selector hook over one Session's lifecycle and control state. */
@@ -18,25 +18,61 @@ export interface SessionPendingInteractionBase {
18
18
  /** Session whose UI can answer this interaction. */
19
19
  readonly sessionId: SessionId;
20
20
  }
21
- /** Declaration-merged roster of domain-owned pending interaction values. */
21
+ /** Declaration-merged map of domain keys to their pending-interaction values. */
22
22
  export interface SessionPendingInteractionMap {
23
23
  }
24
- /** Every pending interaction contributed by the assembled Client. */
24
+ /** Union of every pending-interaction value contributed by the assembled Client. */
25
25
  export type SessionPendingInteraction = [
26
26
  keyof SessionPendingInteractionMap
27
27
  ] extends [never] ? SessionPendingInteractionBase : SessionPendingInteractionMap[keyof SessionPendingInteractionMap];
28
- /** Current effective pending interaction by Session. */
29
- export type SessionPendingInteractionSnapshot = ReadonlyMap<SessionId, SessionPendingInteraction>;
30
- /** Selector hook over Session-scoped pending interactions. */
31
- export type UseSessionPendingInteraction = SnapshotSelectorHook<SessionPendingInteractionSnapshot>;
28
+ /** Independent UI status facts for one Session identity. */
29
+ export interface SessionStatus {
30
+ /** Latest known running state; absent until a baseline or event establishes it. */
31
+ readonly running: boolean | undefined;
32
+ /** Highest-precedence domain request currently awaiting user interaction. */
33
+ readonly pendingInteraction: SessionPendingInteraction | undefined;
34
+ /** Whether an observed stop outside the main view still needs acknowledgement. */
35
+ readonly completionUnread: boolean;
36
+ }
37
+ /** Current UI status indexed by Session identity. */
38
+ export type SessionStatusSnapshot = ReadonlyMap<SessionId, SessionStatus>;
39
+ /** Selector hook over the unified Session UI status snapshot. */
40
+ export type UseSessionStatus = SnapshotSelectorHook<SessionStatusSnapshot>;
41
+ /** Selector hook for explicit or surrounding-Provider Session reference counts. */
42
+ export interface UseSessionRetainInfo {
43
+ /**
44
+ * Read the complete retain information for an explicit Session identity.
45
+ * @param sessionId - Session identity to inspect without retaining it.
46
+ * @returns current local reference counts, or absence while the source is unavailable.
47
+ */
48
+ (sessionId: SessionId): SessionRetainInfo | undefined;
49
+ /**
50
+ * Select from the retain information for an explicit Session identity.
51
+ * @param sessionId - Session identity to inspect without retaining it.
52
+ * @param selector - projection over the current value.
53
+ * @param equal - optional selected-value equality.
54
+ * @returns selected value.
55
+ */
56
+ <Selected>(sessionId: SessionId, selector: (value: SessionRetainInfo | undefined) => Selected, equal?: (left: Selected, right: Selected) => boolean): Selected;
57
+ /**
58
+ * Select from the surrounding Provider's Session retain information.
59
+ * @param selector - projection receiving absence outside a Session binding.
60
+ * @param equal - optional selected-value equality.
61
+ * @returns selected value.
62
+ */
63
+ <Selected>(selector: (value: SessionRetainInfo | undefined) => Selected, equal?: (left: Selected, right: Selected) => boolean): Selected;
64
+ }
32
65
  /** Publish one pending interaction and define how plugin teardown delegates it. */
33
66
  export type PendingInteractionPublisher<T extends SessionPendingInteractionBase> = (interaction: T, delegate: () => Promise<void>) => () => void;
34
67
  declare module '@deepseek-ai/dsh-client-ui-slots' {
68
+ interface SlotScopeTargetMap {
69
+ session: SessionReference;
70
+ }
35
71
  interface GlobalStandardProps {
36
72
  /** Session list and current selection. */
37
73
  useSessions: UseSessions;
38
- /** Pending user interaction presented by a Session-scoped UI consumer. */
39
- useSessionPendingInteraction: UseSessionPendingInteraction;
74
+ useSessionStatus: UseSessionStatus;
75
+ useSessionRetainInfo: UseSessionRetainInfo;
40
76
  }
41
77
  interface SessionStandardProps {
42
78
  /** Current Session lifecycle and control state. */
@@ -55,6 +91,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
55
91
  useProjection: UseProjection;
56
92
  }
57
93
  }
94
+ declare module '@deepseek-ai/dsh-api-session-controller/client' {
95
+ interface SessionReferenceSourceMap {
96
+ mainView: unknown;
97
+ }
98
+ }
58
99
  declare module '@deepseek-ai/cordis' {
59
100
  interface Context {
60
101
  /** Session Controller adapter and session-scoped source registry. */
@@ -85,15 +126,20 @@ export interface SessionSourceDescriptor<Hooks extends SessionSourceRoster = Ses
85
126
  export declare class UiSession extends Service {
86
127
  private readonly sessions;
87
128
  private readonly descriptors;
88
- private bindings;
89
- private absent;
90
- private currentBinding;
91
- private readonly currentListeners;
129
+ private readonly bindings;
130
+ private readonly absent;
131
+ private readonly current;
92
132
  private readonly pendingDomains;
93
133
  private pendingSnapshot;
94
- private readonly pendingListeners;
95
- /** Root source of pending UI interactions, independent from Controller snapshots. */
96
- readonly pendingInteractions: HostObservable<SessionPendingInteractionSnapshot>;
134
+ private readonly running;
135
+ private readonly completionUnread;
136
+ private statusSnapshot;
137
+ private readonly statusListeners;
138
+ private mainRetainId;
139
+ private disposeMainRetain;
140
+ private active;
141
+ /** Root source combining running, pending-interaction, and completion-reminder facts. */
142
+ readonly sessionStatus: HostObservable<SessionStatusSnapshot>;
97
143
  /** Renderer-facing adapter for `session` and `session-maybe` scopes. */
98
144
  readonly adapter: SlotScopeAdapter;
99
145
  /**
@@ -101,6 +147,13 @@ export declare class UiSession extends Service {
101
147
  * @param sessions - Controller-owned Session object layer.
102
148
  */
103
149
  constructor(ctx: Context, sessions: ISessions);
150
+ /**
151
+ * Resolve a stable renderer source for an owned Session reference or explicit absence.
152
+ * @param reference - active reference supplied by the Provider owner, or absence.
153
+ * @returns the binding source, which falls back to the absent projection when its generation ends.
154
+ * @throws when the reference does not belong to the active Controller generation.
155
+ */
156
+ bindingSource(reference: SessionReference | undefined): HostObservable<StandardSourceBinding>;
104
157
  /**
105
158
  * Register one Session-scoped standard-source contribution.
106
159
  * @param descriptor - static member roster and per-binding resolver.
@@ -116,10 +169,14 @@ export declare class UiSession extends Service {
116
169
  */
117
170
  registerPendingInteraction<T extends SessionPendingInteractionBase>(precedence: (interaction: T) => number): PendingInteractionPublisher<T>;
118
171
  private rebuildBindings;
119
- private resolve;
120
- private resolveCurrent;
121
- private publishCurrent;
172
+ private sourceFor;
173
+ private publishMain;
174
+ private watchMainRetention;
122
175
  private publishPendingInteractions;
176
+ private observeRunning;
177
+ private reconcileStatus;
178
+ private isMain;
179
+ private publishStatus;
123
180
  private createMaterializedBinding;
124
181
  private materialize;
125
182
  private materializeAbsent;
@@ -1,11 +1,11 @@
1
1
  /** Session-owned rendering semantics for the standard SessionProvider seat. */
2
- import { type ReactNode } from 'react';
2
+ import type { ReactNode } from 'react';
3
3
  import type { SessionAreaProps, StandardSourceBinding } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  /**
5
5
  * Render the selected Session body or its empty branch.
6
6
  * @param binding - current Session scope binding.
7
7
  * @param props - standard Session area render props.
8
- * @returns the selected Session subtree, keyed by Session identity.
8
+ * @returns the selected Session subtree.
9
9
  */
10
10
  export declare function renderSessionArea(binding: StandardSourceBinding, { empty, children }: SessionAreaProps): ReactNode;
11
11
  //# sourceMappingURL=session-provider.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-session",
3
3
  "description": "Session Controller adapter for React and session-scoped slots",
4
- "version": "0.1.5-rc.2",
4
+ "version": "0.1.6-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -28,6 +28,7 @@
28
28
  "dsh": {
29
29
  "client": {
30
30
  "inject": [
31
+ "@deepseek-ai/dsh-api-remotes",
31
32
  "@deepseek-ai/dsh-api-session-controller",
32
33
  "@deepseek-ai/dsh-client-ui-renderer"
33
34
  ],
@@ -42,11 +43,13 @@
42
43
  "@types/react": "~18.3.1",
43
44
  "react": "^18.2.0",
44
45
  "@deepseek-ai/cordis": "^4.0.2",
45
- "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
46
- "@deepseek-ai/dsh-client-store": "^0.1.5-rc.2",
47
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
48
- "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2"
46
+ "@deepseek-ai/dsh-api-remotes": "^0.1.6-alpha.2",
47
+ "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.2",
48
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.2",
49
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.2",
50
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.2",
51
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
52
+ "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.2"
50
53
  },
51
54
  "files": [
52
55
  "lib/index.js",