@qilin-agent/client-ui-sidechat 0.0.0-stage → 3.1.3

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
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.
@@ -0,0 +1,34 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-sidechat/README.md
5
+ /:
6
+ en: 13b9891fdae386a6
7
+ zh: 29b2b488971ae2d6
8
+ /qilin-client-ui-sidechat:
9
+ en: cecb3667f224db3e
10
+ zh: a13ad6f7b8d5a793
11
+ /qilin-client-ui-sidechat/summary:
12
+ en: be7936e38dba9d2a
13
+ zh: b1c5bf1865ae8e46
14
+ /qilin-client-ui-sidechat/table-of-contents:
15
+ en: a7b1bcef44d01a5e
16
+ zh: ca90e1fd026996cb
17
+ /qilin-client-ui-sidechat/what-it-registers:
18
+ en: 2c7f87227236d463
19
+ zh: ac7e2b8894279298
20
+ /qilin-client-ui-sidechat/the-panel:
21
+ en: b488b91003dc65f8
22
+ zh: ddc092565e682419
23
+ /qilin-client-ui-sidechat/model-experience:
24
+ en: 4eb476dcc206f704
25
+ zh: 56eb09607ced8476
26
+ /qilin-client-ui-sidechat/model-experience/kv-cache-effect:
27
+ en: 21a0d6e4e39c5f40
28
+ zh: 870dfff007e94c1b
29
+ /qilin-client-ui-sidechat/known-limitations-and-deferred-work:
30
+ en: 26b497ace8ab3846
31
+ zh: 10f28c06762c5f86
32
+ /qilin-client-ui-sidechat/known-limitations-and-deferred-work/dev-note:
33
+ en: 4b7fde33e0cea000
34
+ zh: 84f5a8341b6b7311
package/README.md CHANGED
@@ -1,3 +1,67 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "The right Sidebar's sidechat page for the qilin web client: the session's side threads, each thread's own transcript, and the composer that drives them, read back over the thread follow streams."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/client-ui-sidechat
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ The right Sidebar's sidechat page: one column listing the current Session's side threads and one pane showing the open thread's own transcript with a composer. It is a page type that claims no address, opened by kind alone. The roster and transcripts are the panel's own reads — the six Session Controller `sidechat*` remotes plus `session.follow` on each thread's subagent address — and every action travels through those remotes. Nothing in `ui-sidebar-right` knows this package.
13
+
14
+ ## Table of Contents
15
+
16
+ - [What it registers](#what-it-registers)
17
+ - [The panel](#the-panel)
18
+ - [Model Experience](#model-experience)
19
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
20
+ - [Dev Note](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="what-it-registers"></a>
25
+ ## What it registers
26
+
27
+ - **The type** — `ctx.sidebarRightTabs.register(...)` with kind `sidechat`, id `@qilin-agent/client-ui-sidechat`, band `builtin`, `single`, and no patterns; `ctx.sidebarRight.openTab('sidechat')` opens it.
28
+ - **The body** — the keyed `sidebar.right.pane.tab` seat under that id, injecting the panel's state source through the `hooks` compartment and the actions through the inject face.
29
+
30
+ Nine source files under `src/client/`: `definition.tsx` (the type), `sidechat-model.ts` (the pure view model: the follow address and the transcript fold), `sidechat-source.ts` (the object-layer state source), `face.ts` (the actions and their Remote binding), `SidechatBody.tsx` and `SidechatBody.module.css` (what is drawn), `locales.ts` (what it says), and `index.ts` (the wiring).
31
+
32
+ <a id="the-panel"></a>
33
+ ## The panel
34
+
35
+ **The roster** reads `session/sidechatThreads` for the Session the pane is open on, oldest first. Each row shows the thread's durable label and its live or running state; a row click opens that thread's transcript.
36
+
37
+ **The transcript** follows the open thread through `session.follow` on its subagent address (`continuable` child of the current Session). The opening snapshot replays the thread's own events — user messages, the inheritance boundary, and assistant replies — and committed frames append as they arrive. The inherited parent prefix never appears: it is reference context on the Host, not part of the thread's own log slice.
38
+
39
+ **The composer** sends its draft as a sidechat prompt (`session/sidechatPrompt`), Enter to send. A running thread offers cancel (`session/sidechatCancel`); a live idle thread offers release (`session/sidechatRelease`); the rail's button forks a new thread (`session/sidechatStart`), optionally naming it from a first question.
40
+
41
+ <a id="model-experience"></a>
42
+ ## Model Experience
43
+
44
+ None, as this package draws Session-side threads in the browser and registers nothing model-facing. The thread's model requests belong to the thread's own Session on the Host.
45
+
46
+ #### KV Cache effect
47
+
48
+ None; the panel issues no model request and adds no tokens to any prompt. Its reads are durable-event folds over follow streams the Session Remote already serves.
49
+
50
+ ## Known Limitations and Deferred Work
51
+
52
+ <a id="known-limitations-and-deferred-work"></a>
53
+
54
+ - **One tab per surface.** The page carries one in-panel thread list where the upstream coding-sidebar SideChat seats one tab per thread: switching threads replaces the open transcript, and two threads cannot be read side by side.
55
+ - **Host-owned thread identity.** Threads are identified by the Host's sidechat descriptor (provider `sidechat`, mode `continuable`), not an upstream title-prefix convention, so labels follow the Host's `Side: …` labeling rather than any client-side prefix rule.
56
+ - **Durable events only.** The follow requests the assistant stream baseline, but the panel draws no stream frames — assistant text appears when its durable `assistant/message` event commits.
57
+ - **Current Session only.** The panel attaches to the Session it is open on and cannot browse a sidechat workspace attachment, because sidechat threads carry none.
58
+
59
+ <a id="dev-note"></a>
60
+ ### Dev Note
61
+
62
+ <details>
63
+ <summary>Working context for maintainers — click to expand</summary>
64
+
65
+ The state source is registration-scoped object layer (`sidechat-source.ts`): the roster, the open transcript, and the in-flight flags outlive the body's unmounts, and the face is its only writer. The panel declares no store — there is no cross-entry viewing state to share.
66
+
67
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,67 @@
1
+ ---
2
+ description: "qilin Web 客户端右侧边栏的侧聊页:当前会话的侧聊线程、打开线程的自有转录,以及驱动它们的输入框,经由线程 follow 流回读。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/client-ui-sidechat
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 右侧边栏的侧聊页:一列列出当前会话的侧聊线程,一格显示打开线程的自有转录与输入框。它是按类型打开、不声明地址的页面。列表与转录是面板自己的读取——会话控制器的六个 `sidechat*` 远程加上每个线程子代理地址上的 `session.follow`——所有操作都经由这些远程。`ui-sidebar-right` 对本包一无所知。
13
+
14
+ ## 目录
15
+
16
+ - [注册了什么](#what-it-registers)
17
+ - [面板](#the-panel)
18
+ - [Model Experience](#model-experience)
19
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
20
+ - [Dev Note](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="what-it-registers"></a>
25
+ ## 注册内容
26
+
27
+ - **类型** —— `ctx.sidebarRightTabs.register(...)`:kind `sidechat`、id `@qilin-agent/client-ui-sidechat`、band `builtin`、`single`、无 patterns;`ctx.sidebarRight.openTab('sidechat')` 打开它。
28
+ - **面板体** —— 该 id 下带键的 `sidebar.right.pane.tab` 座位,经 `hooks` 舱注入面板的状态源,经 inject 面注入操作。
29
+
30
+ `src/client/` 下九个源文件:`definition.tsx`(类型)、`sidechat-model.ts`(纯视图模型:follow 地址与转录折叠)、`sidechat-source.ts`(对象层状态源)、`face.ts`(操作及其远程绑定)、`SidechatBody.tsx` 与 `SidechatBody.module.css`(绘制内容)、`locales.ts`(文案),以及 `index.ts`(装配)。
31
+
32
+ <a id="the-panel"></a>
33
+ ## 面板
34
+
35
+ **线程列表** 读取面板所在会话的 `session/sidechatThreads`,按创建先后排序。每行显示线程的持久标签与活跃/运行状态;点击一行打开该线程的转录。
36
+
37
+ **转录** 通过 `session.follow` 在线程的子代理地址(当前会话的 `continuable` 子级)上跟随打开的线程。开场快照回放线程自有事件——用户消息、继承边界与助手回复——提交帧到达时追加。继承的父前缀不会出现:它是宿主侧的参考上下文,不属于线程自有日志切片。
38
+
39
+ **输入框** 将草稿作为侧聊 prompt 发送(`session/sidechatPrompt`),Enter 发送。运行中的线程提供取消(`session/sidechatCancel`);活跃空闲的线程提供释放(`session/sidechatRelease`);侧栏按钮从当前会话分叉新线程(`session/sidechatStart`),可用第一个问题命名。
40
+
41
+ <a id="model-experience"></a>
42
+ ## 模型体验
43
+
44
+ 无,本包在浏览器中绘制会话侧的线程,不注册任何面向模型的内容。线程自身的模型请求属于宿主上线程自己的会话。
45
+
46
+ #### KV Cache 效应
47
+
48
+ 无;面板不发起模型请求,也不向任何提示词增加 token。它的读取是会话远程已提供的 follow 流之上的持久事件折叠。
49
+
50
+ ## 已知限制与推迟的工作
51
+
52
+ <a id="known-limitations-and-deferred-work"></a>
53
+
54
+ - **每个表面一个页签。** 页面使用页内线程列表,而上游 coding-sidebar 的 SideChat 为每个线程安排一个页签:切换线程会替换当前打开的转录,无法并排阅读两个线程。
55
+ - **线程身份由宿主所有。** 线程以宿主的侧聊描述符识别(provider `sidechat`、mode `continuable`),而非上游的标题前缀约定,标签遵循宿主的 `Side: …` 命名而非任何客户端前缀规则。
56
+ - **只绘持久事件。** follow 请求会请求助手流基线,但面板不绘制流帧——助手文本在其持久 `assistant/message` 事件提交后出现。
57
+ - **仅当前会话。** 面板只附着于打开它的会话,无法浏览侧聊工作区附件,因为侧聊线程不携带任何附件。
58
+
59
+ <a id="dev-note"></a>
60
+ ### 开发备注
61
+
62
+ <details>
63
+ <summary>维护者的工作背景——点击展开</summary>
64
+
65
+ 状态源是注册作用域的对象层(`sidechat-source.ts`):线程列表、打开的转录与进行中标志在面板体卸载后仍然存活,face 是它唯一的写入者。面板不声明 store——没有需要跨条目共享的查看状态。
66
+
67
+ </details>
package/lib/client.js ADDED
@@ -0,0 +1,569 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@qilin-agent/client-ui-sidechat",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ //#region lib/types/client/definition.js
11
+ /** The tab kind this package owns; `ctx.sidebarRight.openTab` names it. */
12
+ const SIDECHAT_KIND = "sidechat";
13
+ /** This implementation's identity in the tab system, and the key its body registers under. */
14
+ const SIDECHAT_ID = "@qilin-agent/client-ui-sidechat";
15
+ /**
16
+ * The sidechat type's registry definition.
17
+ * @param t - namespace-bound translate, read fresh on every label call.
18
+ * @returns the definition to register.
19
+ */
20
+ function sidechatDefinition(t) {
21
+ return {
22
+ id: SIDECHAT_ID,
23
+ kind: SIDECHAT_KIND,
24
+ priority: "builtin",
25
+ label: () => t("type.label"),
26
+ icon: _qilin_agent_client_ui_primitives.IconNewChatOutline16,
27
+ single: true,
28
+ title: () => t("type.label")
29
+ };
30
+ }
31
+ //#endregion
32
+ //#region lib/types/client/sidechat-model.js
33
+ /**
34
+ * The subagent history address of one sidechat thread: every thread is a
35
+ * `continuable` child of its parent Session.
36
+ * @param parentSessionId - the parent Session the panel is open on.
37
+ * @param childSessionId - the sidechat thread.
38
+ * @returns the follow address.
39
+ */
40
+ function sidechatAddress(parentSessionId, childSessionId) {
41
+ return {
42
+ kind: "subagent",
43
+ parentSessionId,
44
+ childSessionId,
45
+ mode: "continuable"
46
+ };
47
+ }
48
+ /** Whether one message data value carries the content-part array. */
49
+ function hasContentParts(value) {
50
+ return Array.isArray(value);
51
+ }
52
+ /** The joined text of one message event's data, or `undefined` when it carries none. */
53
+ function messageText(data) {
54
+ const content = data.content;
55
+ if (!hasContentParts(content)) return void 0;
56
+ const parts = [];
57
+ for (const part of content) if (part?.type === "text" && typeof part.text === "string") parts.push(part.text);
58
+ const text = parts.join("");
59
+ return text.length > 0 ? text : void 0;
60
+ }
61
+ /**
62
+ * Fold one durable history record into a transcript entry, or `undefined`
63
+ * for events the panel does not draw.
64
+ * @param record - one journal record.
65
+ * @returns the entry, when the event is a user, boundary, or assistant message.
66
+ */
67
+ function transcriptEntryOf(record) {
68
+ const event = record.event;
69
+ const text = messageText(event.data);
70
+ if (text === void 0) return void 0;
71
+ if (event.type === "user/message") {
72
+ const sourceKind = event.data.source?.kind;
73
+ return {
74
+ seq: event.seq,
75
+ kind: sourceKind === "sidechat-boundary" ? "boundary" : "user",
76
+ text
77
+ };
78
+ }
79
+ if (event.type === "assistant/message") return {
80
+ seq: event.seq,
81
+ kind: "assistant",
82
+ text
83
+ };
84
+ }
85
+ /**
86
+ * Fold one follow frame into transcript entries: the opening snapshot replays
87
+ * whole records, committed frames arrive one event at a time, and the
88
+ * process-local assistant stream is ignored (durable events own the transcript).
89
+ * @param frame - one frame from the thread's follow stream.
90
+ * @returns the entries the frame contributes, oldest first.
91
+ */
92
+ function transcriptEntriesOfFrame(frame) {
93
+ if (frame.type === "snapshot") return frame.records.map(transcriptEntryOf).filter((entry) => entry !== void 0);
94
+ if (frame.type === "event") {
95
+ const entry = transcriptEntryOf(frame);
96
+ return entry === void 0 ? [] : [entry];
97
+ }
98
+ return [];
99
+ }
100
+ //#endregion
101
+ //#region lib/types/client/face.js
102
+ /**
103
+ * Bind the sidechat remotes to one state source.
104
+ * @param source - the panel's state source.
105
+ * @param remote - the Session Remote slice.
106
+ * @returns the injected face.
107
+ */
108
+ function sidechatFace(source, remote) {
109
+ let activeStop;
110
+ const refresh = async (parentSessionId) => {
111
+ source.setPhase("loading");
112
+ const rows = await remote.sidechatThreads({ sessionId: parentSessionId });
113
+ if (rows.ok) source.setThreads(rows.value.threads);
114
+ else source.setPhase("failed");
115
+ };
116
+ const openTranscript = (parentSessionId, threadId) => {
117
+ activeStop?.();
118
+ const controller = new AbortController();
119
+ activeStop = () => {
120
+ controller.abort();
121
+ };
122
+ source.select(threadId);
123
+ source.resetEntries(threadId, []);
124
+ (async () => {
125
+ try {
126
+ for await (const frame of remote.follow({
127
+ address: sidechatAddress(parentSessionId, threadId),
128
+ assistantStream: true
129
+ }, controller.signal)) {
130
+ if (controller.signal.aborted) return;
131
+ const entries = transcriptEntriesOfFrame(frame);
132
+ if (frame.type === "snapshot") source.resetEntries(threadId, entries);
133
+ else for (const entry of entries) source.appendEntry(threadId, entry);
134
+ }
135
+ } catch {}
136
+ })();
137
+ return () => {
138
+ controller.abort();
139
+ };
140
+ };
141
+ return {
142
+ hooks: { sidechat: source },
143
+ refresh,
144
+ start: async (parentSessionId, question) => {
145
+ source.setBusy(true);
146
+ try {
147
+ const started = await remote.sidechatStart(question === void 0 ? { sessionId: parentSessionId } : {
148
+ sessionId: parentSessionId,
149
+ question
150
+ });
151
+ if (!started.ok) return;
152
+ await refresh(parentSessionId);
153
+ openTranscript(parentSessionId, started.value.threadId);
154
+ } finally {
155
+ source.setBusy(false);
156
+ }
157
+ },
158
+ send: async (parentSessionId, threadId, text) => {
159
+ source.setBusy(true);
160
+ try {
161
+ const prompted = await remote.sidechatPrompt({
162
+ sessionId: threadId,
163
+ text
164
+ });
165
+ if (!prompted.ok) return false;
166
+ await refresh(parentSessionId);
167
+ return prompted.value.accepted;
168
+ } finally {
169
+ source.setBusy(false);
170
+ }
171
+ },
172
+ cancel: async (threadId) => {
173
+ return (await remote.sidechatCancel({ sessionId: threadId })).ok;
174
+ },
175
+ release: async (parentSessionId, threadId) => {
176
+ await remote.sidechatRelease({ sessionId: threadId });
177
+ await refresh(parentSessionId);
178
+ },
179
+ openTranscript
180
+ };
181
+ }
182
+ //#endregion
183
+ //#region lib/types/client/locales.js
184
+ /** Dictionary namespace owned by this plugin. */
185
+ const NS = "sidebarSidechat";
186
+ /** Simplified Chinese dictionary and key-set source of truth. */
187
+ const zh = {
188
+ "type.label": "侧聊",
189
+ "guide.title": "侧聊线程",
190
+ "guide.description": "从当前会话分叉的侧聊线程",
191
+ "threads.title": "侧聊线程",
192
+ "threads.empty": "还没有侧聊线程",
193
+ "threads.failed": "无法读取侧聊线程",
194
+ "threads.new": "新建侧聊",
195
+ "threads.starting": "正在创建…",
196
+ "threads.count.one": "{count} 个线程",
197
+ "threads.count.other": "{count} 个线程",
198
+ "thread.live": "活跃",
199
+ "thread.running": "运行中",
200
+ "thread.cancel": "取消本轮",
201
+ "thread.release": "释放",
202
+ "thread.open": "打开线程",
203
+ "prompt.placeholder": "在侧聊里问点什么…",
204
+ "prompt.send": "发送",
205
+ "prompt.sending": "发送中…",
206
+ "entry.boundary": "侧聊边界:继承的上文仅供参考。",
207
+ "panel.empty": "选择或新建一个侧聊线程。"
208
+ };
209
+ /** English dictionary. */
210
+ const en = {
211
+ "type.label": "Sidechat",
212
+ "guide.title": "Sidechat threads",
213
+ "guide.description": "Threads forked from the current session",
214
+ "threads.title": "Sidechat threads",
215
+ "threads.empty": "No sidechat threads yet",
216
+ "threads.failed": "Could not read sidechat threads",
217
+ "threads.new": "New sidechat",
218
+ "threads.starting": "Creating…",
219
+ "threads.count.one": "{count} thread",
220
+ "threads.count.other": "{count} threads",
221
+ "thread.live": "live",
222
+ "thread.running": "running",
223
+ "thread.cancel": "Cancel turn",
224
+ "thread.release": "Release",
225
+ "thread.open": "Open thread",
226
+ "prompt.placeholder": "Ask something in the sidechat…",
227
+ "prompt.send": "Send",
228
+ "prompt.sending": "Sending…",
229
+ "entry.boundary": "Sidechat boundary: the inherited context is reference only.",
230
+ "panel.empty": "Select or create a sidechat thread."
231
+ };
232
+ //#endregion
233
+ //#region lib/types/client/sidechat-source.js
234
+ /** The initial snapshot: nothing read, nothing open. */
235
+ const EMPTY_STATE = {
236
+ threads: [],
237
+ phase: "idle",
238
+ selectedId: void 0,
239
+ entries: {},
240
+ busy: false
241
+ };
242
+ /**
243
+ * One registration-scoped state source. The snapshot reference is stable
244
+ * between changes, so hook bindings observe by identity.
245
+ */
246
+ var SidechatSource = class {
247
+ state = EMPTY_STATE;
248
+ listeners = /* @__PURE__ */ new Set();
249
+ /**
250
+ * The current snapshot; the same reference until the next change.
251
+ * @returns the current state object.
252
+ */
253
+ getSnapshot() {
254
+ return this.state;
255
+ }
256
+ /**
257
+ * Subscribe to changes.
258
+ * @param listener - called after every published change.
259
+ * @returns the unsubscribe function.
260
+ */
261
+ subscribe(listener) {
262
+ this.listeners.add(listener);
263
+ return () => {
264
+ this.listeners.delete(listener);
265
+ };
266
+ }
267
+ publish(state) {
268
+ this.state = state;
269
+ for (const listener of this.listeners) listener();
270
+ }
271
+ /**
272
+ * Publish the roster and mark the read complete.
273
+ * @param threads - the parent's thread rows in display order.
274
+ */
275
+ setThreads(threads) {
276
+ this.publish({
277
+ ...this.state,
278
+ threads,
279
+ phase: "ready"
280
+ });
281
+ }
282
+ /**
283
+ * Mark a roster read as in flight or failed; `loading` only on the first read.
284
+ * @param phase - the roster phase to publish.
285
+ */
286
+ setPhase(phase) {
287
+ if (phase !== "failed" && this.state.phase !== "idle" && this.state.phase !== "failed") return;
288
+ this.publish({
289
+ ...this.state,
290
+ phase
291
+ });
292
+ }
293
+ /**
294
+ * Select the thread whose transcript the panel shows.
295
+ * @param threadId - the thread to select, or undefined to clear.
296
+ */
297
+ select(threadId) {
298
+ if (this.state.selectedId === threadId) return;
299
+ this.publish({
300
+ ...this.state,
301
+ selectedId: threadId
302
+ });
303
+ }
304
+ /**
305
+ * Replace one thread's transcript with a freshly replayed cut.
306
+ * @param threadId - the thread owning the transcript.
307
+ * @param entries - the replayed entries in log order.
308
+ */
309
+ resetEntries(threadId, entries) {
310
+ this.publish({
311
+ ...this.state,
312
+ entries: {
313
+ ...this.state.entries,
314
+ [threadId]: entries
315
+ }
316
+ });
317
+ }
318
+ /**
319
+ * Append one committed entry to a thread's transcript.
320
+ * @param threadId - the thread owning the transcript.
321
+ * @param entry - the committed entry to append after the current tail.
322
+ */
323
+ appendEntry(threadId, entry) {
324
+ const existing = this.state.entries[threadId] ?? [];
325
+ const last = existing[existing.length - 1];
326
+ if (last !== void 0 && last.seq >= entry.seq) return;
327
+ this.publish({
328
+ ...this.state,
329
+ entries: {
330
+ ...this.state.entries,
331
+ [threadId]: [...existing, entry]
332
+ }
333
+ });
334
+ }
335
+ /**
336
+ * Set the in-flight flag for start and send actions.
337
+ * @param busy - whether a start or send is in flight.
338
+ */
339
+ setBusy(busy) {
340
+ if (this.state.busy === busy) return;
341
+ this.publish({
342
+ ...this.state,
343
+ busy
344
+ });
345
+ }
346
+ };
347
+ /**
348
+ * Create one registration-scoped sidechat source.
349
+ * @returns a fresh source with empty state and no listeners.
350
+ */
351
+ function createSidechatSource() {
352
+ return new SidechatSource();
353
+ }
354
+ //#endregion
355
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
356
+ function r(e) {
357
+ var t, f, n = "";
358
+ if ("string" == typeof e || "number" == typeof e) n += e;
359
+ else if ("object" == typeof e) if (Array.isArray(e)) {
360
+ var o = e.length;
361
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
362
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
363
+ return n;
364
+ }
365
+ function clsx() {
366
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
367
+ return n;
368
+ }
369
+ //#endregion
370
+ //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-sidechat/src/client/SidechatBody.module.css.mjs
371
+ const css = ".vstdAW_panel{gap:var(--qilin-space-3);width:100%;min-height:0;color:var(--qilin-color-text-primary);flex:1;display:flex}.vstdAW_failed{color:var(--qilin-color-text-secondary);justify-content:center;align-items:center}.vstdAW_rail{gap:var(--qilin-space-2);flex-direction:column;flex-shrink:0;width:168px;min-height:0;display:flex;overflow-y:auto}.vstdAW_railEmpty{color:var(--qilin-color-text-secondary);font-size:var(--qilin-font-size-sm);margin:0}.vstdAW_newThread{padding:var(--qilin-space-2) var(--qilin-space-3);border:1px solid var(--qilin-color-border-primary);border-radius:var(--qilin-radius-md);color:inherit;cursor:pointer;background:0 0}.vstdAW_newThread:disabled{cursor:default;opacity:.6}.vstdAW_threadList{gap:var(--qilin-space-1);flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.vstdAW_threadRow{justify-content:space-between;align-items:center;gap:var(--qilin-space-2);width:100%;padding:var(--qilin-space-2);border-radius:var(--qilin-radius-md);color:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;display:flex}.vstdAW_threadRow.vstdAW_selected{border-color:var(--qilin-color-border-accent);background:var(--qilin-color-bg-accent-subtle)}.vstdAW_threadLabel{text-overflow:ellipsis;white-space:nowrap;font-size:var(--qilin-font-size-sm);overflow:hidden}.vstdAW_threadState{color:var(--qilin-color-text-secondary);font-size:var(--qilin-font-size-xs);flex-shrink:0}.vstdAW_running{color:var(--qilin-color-text-accent)}.vstdAW_transcript{gap:var(--qilin-space-2);flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.vstdAW_transcriptEmpty{color:var(--qilin-color-text-secondary);margin:0}.vstdAW_entries{gap:var(--qilin-space-2);flex-direction:column;flex:1;min-height:0;display:flex;overflow-y:auto}.vstdAW_entry{padding:var(--qilin-space-2) var(--qilin-space-3);border-radius:var(--qilin-radius-md);background:var(--qilin-color-bg-subtle);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.vstdAW_user{background:var(--qilin-color-bg-accent-subtle)}.vstdAW_boundary{color:var(--qilin-color-text-secondary);border:1px dashed var(--qilin-color-border-primary);background:0 0}.vstdAW_assistant{background:0 0}.vstdAW_composer{gap:var(--qilin-space-2);flex-direction:column;display:flex}.vstdAW_input{width:100%;min-height:56px;padding:var(--qilin-space-2);border:1px solid var(--qilin-color-border-primary);border-radius:var(--qilin-radius-md);background:var(--qilin-color-bg-primary);color:inherit;resize:vertical}.vstdAW_composerActions{justify-content:flex-end;gap:var(--qilin-space-2);display:flex}.vstdAW_secondary{padding:var(--qilin-space-1) var(--qilin-space-3);border:1px solid var(--qilin-color-border-primary);border-radius:var(--qilin-radius-md);color:inherit;cursor:pointer;background:0 0}.vstdAW_primary{padding:var(--qilin-space-1) var(--qilin-space-3);border-radius:var(--qilin-radius-md);background:var(--qilin-color-bg-accent);color:var(--qilin-color-text-accent-contrast);cursor:pointer;border:1px solid #0000}.vstdAW_primary:disabled{cursor:default;opacity:.6}";
372
+ const tagId = "@qilin-agent/client-ui-sidechat/SidechatBody.module.css";
373
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
374
+ const tag = document.createElement("style");
375
+ tag.dataset.plugin = "@qilin-agent/client-ui-sidechat";
376
+ tag.dataset.pluginCss = tagId;
377
+ tag.textContent = css;
378
+ document.head.appendChild(tag);
379
+ }
380
+ var SidechatBody_module_css_default = {
381
+ "assistant": "vstdAW_assistant",
382
+ "boundary": "vstdAW_boundary",
383
+ "composer": "vstdAW_composer",
384
+ "composerActions": "vstdAW_composerActions",
385
+ "entries": "vstdAW_entries",
386
+ "entry": "vstdAW_entry",
387
+ "failed": "vstdAW_failed",
388
+ "input": "vstdAW_input",
389
+ "newThread": "vstdAW_newThread",
390
+ "panel": "vstdAW_panel",
391
+ "primary": "vstdAW_primary",
392
+ "rail": "vstdAW_rail",
393
+ "railEmpty": "vstdAW_railEmpty",
394
+ "running": "vstdAW_running",
395
+ "secondary": "vstdAW_secondary",
396
+ "selected": "vstdAW_selected",
397
+ "threadLabel": "vstdAW_threadLabel",
398
+ "threadList": "vstdAW_threadList",
399
+ "threadRow": "vstdAW_threadRow",
400
+ "threadState": "vstdAW_threadState",
401
+ "transcript": "vstdAW_transcript",
402
+ "transcriptEmpty": "vstdAW_transcriptEmpty",
403
+ "user": "vstdAW_user"
404
+ };
405
+ //#endregion
406
+ //#region lib/types/client/SidechatBody.js
407
+ /**
408
+ * The sidechat panel body: the thread roster on the left rail, the open
409
+ * thread's transcript, and the composer. Everything arrives through the
410
+ * derived props shares; the body writes nothing directly.
411
+ */
412
+ /** One transcript row's accent class, by entry kind. */
413
+ const ENTRY_CLASS = {
414
+ user: SidechatBody_module_css_default.user,
415
+ boundary: SidechatBody_module_css_default.boundary,
416
+ assistant: SidechatBody_module_css_default.assistant
417
+ };
418
+ /**
419
+ * The panel body.
420
+ * @param props - the runtime share (sessionId, useSidechat), the injected face, and the copy.
421
+ * @returns the roster rail and the transcript pane, or the empty line.
422
+ */
423
+ function SidechatBody({ sessionId, useSidechat, refresh, start, send, cancel, release, openTranscript, t }) {
424
+ const state = useSidechat((snapshot) => snapshot);
425
+ const [draft, setDraft] = (0, react.useState)("");
426
+ const selected = state.threads.find((row) => row.id === state.selectedId);
427
+ const entries = state.selectedId === void 0 ? [] : state.entries[state.selectedId] ?? [];
428
+ (0, react.useEffect)(() => {
429
+ refresh(sessionId);
430
+ }, [refresh, sessionId]);
431
+ const submit = () => {
432
+ const text = draft.trim();
433
+ if (text.length === 0 || state.selectedId === void 0 || state.busy) return;
434
+ setDraft("");
435
+ send(sessionId, state.selectedId, text);
436
+ };
437
+ if (state.phase === "failed") return (0, react_jsx_runtime.jsx)("p", {
438
+ className: clsx(SidechatBody_module_css_default.panel, SidechatBody_module_css_default.failed),
439
+ children: t("threads.failed")
440
+ });
441
+ return (0, react_jsx_runtime.jsxs)("div", {
442
+ className: SidechatBody_module_css_default.panel,
443
+ children: [(0, react_jsx_runtime.jsxs)("section", {
444
+ className: SidechatBody_module_css_default.rail,
445
+ "aria-label": t("threads.title"),
446
+ children: [(0, react_jsx_runtime.jsx)("button", {
447
+ type: "button",
448
+ className: SidechatBody_module_css_default.newThread,
449
+ disabled: state.busy,
450
+ onClick: () => {
451
+ start(sessionId);
452
+ },
453
+ children: state.busy ? t("threads.starting") : t("threads.new")
454
+ }), state.threads.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
455
+ className: SidechatBody_module_css_default.railEmpty,
456
+ children: t("threads.empty")
457
+ }) : (0, react_jsx_runtime.jsx)("ul", {
458
+ className: SidechatBody_module_css_default.threadList,
459
+ children: state.threads.map((row) => (0, react_jsx_runtime.jsx)("li", { children: (0, react_jsx_runtime.jsxs)("button", {
460
+ type: "button",
461
+ className: clsx(SidechatBody_module_css_default.threadRow, row.id === state.selectedId && SidechatBody_module_css_default.selected),
462
+ "aria-label": t("thread.open"),
463
+ onClick: () => openTranscript(sessionId, row.id),
464
+ children: [(0, react_jsx_runtime.jsx)("span", {
465
+ className: SidechatBody_module_css_default.threadLabel,
466
+ children: row.label
467
+ }), (0, react_jsx_runtime.jsx)("span", {
468
+ className: clsx(SidechatBody_module_css_default.threadState, row.running && SidechatBody_module_css_default.running),
469
+ children: row.running ? t("thread.running") : row.live ? t("thread.live") : ""
470
+ })]
471
+ }) }, row.id))
472
+ })]
473
+ }), (0, react_jsx_runtime.jsx)("section", {
474
+ className: SidechatBody_module_css_default.transcript,
475
+ "aria-label": t("threads.title"),
476
+ children: selected === void 0 ? (0, react_jsx_runtime.jsx)("p", {
477
+ className: SidechatBody_module_css_default.transcriptEmpty,
478
+ children: t("panel.empty")
479
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
480
+ className: SidechatBody_module_css_default.entries,
481
+ children: entries.map((entry) => (0, react_jsx_runtime.jsx)("p", {
482
+ className: clsx(SidechatBody_module_css_default.entry, ENTRY_CLASS[entry.kind]),
483
+ children: entry.kind === "boundary" ? `${t("entry.boundary")}\n${entry.text}` : entry.text
484
+ }, entry.seq))
485
+ }), (0, react_jsx_runtime.jsxs)("div", {
486
+ className: SidechatBody_module_css_default.composer,
487
+ children: [(0, react_jsx_runtime.jsx)("textarea", {
488
+ className: SidechatBody_module_css_default.input,
489
+ value: draft,
490
+ placeholder: t("prompt.placeholder"),
491
+ onChange: (event) => {
492
+ setDraft(event.target.value);
493
+ },
494
+ onKeyDown: (event) => {
495
+ if (event.key === "Enter" && !event.shiftKey) {
496
+ event.preventDefault();
497
+ submit();
498
+ }
499
+ }
500
+ }), (0, react_jsx_runtime.jsxs)("div", {
501
+ className: SidechatBody_module_css_default.composerActions,
502
+ children: [selected.running ? (0, react_jsx_runtime.jsx)("button", {
503
+ type: "button",
504
+ className: SidechatBody_module_css_default.secondary,
505
+ onClick: () => {
506
+ cancel(selected.id);
507
+ },
508
+ children: t("thread.cancel")
509
+ }) : (0, react_jsx_runtime.jsx)("button", {
510
+ type: "button",
511
+ className: SidechatBody_module_css_default.secondary,
512
+ onClick: () => {
513
+ release(sessionId, selected.id);
514
+ },
515
+ children: t("thread.release")
516
+ }), (0, react_jsx_runtime.jsx)("button", {
517
+ type: "button",
518
+ className: SidechatBody_module_css_default.primary,
519
+ disabled: state.busy || draft.trim().length === 0,
520
+ onClick: submit,
521
+ children: state.busy ? t("prompt.sending") : t("prompt.send")
522
+ })]
523
+ })]
524
+ })] })
525
+ })]
526
+ });
527
+ }
528
+ //#endregion
529
+ //#region lib/types/client/index.js
530
+ /**
531
+ * Required browser services: the tab registry, the keyed seat, the Session
532
+ * Remote namespace, and copy.
533
+ */
534
+ const inject = [
535
+ "slots",
536
+ "locale",
537
+ "sidebarRightTabs",
538
+ "remote",
539
+ "remote.session"
540
+ ];
541
+ /**
542
+ * Client plugin body: register the type, its dictionaries, and the body.
543
+ * @param ctx - client root context carrying the registry, the slots, and the Remote face.
544
+ */
545
+ function apply(ctx) {
546
+ ctx.effect(() => ctx.locale.register(NS, {
547
+ zh,
548
+ en
549
+ }), "ui-sidechat: dictionaries");
550
+ const t = ctx.locale.bind(NS);
551
+ ctx.effect(() => ctx.sidebarRightTabs.register(sidechatDefinition(t)), "ui-sidechat: sidechat type");
552
+ const source = createSidechatSource();
553
+ const remote = ctx.remote.session;
554
+ const face = sidechatFace(source, remote);
555
+ ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
556
+ name: "sidebar.right.pane.tab",
557
+ key: SIDECHAT_ID,
558
+ locale: NS,
559
+ inject: () => face
560
+ }, SidechatBody)), "ui-sidechat: sidechat tab body");
561
+ }
562
+ //#endregion
563
+ exports.apply = apply;
564
+ exports.inject = inject;
565
+ return module.exports;
566
+ }
567
+ });
568
+
569
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Pure host half; the whole tab type lives in the browser export. */
3
+ /** Host plugin body: the sidechat page contributes nothing to the host tree. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The sidechat panel body: the thread roster on the left rail, the open
3
+ * thread's transcript, and the composer. Everything arrives through the
4
+ * derived props shares; the body writes nothing directly.
5
+ */
6
+ import { type ReactNode } from 'react';
7
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@qilin-agent/client-ui-slots';
8
+ import type { SidechatInjected } from './face.ts';
9
+ import { NS } from './locales.ts';
10
+ /** The body's composed props: the tab it draws, its face, and its copy. */
11
+ export type SidechatBodyProps = PropsRuntime<'sidebar.right.pane.tab'> & InjectFace<SidechatInjected> & PropsLocale<typeof NS>;
12
+ /**
13
+ * The panel body.
14
+ * @param props - the runtime share (sessionId, useSidechat), the injected face, and the copy.
15
+ * @returns the roster rail and the transcript pane, or the empty line.
16
+ */
17
+ export declare function SidechatBody({ sessionId, useSidechat, refresh, start, send, cancel, release, openTranscript, t, }: SidechatBodyProps): ReactNode;
18
+ //# sourceMappingURL=SidechatBody.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Stage one of this package's registration: what the `sidechat` tab type is.
3
+ *
4
+ * The type is a page, not a viewer: it claims no address, so a tab of this
5
+ * kind is opened by kind alone — `ctx.sidebarRight.openTab('sidechat')`.
6
+ */
7
+ import type { TranslateNS } from '@qilin-agent/client-locale/client';
8
+ import type { SidebarRightTabDefinition } from '@qilin-agent/client-ui-sidebar-right/client';
9
+ /** The tab kind this package owns; `ctx.sidebarRight.openTab` names it. */
10
+ export declare const SIDECHAT_KIND = "sidechat";
11
+ /** This implementation's identity in the tab system, and the key its body registers under. */
12
+ export declare const SIDECHAT_ID = "@qilin-agent/client-ui-sidechat";
13
+ /**
14
+ * The sidechat type's registry definition.
15
+ * @param t - namespace-bound translate, read fresh on every label call.
16
+ * @returns the definition to register.
17
+ */
18
+ export declare function sidechatDefinition(t: TranslateNS<'sidebarSidechat'>): SidebarRightTabDefinition;
19
+ //# sourceMappingURL=definition.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The sidechat panel's business face: the six Session Controller sidechat
3
+ * remotes plus the follow stream, bound to one state source. The panel reads
4
+ * everything through the source and acts only through these actions.
5
+ */
6
+ import type { ClientRemote } from '@qilin-agent/api-remotes/client';
7
+ import type { SessionId } from '@qilin-agent/session';
8
+ import type { SidechatSource } from './sidechat-source.ts';
9
+ /**
10
+ * The Session Remote slice this panel reads and drives, exactly as the Host's
11
+ * generated client declares it.
12
+ */
13
+ export type SidechatRemote = Pick<ClientRemote['session'], 'sidechatStart' | 'sidechatPrompt' | 'sidechatCancel' | 'sidechatSnapshot' | 'sidechatRelease' | 'sidechatThreads' | 'follow'>;
14
+ /** The panel's injected face, as the body receives it. */
15
+ export interface SidechatInjected {
16
+ hooks: {
17
+ /** The panel's state source, bound by the renderer as `useSidechat`. */
18
+ sidechat: SidechatSource;
19
+ };
20
+ /**
21
+ * Re-read the parent's thread roster.
22
+ * @param parentSessionId - the Session the panel is open on.
23
+ */
24
+ refresh(parentSessionId: SessionId): Promise<void>;
25
+ /**
26
+ * Fork one new thread off the parent and open it.
27
+ * @param parentSessionId - the Session to fork from.
28
+ * @param question - optional first question, delivered with the boundary.
29
+ */
30
+ start(parentSessionId: SessionId, question?: string): Promise<void>;
31
+ /**
32
+ * Deliver one follow-up message to a thread.
33
+ * @param parentSessionId - the Session the panel is open on.
34
+ * @param threadId - the thread to prompt.
35
+ * @param text - the message text.
36
+ * @returns whether the Host accepted the prompt.
37
+ */
38
+ send(parentSessionId: SessionId, threadId: SessionId, text: string): Promise<boolean>;
39
+ /**
40
+ * Cancel a thread's running turn.
41
+ * @param threadId - the thread whose turn is cancelled.
42
+ * @returns whether the Host accepted the cancel.
43
+ */
44
+ cancel(threadId: SessionId): Promise<boolean>;
45
+ /**
46
+ * Release a thread's live Agent and refresh the roster.
47
+ * @param parentSessionId - the Session the panel is open on.
48
+ * @param threadId - the thread to release.
49
+ */
50
+ release(parentSessionId: SessionId, threadId: SessionId): Promise<void>;
51
+ /**
52
+ * Open one thread's transcript: replay its own events and follow the live
53
+ * log. Opening a second thread closes the first subscription.
54
+ * @param parentSessionId - the Session the panel is open on.
55
+ * @param threadId - the thread to follow.
56
+ * @returns the stop function.
57
+ */
58
+ openTranscript(parentSessionId: SessionId, threadId: SessionId): () => void;
59
+ }
60
+ /**
61
+ * Bind the sidechat remotes to one state source.
62
+ * @param source - the panel's state source.
63
+ * @param remote - the Session Remote slice.
64
+ * @returns the injected face.
65
+ */
66
+ export declare function sidechatFace(source: SidechatSource, remote: SidechatRemote): SidechatInjected;
67
+ //# sourceMappingURL=face.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Browser half: register `sidechat` as a right-Sidebar tab type.
3
+ *
4
+ * The public two-stage path: the type into `ctx.sidebarRightTabs`, the body
5
+ * into the keyed `sidebar.right.pane.tab` seat, both under the type's `id`.
6
+ *
7
+ * The split is this package's layering: what the type is (`definition.tsx`),
8
+ * what it says (`locales.ts`), the pure view model (`sidechat-model.ts`), the
9
+ * object-layer state source (`sidechat-source.ts`), the actions it performs
10
+ * (`face.ts`), what it draws (`SidechatBody.tsx`), and this module, which
11
+ * wires them.
12
+ */
13
+ import type { Context as ClientContext } from '@qilin-agent/kylin';
14
+ export type { SidechatInjected, SidechatRemote } from './face.ts';
15
+ export type { SidechatState, SidechatPhase } from './sidechat-source.ts';
16
+ export type { SidechatTranscriptEntry } from './sidechat-model.ts';
17
+ export type { SidechatKey } from './locales.ts';
18
+ export type { SidechatBodyProps } from './SidechatBody.tsx';
19
+ /**
20
+ * Required browser services: the tab registry, the keyed seat, the Session
21
+ * Remote namespace, and copy.
22
+ */
23
+ export declare const inject: string[];
24
+ /**
25
+ * Client plugin body: register the type, its dictionaries, and the body.
26
+ * @param ctx - client root context carrying the registry, the slots, and the Remote face.
27
+ */
28
+ export declare function apply(ctx: ClientContext): void;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,36 @@
1
+ declare module '@qilin-agent/client-ui-slots' {
2
+ interface LocaleNamespaceMap {
3
+ /** Sidechat panel type name, thread list copy, and transcript copy. */
4
+ sidebarSidechat: SidechatKey;
5
+ }
6
+ }
7
+ /** Dictionary namespace owned by this plugin. */
8
+ export declare const NS = "sidebarSidechat";
9
+ /** Simplified Chinese dictionary and key-set source of truth. */
10
+ export declare const zh: {
11
+ readonly 'type.label': "侧聊";
12
+ readonly 'guide.title': "侧聊线程";
13
+ readonly 'guide.description': "从当前会话分叉的侧聊线程";
14
+ readonly 'threads.title': "侧聊线程";
15
+ readonly 'threads.empty': "还没有侧聊线程";
16
+ readonly 'threads.failed': "无法读取侧聊线程";
17
+ readonly 'threads.new': "新建侧聊";
18
+ readonly 'threads.starting': "正在创建…";
19
+ readonly 'threads.count.one': "{count} 个线程";
20
+ readonly 'threads.count.other': "{count} 个线程";
21
+ readonly 'thread.live': "活跃";
22
+ readonly 'thread.running': "运行中";
23
+ readonly 'thread.cancel': "取消本轮";
24
+ readonly 'thread.release': "释放";
25
+ readonly 'thread.open': "打开线程";
26
+ readonly 'prompt.placeholder': "在侧聊里问点什么…";
27
+ readonly 'prompt.send': "发送";
28
+ readonly 'prompt.sending': "发送中…";
29
+ readonly 'entry.boundary': "侧聊边界:继承的上文仅供参考。";
30
+ readonly 'panel.empty': "选择或新建一个侧聊线程。";
31
+ };
32
+ /** The namespace's key set; en must define exactly these keys. */
33
+ export type SidechatKey = keyof typeof zh;
34
+ /** English dictionary. */
35
+ export declare const en: Record<SidechatKey, string>;
36
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pure sidechat view model: the follow address of one thread, and the fold
3
+ * from durable Session events to the panel's transcript entries.
4
+ */
5
+ import type { SessionId } from '@qilin-agent/session';
6
+ import type { SessionFollowFrame, SessionHistoryRecord } from '@qilin-agent/api-session-controller/types';
7
+ /** One rendered line of a thread's transcript. */
8
+ export interface SidechatTranscriptEntry {
9
+ readonly seq: number;
10
+ readonly kind: 'user' | 'boundary' | 'assistant';
11
+ readonly text: string;
12
+ }
13
+ /**
14
+ * The subagent history address of one sidechat thread: every thread is a
15
+ * `continuable` child of its parent Session.
16
+ * @param parentSessionId - the parent Session the panel is open on.
17
+ * @param childSessionId - the sidechat thread.
18
+ * @returns the follow address.
19
+ */
20
+ export declare function sidechatAddress(parentSessionId: SessionId, childSessionId: SessionId): {
21
+ kind: 'subagent';
22
+ parentSessionId: SessionId;
23
+ childSessionId: SessionId;
24
+ mode: 'continuable';
25
+ };
26
+ /**
27
+ * Fold one durable history record into a transcript entry, or `undefined`
28
+ * for events the panel does not draw.
29
+ * @param record - one journal record.
30
+ * @returns the entry, when the event is a user, boundary, or assistant message.
31
+ */
32
+ export declare function transcriptEntryOf(record: SessionHistoryRecord): SidechatTranscriptEntry | undefined;
33
+ /**
34
+ * Fold one follow frame into transcript entries: the opening snapshot replays
35
+ * whole records, committed frames arrive one event at a time, and the
36
+ * process-local assistant stream is ignored (durable events own the transcript).
37
+ * @param frame - one frame from the thread's follow stream.
38
+ * @returns the entries the frame contributes, oldest first.
39
+ */
40
+ export declare function transcriptEntriesOfFrame(frame: SessionFollowFrame): readonly SidechatTranscriptEntry[];
41
+ //# sourceMappingURL=sidechat-model.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The sidechat panel's object-layer state source: the thread roster, the
3
+ * open thread's transcript, and the in-flight flags — one bare observable
4
+ * the renderer binds as `useSidechat`. React-free by design; the face in
5
+ * `face.ts` is the only writer.
6
+ */
7
+ import type { SessionId } from '@qilin-agent/session';
8
+ import type { SidechatThreadRow } from '@qilin-agent/api-session-controller/types';
9
+ import type { SidechatTranscriptEntry } from './sidechat-model.ts';
10
+ /** How far the roster read has got. */
11
+ export type SidechatPhase = 'idle' | 'loading' | 'ready' | 'failed';
12
+ /** The panel's state snapshot. */
13
+ export interface SidechatState {
14
+ /** The parent's sidechat threads, oldest first. */
15
+ readonly threads: readonly SidechatThreadRow[];
16
+ /** The roster read's phase. */
17
+ readonly phase: SidechatPhase;
18
+ /** The thread whose transcript is open. */
19
+ readonly selectedId: SessionId | undefined;
20
+ /** One transcript per opened thread, oldest first. */
21
+ readonly entries: Readonly<Record<string, readonly SidechatTranscriptEntry[]>>;
22
+ /** Whether a start or send is in flight. */
23
+ readonly busy: boolean;
24
+ }
25
+ /**
26
+ * One registration-scoped state source. The snapshot reference is stable
27
+ * between changes, so hook bindings observe by identity.
28
+ */
29
+ export declare class SidechatSource {
30
+ private state;
31
+ private readonly listeners;
32
+ /**
33
+ * The current snapshot; the same reference until the next change.
34
+ * @returns the current state object.
35
+ */
36
+ getSnapshot(): SidechatState;
37
+ /**
38
+ * Subscribe to changes.
39
+ * @param listener - called after every published change.
40
+ * @returns the unsubscribe function.
41
+ */
42
+ subscribe(listener: () => void): () => void;
43
+ private publish;
44
+ /**
45
+ * Publish the roster and mark the read complete.
46
+ * @param threads - the parent's thread rows in display order.
47
+ */
48
+ setThreads(threads: readonly SidechatThreadRow[]): void;
49
+ /**
50
+ * Mark a roster read as in flight or failed; `loading` only on the first read.
51
+ * @param phase - the roster phase to publish.
52
+ */
53
+ setPhase(phase: SidechatPhase): void;
54
+ /**
55
+ * Select the thread whose transcript the panel shows.
56
+ * @param threadId - the thread to select, or undefined to clear.
57
+ */
58
+ select(threadId: SessionId | undefined): void;
59
+ /**
60
+ * Replace one thread's transcript with a freshly replayed cut.
61
+ * @param threadId - the thread owning the transcript.
62
+ * @param entries - the replayed entries in log order.
63
+ */
64
+ resetEntries(threadId: SessionId, entries: readonly SidechatTranscriptEntry[]): void;
65
+ /**
66
+ * Append one committed entry to a thread's transcript.
67
+ * @param threadId - the thread owning the transcript.
68
+ * @param entry - the committed entry to append after the current tail.
69
+ */
70
+ appendEntry(threadId: SessionId, entry: SidechatTranscriptEntry): void;
71
+ /**
72
+ * Set the in-flight flag for start and send actions.
73
+ * @param busy - whether a start or send is in flight.
74
+ */
75
+ setBusy(busy: boolean): void;
76
+ }
77
+ /**
78
+ * Create one registration-scoped sidechat source.
79
+ * @returns a fresh source with empty state and no listeners.
80
+ */
81
+ export declare function createSidechatSource(): SidechatSource;
82
+ //# sourceMappingURL=sidechat-source.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Pure host half; the whole tab type lives in the browser export. */
2
+ /** Host plugin body: the sidechat page contributes nothing to the host tree. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-ui-sidechat",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Sidechat tab type for the right Sidebar: one panel with this Session's side threads, each forked from the parent log with the inherited prefix as reference context",
4
+ "version": "3.1.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/client/ui-sidechat"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./client": {
25
+ "types": "./lib/types/client/index.d.ts",
26
+ "default": "./lib/client.js"
27
+ },
28
+ "./src/*": "./src/*",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "qilin": {
32
+ "client": {
33
+ "inject": [
34
+ "@qilin-agent/api-remotes",
35
+ "@qilin-agent/client-ui-sidebar-right"
36
+ ],
37
+ "platform": "web"
38
+ }
39
+ },
40
+ "license": "MIT",
41
+ "peerDependencies": {
42
+ "@qilin-agent/kylin": "~4.0.3"
43
+ },
44
+ "devDependencies": {
45
+ "@testing-library/react": "^16.1.0",
46
+ "@types/react": "~18.3.1",
47
+ "@types/react-dom": "~18.3.0",
48
+ "clsx": "^2.0.0",
49
+ "react": "^18.2.0",
50
+ "react-dom": "^18.2.0",
51
+ "@qilin-agent/api-session-controller": "3.1.3",
52
+ "@qilin-agent/client-test-runtime": "3.1.3",
53
+ "@qilin-agent/client-ui-primitives": "3.1.3",
54
+ "@qilin-agent/api-remotes": "3.1.3",
55
+ "@qilin-agent/client-ui-renderer": "3.1.3",
56
+ "@qilin-agent/client-locale": "3.1.3",
57
+ "@qilin-agent/client-ui-session": "3.1.3",
58
+ "@qilin-agent/client-ui-sidebar-right": "3.1.3",
59
+ "@qilin-agent/client-ui-slots": "3.1.3",
60
+ "@qilin-agent/kylin": "~4.0.3",
61
+ "@qilin-agent/remote-mock": "3.1.3",
62
+ "@qilin-agent/typert-protocol": "3.1.3",
63
+ "@qilin-agent/session": "3.1.3",
64
+ "@qilin-agent/subagent": "3.1.3"
65
+ },
66
+ "files": [
67
+ "lib/index.js",
68
+ "lib/client.js",
69
+ "lib/types/**/*.d.ts"
70
+ ],
71
+ "scripts": {
72
+ "bundle": "tsdown",
73
+ "watch": "tsdown --watch"
74
+ }
6
75
  }