@deepseek-ai/dsh-client-ui-conversation 0.1.7-rc.2 → 0.2.0-rc.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/README.i18n.yaml CHANGED
@@ -6,8 +6,8 @@
6
6
  en: 0ca51f37788dcbdb
7
7
  zh: 14d73c6bc8d4e17f
8
8
  /deepseek-ai-dsh-client-ui-conversation:
9
- en: 71d52109076d859d
10
- zh: 8e63171fbd9bde48
9
+ en: 77ecbd074ef3a3ce
10
+ zh: 2c0e2b720e3b201e
11
11
  /deepseek-ai-dsh-client-ui-conversation/summary:
12
12
  en: bac0bf0d7330f3b1
13
13
  zh: 84a895f4b26d8490
@@ -18,8 +18,8 @@
18
18
  en: 13ae68865fa315d7
19
19
  zh: c152f020abf20f35
20
20
  /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props:
21
- en: ea2f1e81b1f9a38a
22
- zh: a2d4745b3e93e5a9
21
+ en: 7f1d7a29d2238f3a
22
+ zh: f7a66d2f34a1cd42
23
23
  /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries:
24
24
  en: bb1244310f5c0382
25
25
  zh: 824b84077432b559
package/README.md CHANGED
@@ -7,6 +7,8 @@ kind: "package-reference"
7
7
 
8
8
  English | [中文](README.zh.md)
9
9
 
10
+ Desktop product events use the optional [product analytics service](../product-analytics/README.md); ordinary Web interactions are excluded.
11
+
10
12
  ## Summary
11
13
 
12
14
  `ui-conversation` owns target-neutral Conversation assembly and the shared browser shell. It consumes Session Controller `SessionEventLikeEntry` feeds, exposes React-free registries and per-Session bindings through `ctx.uiConversation`, and contributes the `useConversation`, `useInput`, and `inputActions` standard props through `ctx.uiSession`. It also owns the per-session durable image URL cache: `ctx.uiConversation.imageUrl(sessionId, attachment)` resolves one session-authorized browser URL per attachment and revokes it with the Session binding, so every Conversation target shares one `session.attachment` read. Concrete targets such as Chat are separate packages that register their own Definitions, snapshot builders, Views, and renderers.
@@ -74,6 +76,8 @@ The resident composer survives no-Session and Session transitions. Whitespace hi
74
76
 
75
77
  Plain Enter uses the configured delivery mode, exactly Ctrl+Enter or Cmd+Enter uses its complement, and Shift+Enter inserts a line break. Enter with Alt, AltGraph, both Ctrl and Cmd, or Shift plus Ctrl/Cmd leaves the draft and command menu unchanged and keeps the DOM event available to application shortcuts. The Conversation plugin registers the fixed send, newline, complementary-delivery, command-menu, and reference-menu rows and reserves their bindings until it unloads.
76
78
 
79
+ Each composer attempt carries its original occurrence time, gesture, delivery intent, and Session model/run-state snapshot through asynchronous command arbitration. The ordinary-message notification fires once before reference serialization, including attachment-only submissions; handled or claimed commands do not notify. Snapshot and notification failures cannot block sending.
80
+
77
81
  Default sends commit optimistically: Enter clears the draft, occurrence table, and undo history in the same transaction, keeps the composer in `plain`, and runs the send as a detached attempt, so typing and further sends continue during the flight. `sendSession` registers a Session submission echo (`session.beginSubmission`) with the delivery mode before serializing, preserving selected image and file order in `pendingSubmissions`; Session derives the placement from that mode and its current running state, so idle sends use the transcript, busy Queue sends use QueueDock, and busy Steer sends use the pending-steering surface. It then yields one paint, encodes images through the browser's native `FileReader` data-URL path, and cites staged file receipts. Command submissions use the same receipts for generic files, so sending `/goal` or `/plan` never reads those browser files again. The prompt reuses the submission `requestId`; Session retires the echo once at its display handoff, correlated by that `rpcId`. Concurrent failures are restored together in submission order until the user edits the restored content; command submissions keep the frozen `submitting` phase. Detached attempts retain their attachment ids through admission and Session scope disposal. An observed retirement immediately exposes each image preview through the durable cache, replaces it with the canonical URL after fetching the admitted attachment, revokes each URL after its use ends, and releases file cards. Selected generic files enter one FIFO background-upload queue; `maxConcurrentFileUploads` defaults to two active Worker transports, the Conversation service retains queued and active operations plus byte progress across Session navigation, and removing a draft skips its queued transfer or aborts its active transport. Where the browser shell exposes `__DSH_HOST_PATHS__` (the Desktop application), dropped or pasted folders and dropped, picked, or pasted non-image files with a real path become `@path` chips; images keep uploading. Drag-and-drop and paste identify directories through the browser entry API; when that API is absent or returns no entry, pasted items retain ordinary file handling. The file picker cannot select directories. References require the `ui-reference` plugin and the original paths to remain readable by the model's file tools. Paths inside the workspace are relative; others remain absolute. The whole batch is validated before insertion, retains source order and selected text, and uses whitespace-separated, closed-quote mentions. A browser without the bridge refuses dropped or pasted folders; a Desktop folder without a reported path is rejected separately. Continuable subagents disable attachment intake and skip local echoes because their transport does not preserve the browser request id.
78
82
 
79
83
  Queued submission echoes show “Sending…” beside disabled edit, remove, and steer buttons; a collapsed dock keeps the sending status in its header. A matching Host queue row replaces the echo and enables each action according to its normal text-content and running-state requirements. Prompt acknowledgement alone does not enable queue actions. A failed submission removes its echo and displays an error; the composer restores the failed draft when it is empty or still contains the previous automatic restoration, preserving subsequently typed text.
package/README.zh.md CHANGED
@@ -7,6 +7,8 @@ kind: "package-reference"
7
7
 
8
8
  [English](README.md) | 中文
9
9
 
10
+ 桌面端产品事件使用可选的[产品埋点服务](../product-analytics/README.zh.md),不包含普通 Web 交互。
11
+
10
12
  ## 概述
11
13
 
12
14
  `ui-conversation` 拥有与 target 无关的 Conversation 组装和共享浏览器 shell。它消费 Session Controller 的 `SessionEventLikeEntry` feed,通过 `ctx.uiConversation` 暴露不依赖 React 的注册表与逐 Session binding,并通过 `ctx.uiSession` 提供 `useConversation`、`useInput` 和 `inputActions` 标准 props。它还拥有按会话的持久化图片 URL 缓存:`ctx.uiConversation.imageUrl(sessionId, attachment)` 为每个附件解析一个经会话授权的浏览器 URL,并随 Session binding 释放而撤销,因此所有 Conversation target 共享一次 `session.attachment` 读取。Chat 等具体 target 位于独立包,由各自包注册 Definition、快照 builder、View 和 renderer。
@@ -74,6 +76,8 @@ Session 首次绑定或缓存的 Session 成为 current 时,shell 会在渲染
74
76
 
75
77
  普通 Enter 使用已配置的投递模式,严格的 Ctrl+Enter 或 Cmd+Enter 使用互补模式,Shift+Enter 插入换行。带 Alt、AltGraph、同时带 Ctrl 与 Cmd,或 Shift 与 Ctrl/Cmd 的 Enter 保持草稿和指令菜单不变,并将 DOM 事件留给应用快捷键。Conversation 插件注册发送、换行、互补投递、命令菜单和引用菜单的固定条目,并在卸载前保留这些键位。
76
78
 
79
+ 每次输入框提交都携带原始发生时间、操作来源、投递意图以及会话模型和运行状态快照,贯穿异步命令裁决。普通消息通知在引用序列化前发出一次,纯附件提交也遵循这一规则;已处理或认领的命令不发出通知。快照读取与通知消费异常不能阻断发送。
80
+
77
81
  默认发送采用乐观提交:Enter 在同一事务里清空草稿、occurrence 表和撤销历史,composer 保持 `plain`,发送作为 detached attempt 运行,发送期间可以继续输入和提交。`sendSession` 在序列化之前用投递模式注册 Session 提交回显(`session.beginSubmission`),并在 `pendingSubmissions` 中保留图片与文件的选择顺序;Session 根据该模式与当前运行状态推导位置,因此空闲发送进入 transcript(文本记录),繁忙时 Queue 进入 QueueDock,繁忙时 Steer 进入 pending-steering 区域。随后让出一帧,图片经浏览器原生 `FileReader` data-URL 路径编码,文件则引用已暂存凭证。命令提交也用同一凭证表示通用文件,因此发送 `/goal` 或 `/plan` 时不会再次读取这些浏览器文件。提示词复用提交 `requestId`;Session 按同一 `rpcId` 关联展示接管,并仅退休回显一次。多个并发发送失败时,在用户编辑还原内容之前按提交顺序合并还原;命令提交保持冻结的 `submitting` 阶段。Detached attempt 持有附件 id,直到 admission 完成或 Session scope 销毁。回显以 observed 退休时,durable 图片缓存立即公开每个预览 URL,读取 admitted 附件后用规范化 URL 替换预览,并在各 URL 停止使用后撤销,同时释放文件卡。选中的通用文件进入同一个先进先出的后台上传队列;`maxConcurrentFileUploads` 默认允许两个 Worker transport 同时运行,Conversation 服务在切换 Session 时继续持有排队和运行中的传输操作及字节进度,移除草稿会跳过排队中的传输或中止正在运行的传输。浏览器 shell 暴露 `__DSH_HOST_PATHS__` 时(桌面应用),拖入或粘贴的文件夹以及拖入、选择或粘贴的带真实路径的非图片文件会成为 `@路径` chip;图片仍然上传。拖放和粘贴通过浏览器 entry API 识别目录;该 API 不可用或没有返回 entry 时,粘贴项沿用普通文件处理。文件选择器不能选择目录。引用需要启用 `ui-reference` 插件,原路径也必须仍可由模型的文件工具读取。工作区内的路径使用相对形式,其他路径保留绝对形式。整批文件先校验再插入,保留来源顺序和已选中的文字,引用之间有空白分隔,含空格的路径使用闭合引号。没有该桥的浏览器会拒绝拖入或粘贴的文件夹,桌面端无法获取文件夹路径时单独报错。continuable 子代理禁用附件入口,也不创建本地回显,因为其 transport 不保留浏览器 request id。
78
82
 
79
83
  排队提交的本地回显在禁用的编辑、删除、插话按钮旁显示“发送中…”;折叠后的队列在标题栏保留发送状态。匹配的 Host 队列行替换回显后,各操作按原有的纯文本内容和运行状态要求启用。仅收到提示词确认不会启用队列操作。提交失败会移除回显并显示错误;输入框为空或仍保留上一次自动恢复的内容时,composer 恢复失败草稿,保留用户随后输入的文字。
package/lib/client.js CHANGED
@@ -3831,7 +3831,7 @@ window.__ModuleLoader__.load({
3831
3831
  switch (ev.type) {
3832
3832
  case "draft-changed": return this.onDraftChanged(ev.draft);
3833
3833
  case "claim": return this.onClaim(ev.claim);
3834
- case "enter": return this.onEnter(ev.mode, ev.draft);
3834
+ case "enter": return this.onEnter(ev.mode, ev.draft, ev.submission);
3835
3835
  case "adjudicated": return this.onAdjudicated(ev.attempt, ev.outcome);
3836
3836
  case "adjudication-failed": return this.onAdjudicationFailed(ev.attempt, ev.message);
3837
3837
  case "submit-settled": return this.onSubmitSettled(ev);
@@ -3857,7 +3857,7 @@ window.__ModuleLoader__.load({
3857
3857
  return [];
3858
3858
  }
3859
3859
  /** Mint an attempt and controller without assigning its lifecycle owner. */
3860
- mintAttempt(mode, draft) {
3860
+ mintAttempt(mode, draft, submission) {
3861
3861
  const controller = new AbortController();
3862
3862
  this.seq += 1;
3863
3863
  return {
@@ -3865,20 +3865,21 @@ window.__ModuleLoader__.load({
3865
3865
  seq: this.seq,
3866
3866
  signal: controller.signal,
3867
3867
  draftSnapshot: draft,
3868
- mode
3868
+ mode,
3869
+ ...submission === void 0 ? {} : { submission }
3869
3870
  },
3870
3871
  controller
3871
3872
  };
3872
3873
  }
3873
3874
  /** Mint the frozen command/adjudication attempt. */
3874
- beginAttempt(mode, draft) {
3875
- const flight = this.mintAttempt(mode, draft);
3875
+ beginAttempt(mode, draft, submission) {
3876
+ const flight = this.mintAttempt(mode, draft, submission);
3876
3877
  this.inflight = flight;
3877
3878
  return flight.attempt;
3878
3879
  }
3879
3880
  /** Mint an ordinary send that leaves the phase plain. */
3880
- beginDetached(mode, draft) {
3881
- const flight = this.mintAttempt(mode, draft);
3881
+ beginDetached(mode, draft, submission) {
3882
+ const flight = this.mintAttempt(mode, draft, submission);
3882
3883
  this.detached.set(flight.attempt.seq, flight.controller);
3883
3884
  this.claim = void 0;
3884
3885
  this.phase = "plain";
@@ -3896,10 +3897,10 @@ window.__ModuleLoader__.load({
3896
3897
  retainSuffixOf: attempt.draftSnapshot
3897
3898
  }];
3898
3899
  }
3899
- onEnter(mode, draft) {
3900
+ onEnter(mode, draft, submission) {
3900
3901
  if (this.phase === "adjudicating" || this.phase === "submitting") return [];
3901
3902
  if (this.phase === "claimed" && this.claim !== void 0) {
3902
- const attempt = this.beginAttempt(mode, draft);
3903
+ const attempt = this.beginAttempt(mode, draft, submission);
3903
3904
  this.phase = "submitting";
3904
3905
  return [{
3905
3906
  type: "begin-submit",
@@ -3911,7 +3912,7 @@ window.__ModuleLoader__.load({
3911
3912
  const trimmed = draft.trim();
3912
3913
  if (trimmed === "") return [];
3913
3914
  if (trimmed.startsWith("/")) {
3914
- const attempt = this.beginAttempt(mode, draft);
3915
+ const attempt = this.beginAttempt(mode, draft, submission);
3915
3916
  this.phase = "adjudicating";
3916
3917
  return [{
3917
3918
  type: "adjudicate",
@@ -3919,7 +3920,7 @@ window.__ModuleLoader__.load({
3919
3920
  draft
3920
3921
  }];
3921
3922
  }
3922
- return this.detachedEffects(this.beginDetached(mode, draft));
3923
+ return this.detachedEffects(this.beginDetached(mode, draft, submission));
3923
3924
  }
3924
3925
  onAdjudicated(attempt, outcome) {
3925
3926
  const flight = this.inflight;
@@ -13622,7 +13623,19 @@ window.__ModuleLoader__.load({
13622
13623
  * (adjudicating/submitting) force-closes the transient layers: the popup
13623
13624
  * dismisses and the menu tracks frozen.
13624
13625
  */
13625
- submit(mode = "queue") {
13626
+ submit(mode = "queue", source) {
13627
+ if (this.disposed) return;
13628
+ const timestamp = Date.now();
13629
+ let state;
13630
+ if (this.snapshot.phase === "plain" && (this.snapshot.draft.trim() !== "" || this.attachmentIds.length > 0)) try {
13631
+ state = this.deps.submissionState?.();
13632
+ } catch (_error) {}
13633
+ const submission = Object.freeze({
13634
+ timestamp,
13635
+ mode,
13636
+ ...source === void 0 ? {} : { source },
13637
+ ...state === void 0 ? {} : { state }
13638
+ });
13626
13639
  if (this.snapshot.draft.trim() === "" && this.attachmentIds.length > 0) {
13627
13640
  if (this.snapshot.phase === "plain") {
13628
13641
  const attachmentIds = [...this.attachmentIds];
@@ -13634,6 +13647,7 @@ window.__ModuleLoader__.load({
13634
13647
  attachmentIds
13635
13648
  });
13636
13649
  this.commitSend(attachmentIds);
13650
+ this.notifySubmission(submission);
13637
13651
  this.deps.defaultSink("", attachmentIds, mode, controller.signal).then((outcome) => {
13638
13652
  if (this.disposed || !this.attachmentFlights.delete(flight)) return;
13639
13653
  if (outcome.kind === "success") return;
@@ -13655,7 +13669,8 @@ window.__ModuleLoader__.load({
13655
13669
  this.dispatchRun({
13656
13670
  type: "enter",
13657
13671
  mode,
13658
- draft: this.projection.clipboardText
13672
+ draft: this.projection.clipboardText,
13673
+ submission
13659
13674
  });
13660
13675
  const phase = this.snapshot.phase;
13661
13676
  if (phase === "adjudicating" || phase === "submitting") {
@@ -13881,7 +13896,8 @@ window.__ModuleLoader__.load({
13881
13896
  /** Dispatch + execute, refreshing the claim decoration when the styled token flips. */
13882
13897
  dispatchRun(ev) {
13883
13898
  const beforeToken = this.activeClaimToken();
13884
- this.run(this.core.dispatch(ev));
13899
+ const effects = this.core.dispatch(ev);
13900
+ this.run(effects);
13885
13901
  if (this.activeClaimToken() !== beforeToken) this.draftEditor.refreshClaimDecoration();
13886
13902
  }
13887
13903
  run(effects) {
@@ -13931,6 +13947,7 @@ window.__ModuleLoader__.load({
13931
13947
  * its editor snapshot. Chip-free drafts skip the async detour.
13932
13948
  */
13933
13949
  sinkSerialized(attempt, draft, mode) {
13950
+ this.notifySubmission(attempt.submission);
13934
13951
  const attachmentIds = [...this.attachmentIds];
13935
13952
  this.attachmentIds = [];
13936
13953
  const occurrences = this.projection.occurrences;
@@ -13972,6 +13989,12 @@ window.__ModuleLoader__.load({
13972
13989
  this.settleDetachedFailure(attempt, message);
13973
13990
  });
13974
13991
  }
13992
+ notifySubmission(submission) {
13993
+ if (submission === void 0) return;
13994
+ try {
13995
+ this.deps.messageSubmitted?.(submission);
13996
+ } catch (_error) {}
13997
+ }
13975
13998
  /** Settle one detached default send independently of other sends. */
13976
13999
  settleSink(attempt, pending) {
13977
14000
  pending.then((outcome) => {
@@ -14135,6 +14158,27 @@ window.__ModuleLoader__.load({
14135
14158
  }
14136
14159
  };
14137
14160
  //#endregion
14161
+ //#region lib/types/client/input/submission-analytics.js
14162
+ /**
14163
+ * Report the original message occurrence without reading newer Session facts.
14164
+ * @param ctx - client context.
14165
+ * @param submission - original occurrence and Session snapshot.
14166
+ */
14167
+ function reportMessageSubmission(ctx, submission) {
14168
+ const { state, timestamp, mode } = submission;
14169
+ if (state === void 0) return;
14170
+ const model = state.model;
14171
+ ctx.get("productAnalytics")?.track("send_button_click", {
14172
+ ...state.sessionId === void 0 ? {} : { session_id: state.sessionId },
14173
+ ...model === void 0 ? {} : {
14174
+ model_name: `${model.provider}/${model.name}`,
14175
+ ...model.effort === void 0 ? {} : { thinking_effort: model.effort }
14176
+ },
14177
+ run_mode: state.runMode,
14178
+ msg_type: state.running ? mode : "default"
14179
+ }, timestamp);
14180
+ }
14181
+ //#endregion
14138
14182
  //#region lib/types/client/input/hub.js
14139
14183
  /** Session-addressed input facade registry (SessionInputResolver face + composer-layer extras). */
14140
14184
  var InputHub = class {
@@ -14175,6 +14219,26 @@ window.__ModuleLoader__.load({
14175
14219
  const { session, ctx: actx } = binding;
14176
14220
  const shell = new SessionInputShell({
14177
14221
  actx,
14222
+ submissionState: () => {
14223
+ const state = session.getSnapshot();
14224
+ const model = session.projections.faceOf("modelSelection").getSnapshot();
14225
+ const plan = session.projections.faceOf("plan").getSnapshot();
14226
+ const goal = session.projections.faceOf("goal").getSnapshot();
14227
+ const selection = model?.next ?? model?.lastUsed;
14228
+ return Object.freeze({
14229
+ ...state.blank ? {} : { sessionId: state.sessionId },
14230
+ ...selection == null ? {} : { model: Object.freeze({
14231
+ provider: selection.provider,
14232
+ name: selection.model,
14233
+ ...selection.reasoningEffort === void 0 ? {} : { effort: selection.reasoningEffort }
14234
+ }) },
14235
+ runMode: plan?.active ? "plan" : goal?.goal.phase === "active" ? "goal" : "default",
14236
+ running: state.running
14237
+ });
14238
+ },
14239
+ messageSubmitted: (submission) => {
14240
+ reportMessageSubmission(this.rootCtx, submission);
14241
+ },
14178
14242
  inputTriggers: () => this.controller(actx),
14179
14243
  popup: () => this.popup(actx),
14180
14244
  inbox: session.projections.faceOf("inbox"),
@@ -14549,10 +14613,10 @@ window.__ModuleLoader__.load({
14549
14613
  "tool.title.searchSessions": "搜索会话",
14550
14614
  "tool.title.traceSession": "追踪会话",
14551
14615
  "tool.title.listModels": "查看可用模型",
14552
- "tool.title.subagent": "创建代理",
14553
- "tool.title.listAgents": "查看代理",
14616
+ "tool.title.subagent": "创建子智能体",
14617
+ "tool.title.listAgents": "查看子智能体",
14554
14618
  "tool.title.sendMessage": "发送消息",
14555
- "tool.title.interruptAgent": "中断代理",
14619
+ "tool.title.interruptAgent": "中断智能体",
14556
14620
  "tool.title.listJobs": "查看后台任务",
14557
14621
  "tool.title.readJob": "读取任务输出",
14558
14622
  "tool.title.killJob": "取消后台任务",
@@ -14625,14 +14689,14 @@ window.__ModuleLoader__.load({
14625
14689
  "detail.field.turn": "轮次",
14626
14690
  "detail.field.step": "步骤",
14627
14691
  "detail.field.callId": "调用 ID",
14628
- "detail.field.agents": "启动代理数",
14692
+ "detail.field.agents": "启动智能体数",
14629
14693
  "detail.field.result": "结果",
14630
14694
  "detail.field.parent": "父级",
14631
14695
  "detail.field.depth": "层级",
14632
14696
  "detail.field.exitCode": "退出码",
14633
14697
  "detail.field.signal": "信号",
14634
14698
  "detail.field.previousStatus": "中断前状态",
14635
- "detail.field.agent": "代理 ID",
14699
+ "detail.field.agent": "智能体 ID",
14636
14700
  "detail.field.job": "任务 ID",
14637
14701
  "detail.field.task": "任务内容",
14638
14702
  "detail.field.processGroup": "进程组",
@@ -14640,7 +14704,7 @@ window.__ModuleLoader__.load({
14640
14704
  "detail.field.bestMatch": "最相关事件",
14641
14705
  "detail.field.target": "目标事件",
14642
14706
  "detail.field.surface": "记录状态",
14643
- "detail.agents.count": "{count} 个代理",
14707
+ "detail.agents.count": "{count} 个智能体",
14644
14708
  "detail.jobs.count": "{count} 个后台任务",
14645
14709
  "detail.terminals.count": "{count} 个终端",
14646
14710
  "detail.tasks.count": "{count} 个团队任务",
@@ -14659,15 +14723,15 @@ window.__ModuleLoader__.load({
14659
14723
  "detail.wait.title": "子智能体状态",
14660
14724
  "detail.wait.timeout": "等待超时",
14661
14725
  "detail.wait.changed": "检测到变化",
14662
- "detail.agent.reply": "代理回复",
14726
+ "detail.agent.reply": "智能体回复",
14663
14727
  "detail.models.title": "可用模型",
14664
14728
  "detail.output.lines": "第 {begin}–{end} 行,共 {total} 行",
14665
14729
  "detail.output.truncated": "输出已截断",
14666
14730
  "detail.providers.count": "{count} 个检查提供方",
14667
14731
  "detail.plugins.count": "{count} 个动态插件",
14668
14732
  "detail.workflow.script": "工作流脚本",
14669
- "detail.ralph.reportedComplete": "代理报告完成",
14670
- "detail.ralph.reportedBlocker": "代理报告受阻",
14733
+ "detail.ralph.reportedComplete": "智能体报告完成",
14734
+ "detail.ralph.reportedBlocker": "智能体报告受阻",
14671
14735
  "detail.ralph.limit": "已达到轮次上限",
14672
14736
  "detail.report.nextSteps": "待完成事项",
14673
14737
  "detail.trace.replacedBy": "被替换为",
@@ -16759,7 +16823,7 @@ window.__ModuleLoader__.load({
16759
16823
  g.showToast(g.t("file.stillUploading"));
16760
16824
  return;
16761
16825
  }
16762
- keyboard.submit(resolveSubmitMode(g.busyEnter, g.running, accelerated ? "accelerated" : "enter", g.steeringAvailable));
16826
+ keyboard.submit(resolveSubmitMode(g.busyEnter, g.running, accelerated ? "accelerated" : "enter", g.steeringAvailable), "enter");
16763
16827
  },
16764
16828
  intakeFiles: (files, directories) => {
16765
16829
  gate.current.intakeFiles(files, directories);
@@ -17335,7 +17399,7 @@ window.__ModuleLoader__.load({
17335
17399
  }
17336
17400
  if (keyboard === void 0) return;
17337
17401
  /* v8 ignore next -- defensive: the primary button is disabled for empty, disabled, and pending-upload states. */
17338
- if (!empty && !disabled && !machineBusy && !uploadsPending) keyboard.submit(primarySubmitMode);
17402
+ if (!empty && !disabled && !machineBusy && !uploadsPending) keyboard.submit(primarySubmitMode, "click");
17339
17403
  };
17340
17404
  const claimActive = (input?.phase === "claimed" || input?.phase === "submitting") && input.claim !== void 0 && draft.startsWith(input.claim.token);
17341
17405
  const rawHint = claimActive && input.claim.hint !== void 0 && draft.slice(input.claim.token.length).trim() === "" ? input.claim.hint : null;
@@ -1,4 +1,3 @@
1
- /** Registers the target-neutral Conversation assembly, shell, input, and docks. */
2
1
  import type { Context } from '@deepseek-ai/cordis';
3
2
  import z from '@deepseek-ai/schemastery';
4
3
  import { type ConversationKey } from './locales.ts';
@@ -5,4 +5,22 @@ export type { BusyEnterBehavior } from '../../submission-settings.ts';
5
5
  export type InputSubmitMode = BusyEnterBehavior;
6
6
  /** Keyboard gesture whose delivery mode the submission policy resolves. */
7
7
  export type ComposerSubmitGesture = 'enter' | 'accelerated';
8
+ /** Session facts captured when a message submission starts, before asynchronous command arbitration. */
9
+ export interface MessageSubmissionState {
10
+ readonly sessionId?: import('@deepseek-ai/dsh-session/types').SessionId;
11
+ readonly model?: {
12
+ readonly provider: string;
13
+ readonly name: string;
14
+ readonly effort?: string;
15
+ };
16
+ readonly runMode: 'default' | 'plan' | 'goal';
17
+ readonly running: boolean;
18
+ }
19
+ /** Immutable occurrence and delivery intent carried by one composer attempt. */
20
+ export interface MessageSubmission {
21
+ readonly timestamp: number;
22
+ readonly source?: 'click' | 'enter';
23
+ readonly mode: InputSubmitMode;
24
+ readonly state?: MessageSubmissionState;
25
+ }
8
26
  //# sourceMappingURL=composer-submission.d.ts.map
@@ -35,7 +35,7 @@ export interface ComposerKeyboard {
35
35
  /** The shell-owned Lexical editor the composer binds its contenteditable to. */
36
36
  readonly editor: LexicalEditor;
37
37
  /** Submit with an explicit delivery mode resolved by the submission policy (Enter gestures and the primary Send button). */
38
- submit(mode: InputSubmitMode): void;
38
+ submit(mode: InputSubmitMode, source?: 'click' | 'enter'): void;
39
39
  /**
40
40
  * Steer every still-pending queued message into the running turn (the
41
41
  * empty-draft accelerated-Enter gesture; the queue dock's per-row steer
@@ -11,7 +11,7 @@ import type { InboxState } from '@deepseek-ai/dsh-agent/types';
11
11
  import type { ObservableSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-store';
12
12
  import type { Branded } from '@deepseek-ai/dsh-brand';
13
13
  import type { ArbitrateKey, ArbitrateOutcome, Occurrence, ReferenceInsert, TokenSpan } from './draft-editor.ts';
14
- import type { InputSubmitMode } from './composer-submission.ts';
14
+ import type { InputSubmitMode, MessageSubmission } from './composer-submission.ts';
15
15
  /** Attachment payload passed to a claimed command submission. */
16
16
  export type SubmitAttachment = {
17
17
  readonly type: 'image';
@@ -173,7 +173,7 @@ export interface SessionInput extends InputTarget {
173
173
  * THE complexity sink: enter adjudication, submit transaction, and the default sink live inside.
174
174
  * @param mode - delivery intent retained through asynchronous adjudication and serialization.
175
175
  */
176
- submit(mode?: InputSubmitMode): void;
176
+ submit(mode?: InputSubmitMode, source?: 'click' | 'enter'): void;
177
177
  /**
178
178
  * Surface a notice outside the machine's own effect stream: detached
179
179
  * command results and business notifications render through here.
@@ -268,6 +268,8 @@ export interface SubmitAttempt {
268
268
  readonly draftSnapshot: string;
269
269
  /** Default-message delivery intent retained while slash adjudication is pending. */
270
270
  readonly mode: InputSubmitMode;
271
+ /** Original message occurrence, retained across command arbitration. */
272
+ readonly submission?: MessageSubmission;
271
273
  }
272
274
  /**
273
275
  * Submit-machine input events (the machine's single write path). Text
@@ -290,6 +292,7 @@ export type InputEvent =
290
292
  readonly type: 'enter';
291
293
  readonly mode: InputSubmitMode;
292
294
  readonly draft: string;
295
+ readonly submission?: MessageSubmission;
293
296
  } | {
294
297
  readonly type: 'adjudicated';
295
298
  readonly attempt: SubmitAttempt;
@@ -14,7 +14,7 @@ import { type ObservableSnapshot, type SnapshotStore } from '@deepseek-ai/dsh-cl
14
14
  import type { LexicalEditor } from 'lexical';
15
15
  import type { CommandClaim, ConsumeTokenRequest, DraftAttachmentId, InputActions, InputNotice, InputState, InputTriggerController, SessionInput, SubmitAttachment, SubmitOutcome } from '../contract/input.ts';
16
16
  import type { ArbitrateKey, ArbitrateOutcome, ComposerKeyboard, ReferenceInsert, TokenSpan } from '../contract/draft-editor.ts';
17
- import type { InputSubmitMode } from '../contract/composer-submission.ts';
17
+ import type { InputSubmitMode, MessageSubmission, MessageSubmissionState } from '../contract/composer-submission.ts';
18
18
  /** Popup face the shell needs (dismissal only; typed structurally to avoid a value import). */
19
19
  export interface PopupDismissFace {
20
20
  dismiss(): void;
@@ -28,6 +28,10 @@ export interface PopupDismissFace {
28
28
  export interface SessionInputDeps {
29
29
  /** Session-scope ctx handed to claim.submit transactions. */
30
30
  actx: Context;
31
+ /** Snapshot the Session facts before asynchronous command arbitration. */
32
+ submissionState?: () => MessageSubmissionState;
33
+ /** Notify one ordinary message attempt before reference serialization. */
34
+ messageSubmitted?: (submission: MessageSubmission) => void;
31
35
  /** Enter adjudication face resolver; absent/undefined answer = every '/' line falls to the default sink. */
32
36
  inputTriggers?: (() => InputTriggerController | undefined) | undefined;
33
37
  /** PopupSelect shell face resolver (dismissal on submit lock / escape). */
@@ -141,7 +145,7 @@ export declare class SessionInputShell implements SessionInput {
141
145
  * (adjudicating/submitting) force-closes the transient layers: the popup
142
146
  * dismisses and the menu tracks frozen.
143
147
  */
144
- submit(mode?: InputSubmitMode): void;
148
+ submit(mode?: InputSubmitMode, source?: 'click' | 'enter'): void;
145
149
  /**
146
150
  * Keyboard arbitration while the menu is open.
147
151
  * @param key - the intercepted key.
@@ -284,6 +288,7 @@ export declare class SessionInputShell implements SessionInput {
284
288
  * its editor snapshot. Chip-free drafts skip the async detour.
285
289
  */
286
290
  private sinkSerialized;
291
+ private notifySubmission;
287
292
  /** Settle one detached default send independently of other sends. */
288
293
  private settleSink;
289
294
  /** Restore one failed detached send without overwriting text entered after a restoration. */
@@ -1,12 +1,3 @@
1
- /**
2
- * InputHub: the SessionInputResolver implementation (`ctx.conversation.input`) — one
3
- * SessionInputShell per session, created inside the uiSession provide
4
- * materialization (the 'input' standard-kit entry IS the
5
- * creation trigger) and torn down by the scope disposer (instance-and-scope
6
- * share one lifecycle). The hub registers the scoped input-mutation
7
- * listeners on each Session context and owns the default-sink choreography: every session is a
8
- * real host entity, so the sink is one unconditional prompt path.
9
- */
10
1
  import type { Context } from '@deepseek-ai/cordis';
11
2
  import type { SessionBinding } from '@deepseek-ai/dsh-api-session-controller/client';
12
3
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
@@ -0,0 +1,10 @@
1
+ /** Maps ordinary message occurrences to Desktop product events. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { MessageSubmission } from '../contract/composer-submission.ts';
4
+ /**
5
+ * Report the original message occurrence without reading newer Session facts.
6
+ * @param ctx - client context.
7
+ * @param submission - original occurrence and Session snapshot.
8
+ */
9
+ export declare function reportMessageSubmission(ctx: Context, submission: MessageSubmission): void;
10
+ //# sourceMappingURL=submission-analytics.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-conversation",
3
3
  "description": "Target-neutral Conversation assembly, shell, composer, queue, and view navigation",
4
- "version": "0.1.7-rc.2",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -61,38 +61,39 @@
61
61
  "@lexical/plain-text": "^0.49.0",
62
62
  "@lexical/history": "^0.49.0",
63
63
  "@deepseek-ai/cordis": "~4.0.4",
64
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
65
- "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
66
- "@deepseek-ai/dsh-api-workspace-controller": "0.1.7-rc.2",
67
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.2",
68
- "@deepseek-ai/dsh-brand": "0.1.7-rc.2",
69
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
70
- "@deepseek-ai/dsh-client-file-upload": "0.1.7-rc.2",
71
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
72
- "@deepseek-ai/dsh-client-test-runtime": "0.1.7-rc.2",
73
- "@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.2",
74
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
75
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
76
- "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2",
77
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
78
- "@deepseek-ai/dsh-client-ui-workspace": "0.1.7-rc.2",
79
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
80
- "@deepseek-ai/dsh-commands": "0.1.7-rc.2",
81
- "@deepseek-ai/dsh-goal": "0.1.7-rc.2",
82
- "@deepseek-ai/dsh-llm-retry": "0.1.7-rc.2",
83
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
84
- "@deepseek-ai/dsh-plan-mode": "0.1.7-rc.2",
85
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
86
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
87
- "@deepseek-ai/dsh-token-meter": "0.1.7-rc.2",
88
- "@deepseek-ai/dsh-tool-todo": "0.1.7-rc.2",
89
- "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.2",
90
- "@deepseek-ai/dsh-workspace": "0.1.7-rc.2",
91
- "@deepseek-ai/dsh-file-reference": "0.1.7-rc.2",
92
- "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.2",
93
- "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
94
- "@deepseek-ai/dsh-settings": "0.1.7-rc.2",
95
- "@deepseek-ai/dsh-client-shortcuts": "0.1.7-rc.2"
64
+ "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.1",
65
+ "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.1",
66
+ "@deepseek-ai/dsh-api-workspace-controller": "0.2.0-rc.1",
67
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
68
+ "@deepseek-ai/dsh-client-file-upload": "0.2.0-rc.1",
69
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.1",
70
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.1",
71
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
72
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.1",
73
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
74
+ "@deepseek-ai/dsh-client-ui-workspace": "0.2.0-rc.1",
75
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
76
+ "@deepseek-ai/dsh-brand": "0.2.0-rc.1",
77
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
78
+ "@deepseek-ai/dsh-commands": "0.2.0-rc.1",
79
+ "@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.1",
80
+ "@deepseek-ai/dsh-goal": "0.2.0-rc.1",
81
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
82
+ "@deepseek-ai/dsh-llm-retry": "0.2.0-rc.1",
83
+ "@deepseek-ai/dsh-plan-mode": "0.2.0-rc.1",
84
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1",
85
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
86
+ "@deepseek-ai/dsh-token-meter": "0.2.0-rc.1",
87
+ "@deepseek-ai/dsh-tool-todo": "0.2.0-rc.1",
88
+ "@deepseek-ai/dsh-util-crypto": "0.2.0-rc.1",
89
+ "@deepseek-ai/dsh-workspace": "0.2.0-rc.1",
90
+ "@deepseek-ai/dsh-file-reference": "0.2.0-rc.1",
91
+ "@deepseek-ai/dsh-util-workspace-path": "0.2.0-rc.1",
92
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
93
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
94
+ "@deepseek-ai/dsh-client-shortcuts": "0.2.0-rc.1",
95
+ "@deepseek-ai/dsh-client-product-analytics": "0.2.0-rc.1",
96
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1"
96
97
  },
97
98
  "files": [
98
99
  "lib/index.js",