@lemoncat7/dsh-knowledge 2.6.1 → 2.7.0

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.md CHANGED
@@ -8,6 +8,16 @@
8
8
 
9
9
  ## 兼容性
10
10
 
11
+ ### 2.7.0 更新
12
+
13
+ 正式版,适配 DSH **0.1.2-rc.1**。
14
+
15
+ - 新增回写任务工作区:按最新创建顺序查看队列、日期、阻塞项、失败原因和写入目标,支持重试与取消。
16
+ - 保留中断后的内容快照和写入计划,支持重启恢复;取消请求持久化,执行退出前不会提前放行后续轮次。
17
+ - 队列和对话共享状态变化通知,断线自动重连、轮询兜底;修复记录不存在时仍显示旧等待状态的问题。
18
+ - 新任务记录创建时间,旧任务未记录的时间不伪造。已写入的内容不会因取消任务而撤销。
19
+ - 150 项自动化测试通过,另通过桌面、手机、平板浏览器验证。需更新发起回写的客户端;只升级中央知识库不会更新客户端队列逻辑。
20
+
11
21
  ### 2.6.1 更新
12
22
 
13
23
  适配 DSH **0.1.2-rc.1**。
@@ -20,6 +30,16 @@
20
30
 
21
31
  `2.6.0` 将轮后回写改为本机持久化队列:先保存本轮快照,再释放会话结束流程,不再等待提取模型或远程写入。失败可在原回答下重试;重启和后续对话不会覆盖未完成任务。状态显示等待回写/回写中/成功及目标文档/失败与原因。远程直接写入需要中央服务同时升级到 `2.6.0`,旧服务不支持幂等回执时会明确报错并保留计划,不冒险重复写入。
22
32
 
33
+ ### 回写任务管理
34
+
35
+ 知识工作区的“回写任务”,以及回答下方的“管理回写”,可查看本机持久化队列。按创建先后倒序显示,最新任务在前,每页最多 50 条,可按会话筛选,查看创建日期时间、尝试次数、下次重试时间、阻塞任务、失败原因及完成后的目标文档。旧记录未保存创建时间时明确标注,不用迁移时间冒充。即使连接中央知识库,这里管理的也是当前 DSH 客户端的队列。
36
+
37
+ - 网络等暂时错误最多自动尝试 5 次,之后保留为失败项,支持手动重试。重试复用原快照和已保存的计划,不使用后续对话重新生成旧任务。
38
+ - 服务正常关闭后恢复排队;进程意外退出时,在旧工作租约到期后恢复(租约为 60 秒)。失败项仍需处理,不会在每次启动时无限重试。
39
+ - 同会话必须按顺序回写。前一轮失败时,可重试前一轮,或取消它以放行后续轮次;其他会话不会被该失败项阻塞。
40
+ - 排队和失败项可立即取消。执行中的任务先记录取消请求,待当前执行退出后完成取消,期间不提前放行后续任务。取消请求在重启后仍有效;已写入内容不撤销,也不支持恢复已取消的任务。
41
+ - 页面通过共享长轮询接收状态变更通知,收到变化后立即查询最新状态,同时保留每 5 秒刷新队列的兜底。隐藏后暂停连接,重新显示或网络恢复后重新同步;原回答下也使用共享通知通道。确认记录不存在时清除旧等待状态,查询网络失败则提示重连,不误报任务不存在。历史版本未保存快照的任务不能安全重试。
42
+
23
43
  前端回写状态自动轮询,修复浏览器原生 `fetch` 的接收者绑定问题;读取失败会明确显示正在重连,不把查询失败误报为写入失败。139 项自动化测试通过,并验证真实 Chromium 中的状态请求、目标文档链接及桌面/手机/平板展示。
24
44
 
25
45
  `2.5.1` 修复知识目录的长文件名撑宽列表、遮挡结束状态的问题:名称按剩余空间省略,悬停可查看完整名称和路径,“已解决/已收集完成”标签完整保留。不改变文档内容、权限、配色或动画。
package/lib/client.d.ts CHANGED
@@ -15,6 +15,9 @@ export type KnowledgeDocumentTarget = {
15
15
  } | {
16
16
  view: 'notes';
17
17
  noteId?: string;
18
+ } | {
19
+ view: 'writeback';
20
+ sessionId: string;
18
21
  };
19
22
  /** Cordis services needed by the browser half. */
20
23
  export declare const inject: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA8CnE,MAAM,WAAW,4BAA4B;IAC3C,MAAM,IAAI,OAAO,CAAA;IACjB,IAAI,IAAI,IAAI,CAAA;IACZ,MAAM,IAAI,IAAI,CAAA;IACd,YAAY,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAAA;IACnD,aAAa,IAAI,uBAAuB,GAAG,SAAS,CAAA;IACpD,KAAK,IAAI,IAAI,CAAA;IACb,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAMtC,kDAAkD;AAClD,eAAO,MAAM,MAAM,UAA2C,CAAA;AAE9D,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyB9C"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA8CnE,MAAM,WAAW,4BAA4B;IAC3C,MAAM,IAAI,OAAO,CAAA;IACjB,IAAI,IAAI,IAAI,CAAA;IACZ,MAAM,IAAI,IAAI,CAAA;IACd,YAAY,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAAA;IACnD,aAAa,IAAI,uBAAuB,GAAG,SAAS,CAAA;IACpD,KAAK,IAAI,IAAI,CAAA;IACb,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAM5C,kDAAkD;AAClD,eAAO,MAAM,MAAM,UAA2C,CAAA;AAE9D,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyB9C"}
package/lib/client.js CHANGED
@@ -44,6 +44,8 @@ var WritebackStatusClient = class {
44
44
  failures = 0;
45
45
  value;
46
46
  readError;
47
+ missing = false;
48
+ invalidated = false;
47
49
  setVisible(visible) {
48
50
  this.visible = visible;
49
51
  if (visible) this.refresh();
@@ -56,6 +58,11 @@ var WritebackStatusClient = class {
56
58
  if (this.disposed || !this.visible || this.retrying || this.request && !this.request.signal.aborted) return;
57
59
  void this.load(false);
58
60
  }
61
+ invalidate() {
62
+ if (this.disposed || !this.visible) return;
63
+ if (this.retrying || this.request && !this.request.signal.aborted) this.invalidated = true;
64
+ else this.refresh();
65
+ }
59
66
  retry() {
60
67
  if (this.disposed || this.retrying || !this.value?.retryable) return;
61
68
  this.retrying = true;
@@ -77,13 +84,20 @@ var WritebackStatusClient = class {
77
84
  });
78
85
  const body = await response.json();
79
86
  if (this.request !== controller || this.disposed) return;
80
- if (!response.ok && (retry || response.status !== 404)) throw new Error(body.error ?? `\u72B6\u6001\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
81
- this.readError = void 0;
87
+ if (!response.ok && (retry || response.status !== 404 || String(body.status) !== "missing")) throw new Error(body.error ?? `\u72B6\u6001\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
88
+ if (response.ok && !body.summary) throw new Error("\u65E0\u6548\u7684\u56DE\u5199\u72B6\u6001\u54CD\u5E94");
82
89
  if (response.ok && body.summary) {
90
+ this.readError = void 0;
91
+ this.missing = false;
83
92
  this.value = body;
84
93
  this.failures = 0;
85
- delay = body.status === "completed" ? 0 : body.status === "failed" ? 15e3 : 1500;
94
+ delay = body.status === "completed" || body.status === "cancelled" ? 0 : body.status === "failed" ? 15e3 : 1500;
86
95
  } else {
96
+ if (this.value || this.missing) {
97
+ this.missing = true;
98
+ this.value = void 0;
99
+ this.readError = "\u5F53\u524D\u5BA2\u6237\u7AEF\u627E\u4E0D\u5230\u8FD9\u8F6E\u56DE\u5199\u8BB0\u5F55\uFF1B\u65E7\u7B49\u5F85\u72B6\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u56DE\u5199\u4EFB\u52A1\u4E2D\u6838\u5BF9\u3002";
100
+ } else this.readError = void 0;
87
101
  delay = ++this.failures <= 3 ? 1500 : 6e4;
88
102
  }
89
103
  } catch (error) {
@@ -97,7 +111,10 @@ var WritebackStatusClient = class {
97
111
  this.request = void 0;
98
112
  if (retry) this.retrying = false;
99
113
  this.change(this.value, this.retrying, this.readError);
100
- if (delay && this.visible) this.timer = setTimeout(() => this.refresh(), delay);
114
+ if (this.invalidated && this.visible) {
115
+ this.invalidated = false;
116
+ this.refresh();
117
+ } else if (delay && this.visible) this.timer = setTimeout(() => this.refresh(), delay);
101
118
  }
102
119
  }
103
120
  }
@@ -108,6 +125,75 @@ var WritebackStatusClient = class {
108
125
  }
109
126
  };
110
127
 
128
+ // web/writeback-live.js
129
+ var groups = /* @__PURE__ */ new Map();
130
+ function subscribeWritebackChanges(client, listener) {
131
+ let group = groups.get(client);
132
+ if (!group) {
133
+ let emit2 = function() {
134
+ for (const fn of listeners) fn();
135
+ }, resume2 = function() {
136
+ clearTimeout(timer);
137
+ request2?.abort();
138
+ request2 = void 0;
139
+ if (!document.hidden && !stopped) {
140
+ emit2();
141
+ void poll();
142
+ }
143
+ };
144
+ var emit = emit2, resume = resume2;
145
+ const listeners = /* @__PURE__ */ new Set();
146
+ let stopped = false, request2, timer, revision = "", failures = 0;
147
+ async function poll() {
148
+ if (stopped || document.hidden) return;
149
+ const controller = new AbortController();
150
+ request2 = controller;
151
+ try {
152
+ const response = await fetch(`/knowledge-control/v1/writeback-changes?since=${encodeURIComponent(revision)}`, {
153
+ credentials: "same-origin",
154
+ cache: "no-store",
155
+ headers: { "x-dsh-knowledge-client": client },
156
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(25e3)])
157
+ });
158
+ if (!response.ok) throw new Error("notification transport unavailable");
159
+ const data = await response.json();
160
+ if (typeof data.revision !== "string") throw new Error("invalid revision");
161
+ if (stopped || controller.signal.aborted) return;
162
+ const changed = revision !== data.revision || failures > 0;
163
+ revision = data.revision;
164
+ failures = 0;
165
+ if (changed) emit2();
166
+ } catch {
167
+ if (!controller.signal.aborted) failures++;
168
+ } finally {
169
+ if (request2 === controller) {
170
+ request2 = void 0;
171
+ if (!stopped && !document.hidden) timer = setTimeout(poll, failures ? Math.min(3e4, 1e3 * 2 ** Math.min(failures, 5)) : 0);
172
+ }
173
+ }
174
+ }
175
+ document.addEventListener("visibilitychange", resume2);
176
+ window.addEventListener("online", resume2);
177
+ group = { listeners, close() {
178
+ stopped = true;
179
+ clearTimeout(timer);
180
+ request2?.abort();
181
+ document.removeEventListener("visibilitychange", resume2);
182
+ window.removeEventListener("online", resume2);
183
+ } };
184
+ groups.set(client, group);
185
+ timer = setTimeout(poll, 0);
186
+ }
187
+ group.listeners.add(listener);
188
+ return () => {
189
+ group.listeners.delete(listener);
190
+ if (!group.listeners.size) {
191
+ group.close();
192
+ groups.delete(client);
193
+ }
194
+ };
195
+ }
196
+
111
197
  // src/workspace-ownership.ts
112
198
  var WORKSPACE_ACTIVATE_EVENT = "@lemoncat7/dsh-plugin-ui/workspace-activate";
113
199
  function activatePluginWorkspace(pluginId) {
@@ -4040,6 +4126,7 @@ function KnowledgeWritebackStatus({
4040
4126
  }
4041
4127
  );
4042
4128
  client.current = status;
4129
+ const unsubscribeChanges = subscribeWritebackChanges("conversation-web", () => status.invalidate());
4043
4130
  let inView = true;
4044
4131
  const refresh = () => status.setVisible(inView && !document.hidden);
4045
4132
  const observer = typeof IntersectionObserver === "undefined" ? void 0 : new IntersectionObserver((entries2) => {
@@ -4057,10 +4144,14 @@ function KnowledgeWritebackStatus({
4057
4144
  window.removeEventListener("focus", refresh);
4058
4145
  window.removeEventListener("online", refresh);
4059
4146
  status.dispose();
4147
+ unsubscribeChanges();
4060
4148
  client.current = void 0;
4061
4149
  };
4062
4150
  }, [sessionId, turn]);
4063
- if (state === void 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: container, children: readError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-knowledge-writeback-notice", role: "status", children: readError }) });
4151
+ if (state === void 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: container, children: readError && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-knowledge-writeback-notice", role: "status", children: [
4152
+ readError,
4153
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => workspace.openDocument({ view: "writeback", sessionId }), children: "\u7BA1\u7406\u56DE\u5199" })
4154
+ ] }) });
4064
4155
  const retry = () => client.current?.retry();
4065
4156
  const destinations = state.destinations ?? [];
4066
4157
  const summary = state.summary.replace(/^知识库回写\s*·\s*/u, "");
@@ -4071,7 +4162,8 @@ function KnowledgeWritebackStatus({
4071
4162
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: state.error, children: summary }),
4072
4163
  state.status === "failed" && state.retryable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", disabled: retrying, onClick: () => {
4073
4164
  void retry();
4074
- }, children: retrying ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u8BD5" })
4165
+ }, children: retrying ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u8BD5" }),
4166
+ state.status !== "completed" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => workspace.openDocument({ view: "writeback", sessionId }), children: "\u7BA1\u7406\u56DE\u5199" })
4075
4167
  ] }),
4076
4168
  destinations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "dsh-knowledge-writeback-destinations", "aria-label": "\u56DE\u5199\u76EE\u6807", children: destinations.map((destination, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { children: [
4077
4169
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-knowledge-writeback-base", children: destination.knowledgeBaseName }),
@@ -4487,7 +4579,10 @@ function knowledgePanelUrl(managementPath, sessionId, projectId, target) {
4487
4579
  if (sessionId !== void 0) params.set("sessionId", sessionId);
4488
4580
  if (projectId !== void 0) params.set("projectId", projectId);
4489
4581
  if (target !== void 0) {
4490
- if (target.view === "notes") {
4582
+ if (target.view === "writeback") {
4583
+ params.set("view", "writeback");
4584
+ params.set("sessionId", target.sessionId);
4585
+ } else if (target.view === "notes") {
4491
4586
  params.set("view", "notes");
4492
4587
  if (target.noteId !== void 0) params.set("noteId", target.noteId);
4493
4588
  } else {