@prettier-ai/dsh-client-ui-session 0.1.2-alpha.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +45 -0
- package/README.zh.md +45 -0
- package/lib/client.js +340 -0
- package/lib/index.js +6 -0
- package/lib/invariant.js +16 -0
- package/lib/types/client/index.d.ts +135 -0
- package/lib/types/client/session-provider.d.ts +11 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/invariant.d.ts +13 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-session/README.md
|
|
5
|
+
README.md: a98692cab4c40a53d7e4b78eec91a45af85fe76f
|
|
6
|
+
README.zh.md: 4b8e26da6b7255bd90af853315ad551c2ad85801
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "React and Slot adapters for Session Controller lists, interaction state, and per-session context."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
# @deepseek-ai/dsh-client-ui-session
|
|
6
|
+
|
|
7
|
+
English | [中文](README.zh.md)
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
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.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Model Experience](#model-experience)
|
|
16
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
17
|
+
- [Dev Note](#dev-note)
|
|
18
|
+
|
|
19
|
+
-----
|
|
20
|
+
|
|
21
|
+
<a id="model-experience"></a>
|
|
22
|
+
## Model Experience
|
|
23
|
+
|
|
24
|
+
None, as this package adapts browser-side Session state and registers nothing model-facing.
|
|
25
|
+
|
|
26
|
+
#### KV Cache effect
|
|
27
|
+
|
|
28
|
+
None; Session selectors and Slot scopes do not assemble model requests.
|
|
29
|
+
|
|
30
|
+
## Known Limitations and Deferred Work
|
|
31
|
+
|
|
32
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
33
|
+
|
|
34
|
+
- **Pending interactions are process-local projections** — the owning Remote waterfall must replay an outstanding request after a browser reconnect.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
<a id="dev-note"></a>
|
|
38
|
+
### Dev Note
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
42
|
+
|
|
43
|
+
None.
|
|
44
|
+
|
|
45
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "面向 Session Controller 列表、交互状态与逐会话上下文的 React 与 Slot 适配器。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
# @deepseek-ai/dsh-client-ui-session
|
|
6
|
+
|
|
7
|
+
[English](README.md) | 中文
|
|
8
|
+
|
|
9
|
+
## 概述
|
|
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 状态时,请使用它。
|
|
12
|
+
|
|
13
|
+
## 目录
|
|
14
|
+
|
|
15
|
+
- [模型体验](#model-experience)
|
|
16
|
+
- [已知限制与暂缓事项](#known-limitations-and-deferred-work)
|
|
17
|
+
- [开发备注](#dev-note)
|
|
18
|
+
|
|
19
|
+
-----
|
|
20
|
+
|
|
21
|
+
<a id="model-experience"></a>
|
|
22
|
+
## 模型体验
|
|
23
|
+
|
|
24
|
+
无,因为本包适配浏览器侧 Session 状态,不注册任何面向模型的内容。
|
|
25
|
+
|
|
26
|
+
#### KV Cache 影响
|
|
27
|
+
|
|
28
|
+
无;Session selector 与 Slot scope 不会组装模型请求。
|
|
29
|
+
|
|
30
|
+
## 已知限制与暂缓事项
|
|
31
|
+
|
|
32
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
33
|
+
|
|
34
|
+
- **Pending interaction 是进程本地投影**——浏览器重连后,所属 Remote waterfall 必须重放仍未完成的请求。
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
<a id="dev-note"></a>
|
|
38
|
+
### 开发备注
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>维护者工作上下文——点击展开</summary>
|
|
42
|
+
|
|
43
|
+
无。
|
|
44
|
+
|
|
45
|
+
</details>
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@prettier-ai/dsh-client-ui-session",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _prettier_ai_cordis = require("@prettier-ai/cordis");
|
|
8
|
+
let _prettier_ai_dsh_client_store = require("@prettier-ai/dsh-client-store");
|
|
9
|
+
let _prettier_ai_dsh_client_ui_slots = require("@prettier-ai/dsh-client-ui-slots");
|
|
10
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
let react = require("react");
|
|
12
|
+
//#region lib/types/client/session-provider.js
|
|
13
|
+
/** Session-owned rendering semantics for the standard SessionProvider seat. */
|
|
14
|
+
/**
|
|
15
|
+
* Render the selected Session body or its empty branch.
|
|
16
|
+
* @param binding - current Session scope binding.
|
|
17
|
+
* @param props - standard Session area render props.
|
|
18
|
+
* @returns the selected Session subtree, keyed by Session identity.
|
|
19
|
+
*/
|
|
20
|
+
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);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region lib/types/client/index.js
|
|
27
|
+
/** Session Controller adapter for React selector hooks and Slot scope data. */
|
|
28
|
+
var PendingInteractionDomain = class {
|
|
29
|
+
precedence;
|
|
30
|
+
changed;
|
|
31
|
+
values = /* @__PURE__ */ new Map();
|
|
32
|
+
constructor(precedence, changed) {
|
|
33
|
+
this.precedence = precedence;
|
|
34
|
+
this.changed = changed;
|
|
35
|
+
}
|
|
36
|
+
valuesSnapshot() {
|
|
37
|
+
return [...this.values.values()].map((entry) => entry.interaction);
|
|
38
|
+
}
|
|
39
|
+
publish(interaction, delegate) {
|
|
40
|
+
if (this.values.has(interaction.key)) throw new Error(`ui-session: duplicate pending interaction key '${interaction.key}'`);
|
|
41
|
+
this.values.set(interaction.key, {
|
|
42
|
+
interaction,
|
|
43
|
+
delegate
|
|
44
|
+
});
|
|
45
|
+
this.changed();
|
|
46
|
+
let active = true;
|
|
47
|
+
return () => {
|
|
48
|
+
if (!active) return;
|
|
49
|
+
active = false;
|
|
50
|
+
if (!this.values.delete(interaction.key)) return;
|
|
51
|
+
this.changed();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Remove every pending value and return the operations that settle their owners. */
|
|
55
|
+
release() {
|
|
56
|
+
const delegates = [...this.values.values()].map((entry) => entry.delegate);
|
|
57
|
+
this.values.clear();
|
|
58
|
+
return delegates;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const BUILTIN_SOURCE = {
|
|
62
|
+
hooks: ["session"],
|
|
63
|
+
keyedHooks: ["projection"],
|
|
64
|
+
props: ["sessionId"],
|
|
65
|
+
resolve: (binding) => ({
|
|
66
|
+
hooks: { session: binding.session },
|
|
67
|
+
keyedHooks: { projection: (key) => binding.session.projections.faceOf(key) },
|
|
68
|
+
props: { sessionId: binding.sessionId }
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
/** Session-scoped source roster and renderer adapter. */
|
|
72
|
+
var UiSession = class extends _prettier_ai_cordis.Service {
|
|
73
|
+
sessions;
|
|
74
|
+
descriptors = [BUILTIN_SOURCE];
|
|
75
|
+
bindings = /* @__PURE__ */ new Map();
|
|
76
|
+
absent;
|
|
77
|
+
currentBinding;
|
|
78
|
+
currentListeners = /* @__PURE__ */ new Set();
|
|
79
|
+
pendingDomains = [];
|
|
80
|
+
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,
|
|
85
|
+
subscribe: (listener) => {
|
|
86
|
+
this.pendingListeners.add(listener);
|
|
87
|
+
return () => {
|
|
88
|
+
this.pendingListeners.delete(listener);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** Renderer-facing adapter for `session` and `session-maybe` scopes. */
|
|
93
|
+
adapter;
|
|
94
|
+
/**
|
|
95
|
+
* @param ctx - Client root context.
|
|
96
|
+
* @param sessions - Controller-owned Session object layer.
|
|
97
|
+
*/
|
|
98
|
+
constructor(ctx, sessions) {
|
|
99
|
+
super(ctx, "uiSession");
|
|
100
|
+
this.sessions = sessions;
|
|
101
|
+
this.absent = this.materializeAbsent();
|
|
102
|
+
this.currentBinding = this.resolveCurrent();
|
|
103
|
+
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),
|
|
114
|
+
renderArea: renderSessionArea
|
|
115
|
+
};
|
|
116
|
+
ctx.effect(() => {
|
|
117
|
+
const disposeList = sessions.list.subscribe(() => {
|
|
118
|
+
this.publishCurrent();
|
|
119
|
+
});
|
|
120
|
+
return () => {
|
|
121
|
+
disposeList();
|
|
122
|
+
const records = [...this.bindings.values()];
|
|
123
|
+
this.bindings.clear();
|
|
124
|
+
for (const record of records) record.release();
|
|
125
|
+
};
|
|
126
|
+
}, "ui-session: Session binding projection");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Register one Session-scoped standard-source contribution.
|
|
130
|
+
* @param descriptor - static member roster and per-binding resolver.
|
|
131
|
+
* @returns disposer owned by the caller's Cordis fiber.
|
|
132
|
+
*/
|
|
133
|
+
provide(descriptor) {
|
|
134
|
+
const runtimeDescriptor = descriptor;
|
|
135
|
+
const dispose = this.ctx.effect(() => {
|
|
136
|
+
this.descriptors.push(runtimeDescriptor);
|
|
137
|
+
try {
|
|
138
|
+
this.rebuildBindings();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.descriptors.pop();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
return () => {
|
|
144
|
+
const index = this.descriptors.indexOf(runtimeDescriptor);
|
|
145
|
+
this.descriptors.splice(index, 1);
|
|
146
|
+
this.rebuildBindings();
|
|
147
|
+
};
|
|
148
|
+
}, "uiSession.provide()");
|
|
149
|
+
return () => {
|
|
150
|
+
dispose();
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Register one pending-interaction domain and return its publication function.
|
|
155
|
+
* Domain teardown first removes its visible values, then delegates and awaits
|
|
156
|
+
* every still-active owner request.
|
|
157
|
+
* @param precedence - deterministic cross-domain precedence; larger values win.
|
|
158
|
+
* @returns a function that publishes one interaction and its teardown delegation.
|
|
159
|
+
*/
|
|
160
|
+
registerPendingInteraction(precedence) {
|
|
161
|
+
const domain = new PendingInteractionDomain(precedence, () => {
|
|
162
|
+
this.publishPendingInteractions();
|
|
163
|
+
});
|
|
164
|
+
const runtimeDomain = domain;
|
|
165
|
+
this.ctx.effect(() => {
|
|
166
|
+
this.pendingDomains.push(runtimeDomain);
|
|
167
|
+
this.publishPendingInteractions();
|
|
168
|
+
return async () => {
|
|
169
|
+
const delegates = domain.release();
|
|
170
|
+
const index = this.pendingDomains.indexOf(runtimeDomain);
|
|
171
|
+
this.pendingDomains.splice(index, 1);
|
|
172
|
+
this.publishPendingInteractions();
|
|
173
|
+
await Promise.allSettled(delegates.map((delegate) => Promise.resolve().then(delegate)));
|
|
174
|
+
};
|
|
175
|
+
}, "uiSession.registerPendingInteraction()");
|
|
176
|
+
return (interaction, delegate) => domain.publish(interaction, delegate);
|
|
177
|
+
}
|
|
178
|
+
rebuildBindings() {
|
|
179
|
+
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();
|
|
192
|
+
}
|
|
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;
|
|
198
|
+
const record = this.createMaterializedBinding(owner);
|
|
199
|
+
this.bindings.set(key, record);
|
|
200
|
+
cached?.release();
|
|
201
|
+
return record.value;
|
|
202
|
+
}
|
|
203
|
+
resolveCurrent() {
|
|
204
|
+
const current = this.sessions.list.getSnapshot().current;
|
|
205
|
+
return current === void 0 ? this.absent : this.resolve(current) ?? this.absent;
|
|
206
|
+
}
|
|
207
|
+
publishCurrent() {
|
|
208
|
+
const next = this.resolveCurrent();
|
|
209
|
+
if (next === this.currentBinding) return;
|
|
210
|
+
this.currentBinding = next;
|
|
211
|
+
(0, _prettier_ai_dsh_client_store.notifySubscribers)(this.currentListeners, "[ui-session] current binding");
|
|
212
|
+
}
|
|
213
|
+
publishPendingInteractions() {
|
|
214
|
+
const next = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const domain of this.pendingDomains) for (const interaction of domain.valuesSnapshot()) {
|
|
216
|
+
const precedence = domain.precedence(interaction);
|
|
217
|
+
const previous = next.get(interaction.sessionId);
|
|
218
|
+
if (previous === void 0 || precedence >= previous.precedence) next.set(interaction.sessionId, {
|
|
219
|
+
interaction,
|
|
220
|
+
precedence
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const projected = new Map([...next].map(([sessionId, value]) => [sessionId, value.interaction]));
|
|
224
|
+
if (samePendingInteractions(this.pendingSnapshot, projected)) return;
|
|
225
|
+
this.pendingSnapshot = projected;
|
|
226
|
+
(0, _prettier_ai_dsh_client_store.notifySubscribers)(this.pendingListeners, "[ui-session] pending interactions");
|
|
227
|
+
}
|
|
228
|
+
createMaterializedBinding(owner) {
|
|
229
|
+
const value = this.materialize(owner);
|
|
230
|
+
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, _prettier_ai_dsh_client_store.notifySubscribers)(this.currentListeners, "[ui-session] current binding");
|
|
236
|
+
}, `ui-session: binding ${owner.sessionId}`);
|
|
237
|
+
const record = {
|
|
238
|
+
owner,
|
|
239
|
+
value,
|
|
240
|
+
release: () => {
|
|
241
|
+
releaseEffect();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
return record;
|
|
245
|
+
}
|
|
246
|
+
materialize(binding) {
|
|
247
|
+
const hooks = {};
|
|
248
|
+
const keyedHooks = {};
|
|
249
|
+
const props = {};
|
|
250
|
+
const finalProps = /* @__PURE__ */ new Set();
|
|
251
|
+
for (const descriptor of this.descriptors) {
|
|
252
|
+
const contribution = descriptor.resolve(binding);
|
|
253
|
+
validateContribution(descriptor, contribution);
|
|
254
|
+
copyDeclared("hook", hooks, descriptor.hooks, contribution.hooks, finalProps);
|
|
255
|
+
copyDeclared("keyed hook", keyedHooks, descriptor.keyedHooks, contribution.keyedHooks, finalProps);
|
|
256
|
+
copyDeclared("prop", props, descriptor.props, contribution.props, finalProps);
|
|
257
|
+
}
|
|
258
|
+
const value = {
|
|
259
|
+
key: binding.sessionId,
|
|
260
|
+
ctx: binding.ctx,
|
|
261
|
+
hooks,
|
|
262
|
+
keyedHooks,
|
|
263
|
+
props
|
|
264
|
+
};
|
|
265
|
+
this.ctx.slots.bindStoreScope(value);
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
materializeAbsent() {
|
|
269
|
+
const hooks = {};
|
|
270
|
+
const keyedHooks = {};
|
|
271
|
+
const props = {};
|
|
272
|
+
const finalProps = /* @__PURE__ */ new Set();
|
|
273
|
+
for (const descriptor of this.descriptors) {
|
|
274
|
+
declareAbsent("hook", hooks, descriptor.hooks, finalProps);
|
|
275
|
+
declareAbsent("keyed hook", keyedHooks, descriptor.keyedHooks, finalProps);
|
|
276
|
+
declareAbsent("prop", props, descriptor.props, finalProps);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
key: void 0,
|
|
280
|
+
hooks,
|
|
281
|
+
keyedHooks,
|
|
282
|
+
props
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function validateContribution(descriptor, contribution) {
|
|
287
|
+
rejectUndeclared("hook", descriptor.hooks, contribution.hooks);
|
|
288
|
+
rejectUndeclared("keyed hook", descriptor.keyedHooks, contribution.keyedHooks);
|
|
289
|
+
rejectUndeclared("prop", descriptor.props, contribution.props);
|
|
290
|
+
}
|
|
291
|
+
function rejectUndeclared(kind, declared, values) {
|
|
292
|
+
for (const name of Object.keys(values ?? {})) if (!(declared ?? []).includes(name)) throw new Error(`uiSession.provide: undeclared ${kind} '${name}'`);
|
|
293
|
+
}
|
|
294
|
+
function copyDeclared(kind, target, declared, values, finalProps) {
|
|
295
|
+
for (const name of declared ?? []) {
|
|
296
|
+
claimStandardProp(kind, name, finalProps);
|
|
297
|
+
const value = values?.[name];
|
|
298
|
+
if (value === void 0) throw new Error(`uiSession.provide: missing ${kind} '${name}'`);
|
|
299
|
+
target[name] = value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function declareAbsent(kind, target, declared, finalProps) {
|
|
303
|
+
for (const name of declared ?? []) {
|
|
304
|
+
claimStandardProp(kind, name, finalProps);
|
|
305
|
+
target[name] = void 0;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function claimStandardProp(kind, name, finalProps) {
|
|
309
|
+
const propName = kind === "prop" ? name : (0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name);
|
|
310
|
+
if (finalProps.has(propName)) throw new Error(`uiSession.provide: duplicate ${kind} '${name}' at prop '${propName}'`);
|
|
311
|
+
finalProps.add(propName);
|
|
312
|
+
}
|
|
313
|
+
/** Required Controller and renderer services. */
|
|
314
|
+
const inject = ["sessions", "slots"];
|
|
315
|
+
/**
|
|
316
|
+
* Install the Session root source and scoped adapter.
|
|
317
|
+
* @param ctx - Client Cordis context.
|
|
318
|
+
*/
|
|
319
|
+
function apply(ctx) {
|
|
320
|
+
const service = new UiSession(ctx, ctx.sessions);
|
|
321
|
+
ctx.slots.provideRoot({ hooks: {
|
|
322
|
+
sessions: ctx.sessions.list,
|
|
323
|
+
sessionPendingInteraction: service.pendingInteractions
|
|
324
|
+
} });
|
|
325
|
+
ctx.slots.installScope("session", service.adapter);
|
|
326
|
+
}
|
|
327
|
+
function samePendingInteractions(left, right) {
|
|
328
|
+
if (left.size !== right.size) return false;
|
|
329
|
+
for (const [sessionId, interaction] of left) if (right.get(sessionId) !== interaction) return false;
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
exports.UiSession = UiSession;
|
|
334
|
+
exports.apply = apply;
|
|
335
|
+
exports.inject = inject;
|
|
336
|
+
return module.exports;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
const PACKAGE_NAME = "@prettier-ai/dsh-client-ui-session";
|
|
3
|
+
/** Cordis companion plugin name. */
|
|
4
|
+
const name = "client-ui-session-invariant";
|
|
5
|
+
/** Service required before the companion reserves package ownership. */
|
|
6
|
+
const inject = ["invariants"];
|
|
7
|
+
/** No runtime invariant: the adapter materialization path enforces Session binding consistency. */
|
|
8
|
+
const install = () => {};
|
|
9
|
+
/**
|
|
10
|
+
* Register this package's invariant companion.
|
|
11
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
12
|
+
* @returns the installed registration's disposer.
|
|
13
|
+
*/
|
|
14
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
15
|
+
//#endregion
|
|
16
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Session Controller adapter for React selector hooks and Slot scope data. */
|
|
2
|
+
import { Service, type Context } from '@prettier-ai/cordis';
|
|
3
|
+
import type { ISessions, SessionBinding, SessionListState, SessionSnapshot, UseProjection } from '@prettier-ai/dsh-api-session-controller/client';
|
|
4
|
+
import type { SessionId } from '@prettier-ai/dsh-session/types';
|
|
5
|
+
import type { HostObservable, KeyedStandardSource, MaybeSnapshotSelectorHook, SlotScopeAdapter, SnapshotSelectorHook } from '@prettier-ai/dsh-client-ui-slots';
|
|
6
|
+
/** Selector hook over the Session Controller list and current selection. */
|
|
7
|
+
export type UseSessions = SnapshotSelectorHook<SessionListState>;
|
|
8
|
+
/** Selector hook over one Session's lifecycle and control state. */
|
|
9
|
+
export type SessionSnapshotSelector = SnapshotSelectorHook<SessionSnapshot>;
|
|
10
|
+
/** Public name for the Session lifecycle selector hook. */
|
|
11
|
+
export type UseSession = SessionSnapshotSelector;
|
|
12
|
+
/** Common identity carried by every Session-scoped pending interaction. */
|
|
13
|
+
export interface SessionPendingInteractionBase {
|
|
14
|
+
/** Opaque request identity; a replacement request must use a new key. */
|
|
15
|
+
readonly key: string;
|
|
16
|
+
/** Domain-owned presentation discriminator. */
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
/** Session whose UI can answer this interaction. */
|
|
19
|
+
readonly sessionId: SessionId;
|
|
20
|
+
}
|
|
21
|
+
/** Declaration-merged roster of domain-owned pending interaction values. */
|
|
22
|
+
export interface SessionPendingInteractionMap {
|
|
23
|
+
}
|
|
24
|
+
/** Every pending interaction contributed by the assembled Client. */
|
|
25
|
+
export type SessionPendingInteraction = [
|
|
26
|
+
keyof SessionPendingInteractionMap
|
|
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>;
|
|
32
|
+
/** Publish one pending interaction and define how plugin teardown delegates it. */
|
|
33
|
+
export type PendingInteractionPublisher<T extends SessionPendingInteractionBase> = (interaction: T, delegate: () => Promise<void>) => () => void;
|
|
34
|
+
declare module '@prettier-ai/dsh-client-ui-slots' {
|
|
35
|
+
interface GlobalStandardProps {
|
|
36
|
+
/** Session list and current selection. */
|
|
37
|
+
useSessions: UseSessions;
|
|
38
|
+
/** Pending user interaction presented by a Session-scoped UI consumer. */
|
|
39
|
+
useSessionPendingInteraction: UseSessionPendingInteraction;
|
|
40
|
+
}
|
|
41
|
+
interface SessionStandardProps {
|
|
42
|
+
/** Current Session lifecycle and control state. */
|
|
43
|
+
useSession: SessionSnapshotSelector;
|
|
44
|
+
/** Current Session identity. */
|
|
45
|
+
sessionId: SessionId;
|
|
46
|
+
/** Host-computed projection values addressed by projection key. */
|
|
47
|
+
useProjection: UseProjection;
|
|
48
|
+
}
|
|
49
|
+
interface SessionMaybeStandardProps {
|
|
50
|
+
/** Current Session state, absent while no Session is selected. */
|
|
51
|
+
useSession: MaybeSnapshotSelectorHook<SessionSnapshot>;
|
|
52
|
+
/** Current Session identity, absent while no Session is selected. */
|
|
53
|
+
sessionId: SessionId | undefined;
|
|
54
|
+
/** Host-computed projection values; every key is absent without a Session. */
|
|
55
|
+
useProjection: UseProjection;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
declare module '@prettier-ai/cordis' {
|
|
59
|
+
interface Context {
|
|
60
|
+
/** Session Controller adapter and session-scoped source registry. */
|
|
61
|
+
uiSession: UiSession;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
type SessionSourceRoster = readonly string[] | undefined;
|
|
65
|
+
type SessionSourceRecord<Roster extends SessionSourceRoster, Value> = Roster extends readonly string[] ? Readonly<Record<Roster[number], Value>> : never;
|
|
66
|
+
/** Bare values produced by one Session-scoped source contribution. */
|
|
67
|
+
export interface SessionSourceContribution<Hooks extends SessionSourceRoster = SessionSourceRoster, KeyedHooks extends SessionSourceRoster = SessionSourceRoster, Props extends SessionSourceRoster = SessionSourceRoster> {
|
|
68
|
+
readonly hooks?: SessionSourceRecord<Hooks, HostObservable<unknown>>;
|
|
69
|
+
readonly keyedHooks?: SessionSourceRecord<KeyedHooks, KeyedStandardSource>;
|
|
70
|
+
readonly props?: SessionSourceRecord<Props, unknown>;
|
|
71
|
+
}
|
|
72
|
+
/** Static roster and per-Session resolver for one standard-props contribution. */
|
|
73
|
+
export interface SessionSourceDescriptor<Hooks extends SessionSourceRoster = SessionSourceRoster, KeyedHooks extends SessionSourceRoster = SessionSourceRoster, Props extends SessionSourceRoster = SessionSourceRoster> {
|
|
74
|
+
readonly hooks?: Hooks;
|
|
75
|
+
readonly keyedHooks?: KeyedHooks;
|
|
76
|
+
readonly props?: Props;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve every declared member for one Session binding.
|
|
79
|
+
* @param binding - Controller-owned Session binding.
|
|
80
|
+
* @returns all declared bare sources and stable props.
|
|
81
|
+
*/
|
|
82
|
+
resolve(binding: SessionBinding): SessionSourceContribution<NoInfer<Hooks>, NoInfer<KeyedHooks>, NoInfer<Props>>;
|
|
83
|
+
}
|
|
84
|
+
/** Session-scoped source roster and renderer adapter. */
|
|
85
|
+
export declare class UiSession extends Service {
|
|
86
|
+
private readonly sessions;
|
|
87
|
+
private readonly descriptors;
|
|
88
|
+
private bindings;
|
|
89
|
+
private absent;
|
|
90
|
+
private currentBinding;
|
|
91
|
+
private readonly currentListeners;
|
|
92
|
+
private readonly pendingDomains;
|
|
93
|
+
private pendingSnapshot;
|
|
94
|
+
private readonly pendingListeners;
|
|
95
|
+
/** Root source of pending UI interactions, independent from Controller snapshots. */
|
|
96
|
+
readonly pendingInteractions: HostObservable<SessionPendingInteractionSnapshot>;
|
|
97
|
+
/** Renderer-facing adapter for `session` and `session-maybe` scopes. */
|
|
98
|
+
readonly adapter: SlotScopeAdapter;
|
|
99
|
+
/**
|
|
100
|
+
* @param ctx - Client root context.
|
|
101
|
+
* @param sessions - Controller-owned Session object layer.
|
|
102
|
+
*/
|
|
103
|
+
constructor(ctx: Context, sessions: ISessions);
|
|
104
|
+
/**
|
|
105
|
+
* Register one Session-scoped standard-source contribution.
|
|
106
|
+
* @param descriptor - static member roster and per-binding resolver.
|
|
107
|
+
* @returns disposer owned by the caller's Cordis fiber.
|
|
108
|
+
*/
|
|
109
|
+
provide<const Hooks extends SessionSourceRoster = undefined, const KeyedHooks extends SessionSourceRoster = undefined, const Props extends SessionSourceRoster = undefined>(descriptor: SessionSourceDescriptor<Hooks, KeyedHooks, Props>): () => void;
|
|
110
|
+
/**
|
|
111
|
+
* Register one pending-interaction domain and return its publication function.
|
|
112
|
+
* Domain teardown first removes its visible values, then delegates and awaits
|
|
113
|
+
* every still-active owner request.
|
|
114
|
+
* @param precedence - deterministic cross-domain precedence; larger values win.
|
|
115
|
+
* @returns a function that publishes one interaction and its teardown delegation.
|
|
116
|
+
*/
|
|
117
|
+
registerPendingInteraction<T extends SessionPendingInteractionBase>(precedence: (interaction: T) => number): PendingInteractionPublisher<T>;
|
|
118
|
+
private rebuildBindings;
|
|
119
|
+
private resolve;
|
|
120
|
+
private resolveCurrent;
|
|
121
|
+
private publishCurrent;
|
|
122
|
+
private publishPendingInteractions;
|
|
123
|
+
private createMaterializedBinding;
|
|
124
|
+
private materialize;
|
|
125
|
+
private materializeAbsent;
|
|
126
|
+
}
|
|
127
|
+
/** Required Controller and renderer services. */
|
|
128
|
+
export declare const inject: string[];
|
|
129
|
+
/**
|
|
130
|
+
* Install the Session root source and scoped adapter.
|
|
131
|
+
* @param ctx - Client Cordis context.
|
|
132
|
+
*/
|
|
133
|
+
export declare function apply(ctx: Context): void;
|
|
134
|
+
export {};
|
|
135
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Session-owned rendering semantics for the standard SessionProvider seat. */
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { SessionAreaProps, StandardSourceBinding } from '@prettier-ai/dsh-client-ui-slots';
|
|
4
|
+
/**
|
|
5
|
+
* Render the selected Session body or its empty branch.
|
|
6
|
+
* @param binding - current Session scope binding.
|
|
7
|
+
* @param props - standard Session area render props.
|
|
8
|
+
* @returns the selected Session subtree, keyed by Session identity.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderSessionArea(binding: StandardSourceBinding, { empty, children }: SessionAreaProps): ReactNode;
|
|
11
|
+
//# sourceMappingURL=session-provider.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Package-owned invariant companion for the Session UI adapter. */
|
|
2
|
+
import type { Context } from '@prettier-ai/cordis';
|
|
3
|
+
/** Cordis companion plugin name. */
|
|
4
|
+
export declare const name = "client-ui-session-invariant";
|
|
5
|
+
/** Service required before the companion reserves package ownership. */
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Register this package's invariant companion.
|
|
9
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
10
|
+
* @returns the installed registration's disposer.
|
|
11
|
+
*/
|
|
12
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
13
|
+
//# sourceMappingURL=invariant.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prettier-ai/dsh-client-ui-session",
|
|
3
|
+
"description": "Session Controller adapter for React and session-scoped slots",
|
|
4
|
+
"version": "0.1.2-alpha.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/client/ui-session"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./lib/types/client/index.d.ts",
|
|
27
|
+
"default": "./lib/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"dsh": {
|
|
33
|
+
"client": {
|
|
34
|
+
"inject": [
|
|
35
|
+
"@prettier-ai/dsh-api-session-controller",
|
|
36
|
+
"@prettier-ai/dsh-client-ui-renderer"
|
|
37
|
+
],
|
|
38
|
+
"platform": "web"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@prettier-ai/cordis": "^4.0.1",
|
|
44
|
+
"@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
|
|
45
|
+
"@prettier-ai/dsh-api-session-controller": "^0.1.2-alpha.1",
|
|
46
|
+
"@prettier-ai/dsh-session": "^0.1.2-alpha.1",
|
|
47
|
+
"@prettier-ai/dsh-invariants": "^0.1.2-alpha.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/react": "~18.3.1",
|
|
51
|
+
"react": "^18.2.0",
|
|
52
|
+
"@prettier-ai/cordis": "^4.0.1",
|
|
53
|
+
"@prettier-ai/dsh-client-store": "^0.1.2-alpha.1",
|
|
54
|
+
"@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
|
|
55
|
+
"@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
|
|
56
|
+
"@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
|
|
57
|
+
"@prettier-ai/dsh-api-session-controller": "^0.1.2-alpha.1",
|
|
58
|
+
"@prettier-ai/dsh-session": "^0.1.2-alpha.1"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"lib/index.js",
|
|
62
|
+
"lib/invariant.js",
|
|
63
|
+
"lib/client.js",
|
|
64
|
+
"lib/types/**/*.d.ts"
|
|
65
|
+
],
|
|
66
|
+
"scripts": {
|
|
67
|
+
"bundle": "tsdown",
|
|
68
|
+
"watch": "tsdown --watch"
|
|
69
|
+
}
|
|
70
|
+
}
|