@ch4acko3/dsh-turn-fold 0.4.0 → 0.4.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.md CHANGED
@@ -2,14 +2,17 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
+ [![Powered by Harmony](https://memorax-ai.github.io/dsh-harmony/harmony-powered.svg)](https://memorax-ai.github.io/dsh-harmony/)
6
+
5
7
  A [dsh-harmony](https://github.com/memorax-ai/dsh-harmony) provider that adds
6
8
  Codex-Desktop-style turn folding to the DSH WebUI conversation.
7
9
 
8
10
  https://github.com/user-attachments/assets/3c9dfdcf-a454-4750-9edf-76771ed5a9a6
9
11
 
10
12
  During a turn, the summary bar remains visible while native thinking, notes,
11
- commands, and tool calls keep streaming. Consecutive reasoning and tool activity
12
- share one compact group from the second item onward. When a turn settles, known
13
+ commands, and tool calls keep streaming. Consecutive reasoning, context
14
+ injections, and tool activity share one compact group from the second item
15
+ onward. When a turn settles, known
13
16
  Agent activity, including context injection, moves into one disclosure
14
17
  immediately before the final answer. Its configurable metrics default to wall
15
18
  time, tool-call count, and input/output tokens, and appear only when the loaded
package/README.zh-CN.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
+ [![Powered by Harmony](https://memorax-ai.github.io/dsh-harmony/harmony-powered.svg)](https://memorax-ai.github.io/dsh-harmony/)
6
+
5
7
  一个基于 [dsh-harmony](https://github.com/memorax-ai/dsh-harmony) 的 Provider,为 DSH WebUI 对话添加 Codex Desktop 风格的回合折叠。
6
8
 
7
9
  https://github.com/user-attachments/assets/3c9dfdcf-a454-4750-9edf-76771ed5a9a6
8
10
 
9
- 回合进行期间,摘要栏保持可见,原生思考、说明、命令和工具调用继续流式输出。从第二项开始,连续的推理和工具活动会合并为一个紧凑分组。回合结束后,包括上下文注入在内的已知 Agent 活动会收进最终答复前方的折叠栏。摘要指标可以配置,默认显示耗时、工具调用次数以及输入/输出 token;只有当已加载的回合包含足够数据、无需猜测即可计算时才会展示对应指标。
11
+ 回合进行期间,摘要栏保持可见,原生思考、说明、命令和工具调用继续流式输出。从第二项开始,连续的推理、上下文注入和工具活动会合并为一个紧凑分组。回合结束后,包括上下文注入在内的已知 Agent 活动会收进最终答复前方的折叠栏。摘要指标可以配置,默认显示耗时、工具调用次数以及输入/输出 token;只有当已加载的回合包含足够数据、无需猜测即可计算时才会展示对应指标。
10
12
 
11
13
  最终答复通过 `turn-tail.closing.finalNode` 定位,不依赖 `finish_reason` 或 DOM 位置。正常完成、已停止和已中断的回合都会折叠,后两者显示不同的状态标签。当 DSH 将结束分支标记为不可用、结束答复之后仍有节点,或者用户的键盘焦点或文本选择位于活动内容中时,回合会保持展开。失败、达到最大 token、缺少结束节点以及仍在进行的回合也会保持展开,避免隐藏错误或未完成的工作。
12
14
 
package/index.cjs CHANGED
@@ -1,13 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  // Host entry for the @ch4acko3/dsh-turn-fold Harmony provider.
4
- const { settingsNamespace } = require('@deepseek-ai/dsh-settings')
5
- const { Config, SETTINGS_NAMESPACE } = require('./settings.cjs')
4
+ const { Config, SETTINGS_NAMESPACE, createSettingsSchema } = require('./settings.cjs')
6
5
 
7
6
  exports.Config = Config
8
7
  exports.inject = ['harmony']
9
8
  exports.apply = (ctx, config) => {
10
9
  ctx.inject(['settings'], (settingsCtx) => {
11
- settingsCtx.settings.register(settingsNamespace(SETTINGS_NAMESPACE), Config, { base: config })
10
+ settingsCtx.settings.register(SETTINGS_NAMESPACE, createSettingsSchema(), { base: config })
12
11
  })
13
12
  }
package/inline-source.cjs CHANGED
@@ -242,9 +242,11 @@ function __ch4acko3DshTurnFoldActivityGroup(props) {
242
242
  var foldKey = props.foldKey;
243
243
  var toolCount = 0;
244
244
  var reasoningCount = 0;
245
+ var contextCount = 0;
245
246
  for (var itemIndex = 0; itemIndex < props.items.length; itemIndex++) {
246
247
  if (props.items[itemIndex].kind === "tool") toolCount++;
247
- else reasoningCount++;
248
+ else if (props.items[itemIndex].kind === "reasoning") reasoningCount++;
249
+ else if (props.items[itemIndex].kind === "context") contextCount++;
248
250
  }
249
251
  var initialOpen = __ch4acko3DshTurnFoldOpenKeys.has(foldKey);
250
252
  var openState = react.useState(initialOpen);
@@ -265,12 +267,15 @@ function __ch4acko3DshTurnFoldActivityGroup(props) {
265
267
  };
266
268
  }, []);
267
269
  var reasoningTitle = reasoningCount === 0 ? null : __ch4acko3DshTurnFoldText("activityGroup.reasoning." + (reasoningCount === 1 ? "one" : "many"), { count: reasoningCount });
270
+ var contextTitle = contextCount === 0 ? null : __ch4acko3DshTurnFoldText("activityGroup.context." + (contextCount === 1 ? "one" : "many"), { count: contextCount });
268
271
  var toolTitle = toolCount === 0 ? null : __ch4acko3DshTurnFoldText("activityGroup.tools." + (toolCount === 1 ? "one" : "many"), { count: toolCount });
269
- var title = reasoningTitle === null ? toolTitle : toolTitle === null ? reasoningTitle : react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, { children: [
270
- reasoningTitle,
271
- react_jsx_runtime.jsx("span", { className: "__ch4acko3-dsh-turn-fold-activity__separator", "aria-hidden": true }),
272
- toolTitle
273
- ] });
272
+ var titleParts = [reasoningTitle, contextTitle, toolTitle].filter(function (part) { return part !== null; });
273
+ var titleChildren = [];
274
+ for (var titleIndex = 0; titleIndex < titleParts.length; titleIndex++) {
275
+ if (titleIndex > 0) titleChildren.push(react_jsx_runtime.jsx("span", { className: "__ch4acko3-dsh-turn-fold-activity__separator", "aria-hidden": true }, "separator-" + String(titleIndex)));
276
+ titleChildren.push(titleParts[titleIndex]);
277
+ }
278
+ var title = titleParts.length === 1 ? titleParts[0] : react_jsx_runtime.jsx(react_jsx_runtime.Fragment, { children: titleChildren });
274
279
  var failure = props.failed === 0 ? null : __ch4acko3DshTurnFoldText("activityGroup.failures." + (props.failed === 1 ? "one" : "many"), { count: props.failed });
275
280
  var state = props.running ? "running" : props.failed > 0 ? "error" : "ok";
276
281
  function toggle() {
@@ -829,7 +834,7 @@ function __ch4acko3DshTurnFoldHasToolCallBlock(node) {
829
834
  return Array.isArray(blocks) && blocks.some(function (block) { return block !== void 0 && block.kind === "tool-call"; });
830
835
  }
831
836
  function __ch4acko3DshTurnFoldIsActivityNode(node) {
832
- if (node.kind === "tool-call") return true;
837
+ if (node.kind === "tool-call" || node.kind === "context") return true;
833
838
  if (node.kind !== "assistant-step" || __ch4acko3DshTurnFoldReasoningTexts(node).length === 0) return false;
834
839
  return __ch4acko3DshTurnFoldHasToolCallBlock(node) || !__ch4acko3DshTurnFoldHasVisibleNonReasoning(node);
835
840
  }
@@ -877,6 +882,8 @@ function __ch4acko3DshTurnFoldRenderEntries(entries, nodeStore, renderNode, sess
877
882
  if (activityEntry.node.kind === "tool-call") {
878
883
  toolKeys.push(activityEntry.key);
879
884
  items.push({ kind: "tool", key: activityEntry.key });
885
+ } else if (activityEntry.node.kind === "context") {
886
+ items.push({ kind: "context", key: activityEntry.key });
880
887
  } else {
881
888
  var texts = __ch4acko3DshTurnFoldReasoningTexts(activityEntry.node);
882
889
  for (var textIndex = 0; textIndex < texts.length; textIndex++) items.push({ kind: "reasoning", key: String(activityEntry.key) + ":" + String(textIndex), text: texts[textIndex] });
package/locales/en.json CHANGED
@@ -17,6 +17,8 @@
17
17
  "summary.interruptedSuffix": " - Interrupted",
18
18
  "activityGroup.reasoning.one": "{count} reasoning step",
19
19
  "activityGroup.reasoning.many": "{count} reasoning steps",
20
+ "activityGroup.context.one": "{count} context injection",
21
+ "activityGroup.context.many": "{count} context injections",
20
22
  "activityGroup.tools.one": "{count} tool call",
21
23
  "activityGroup.tools.many": "{count} tool calls",
22
24
  "activityGroup.failures.one": "{count} failed",
package/locales/zh.json CHANGED
@@ -17,6 +17,8 @@
17
17
  "summary.interruptedSuffix": " - 已中断",
18
18
  "activityGroup.reasoning.one": "{count} 段思考",
19
19
  "activityGroup.reasoning.many": "{count} 段思考",
20
+ "activityGroup.context.one": "{count} 次上下文注入",
21
+ "activityGroup.context.many": "{count} 次上下文注入",
20
22
  "activityGroup.tools.one": "{count} 次工具调用",
21
23
  "activityGroup.tools.many": "{count} 次工具调用",
22
24
  "activityGroup.failures.one": "{count} 个失败",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ch4acko3/dsh-turn-fold",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Codex-style turn folding for the DSH WebUI conversation, implemented as a dsh-harmony provider.",
5
5
  "main": "./index.cjs",
6
6
  "engines": {
@@ -61,7 +61,7 @@
61
61
  "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8",
62
62
  "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
63
63
  "@phenomnomnominal/tsquery": "^6.2.0",
64
- "dsh-harmony": "^0.7.0",
64
+ "dsh-harmony": "^0.7.2",
65
65
  "react": "18.3.1",
66
66
  "react-test-renderer": "18.3.1",
67
67
  "typescript": "^6.0.3"
package/patch.cjs CHANGED
@@ -27,6 +27,7 @@ const TARGET = {
27
27
  module.exports = [
28
28
  {
29
29
  id: 'inject-turn-fold-runtime',
30
+ description: 'Provides the Turn Fold rendering, disclosure, metrics, settings, and locale runtime used by ChatView.',
30
31
  target: TARGET,
31
32
  select: 'FunctionDeclaration[name.name="ChatView"], VariableStatement:has(VariableDeclaration[name.name="ChatView"])',
32
33
  expect: 1,
@@ -36,6 +37,7 @@ module.exports = [
36
37
  },
37
38
  {
38
39
  id: 'rewrite-node-render-loop',
40
+ description: 'Routes ChatView node rendering through Turn Fold while preserving the native node renderer.',
39
41
  target: TARGET,
40
42
  select: 'CallExpression[expression.name.name="map"][expression.expression.name="order"]',
41
43
  expect: 1,
@@ -52,6 +54,7 @@ module.exports = [
52
54
  },
53
55
  {
54
56
  id: 'install-turn-fold-services',
57
+ description: 'Registers Turn Fold locales and connects its settings when the conversation UI starts.',
55
58
  target: TARGET,
56
59
  select: 'VariableStatement:has(VariableDeclaration[name.name="t"][initializer.expression.name.name="bind"])',
57
60
  expect: 1,
package/settings.cjs CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const z = require('@deepseek-ai/schemastery')
4
-
5
3
  const SETTINGS_NAMESPACE = 'dsh-turn-fold'
6
4
 
7
5
  const SUMMARY_FIELDS = [
@@ -24,12 +22,50 @@ const DEFAULT_SUMMARY_FIELDS = [
24
22
  'outputTokens',
25
23
  ]
26
24
 
27
- const Config = z.object({
28
- summaryFields: z.array(z.union(SUMMARY_FIELDS)).default(DEFAULT_SUMMARY_FIELDS),
25
+ function decodeConfig(value) {
26
+ const source = value == null ? {} : value
27
+ if (typeof source !== 'object' || Array.isArray(source)) throw new TypeError('expected config to be an object')
28
+ const summaryFields = source.summaryFields === undefined ? DEFAULT_SUMMARY_FIELDS : source.summaryFields
29
+ if (!Array.isArray(summaryFields)) throw new TypeError('summaryFields must be an array')
30
+ for (const field of summaryFields) {
31
+ if (!SUMMARY_FIELDS.includes(field)) throw new TypeError(`unknown summary field: ${String(field)}`)
32
+ }
33
+ return { ...source, summaryFields: summaryFields.slice() }
34
+ }
35
+
36
+ function Config(value) {
37
+ return decodeConfig(value)
38
+ }
39
+
40
+ Object.defineProperty(Config, '~standard', {
41
+ value: {
42
+ version: 1,
43
+ vendor: '@ch4acko3/dsh-turn-fold',
44
+ validate(value) {
45
+ try {
46
+ return { value: decodeConfig(value) }
47
+ } catch (error) {
48
+ return { issues: [{ message: error instanceof Error ? error.message : String(error) }] }
49
+ }
50
+ },
51
+ },
29
52
  })
30
53
 
54
+ let settingsSchema
55
+ function createSettingsSchema() {
56
+ if (settingsSchema !== undefined) return settingsSchema
57
+ // The Host loads CommonJS providers alongside the ESM settings graph. Defer
58
+ // Schemastery until that service is ready to avoid a Node 24 CJS/ESM race.
59
+ const z = require('@deepseek-ai/schemastery')
60
+ settingsSchema = z.object({
61
+ summaryFields: z.array(z.union(SUMMARY_FIELDS)).default(DEFAULT_SUMMARY_FIELDS),
62
+ })
63
+ return settingsSchema
64
+ }
65
+
31
66
  module.exports = {
32
67
  Config,
68
+ createSettingsSchema,
33
69
  DEFAULT_SUMMARY_FIELDS,
34
70
  SETTINGS_NAMESPACE,
35
71
  SUMMARY_FIELDS,