dsh-dsml-rescue 0.1.0 → 0.2.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
@@ -5,15 +5,16 @@
5
5
  <h1 align="center">dsh-dsml-rescue</h1>
6
6
 
7
7
  <div align="center">
8
- <p><strong>Continue the turn a model stalled by writing tool-call markup into its reasoning</strong></p>
9
- <p><em>把"思考里写了工具调用标记、于是什么都没发生"的那一回合续上</em></p>
8
+ <p><strong>A stalled turn gets another one</strong></p>
9
+ <p><em>空转的轮次,替你续上</em></p>
10
10
 
11
11
  <p>
12
- <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
12
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
13
13
  </p>
14
14
 
15
15
  <p>
16
16
  <a href="https://github.com/dsh-plugin-lab/dsh-dsml-rescue/actions/workflows/ci.yml"><img src="https://github.com/dsh-plugin-lab/dsh-dsml-rescue/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
17
+ <a href="https://www.npmjs.com/package/dsh-dsml-rescue"><img src="https://img.shields.io/npm/v/dsh-dsml-rescue.svg?style=flat" alt="npm"></a>
17
18
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat" alt="MIT License"></a>
18
19
  </p>
19
20
 
@@ -28,51 +29,60 @@
28
29
 
29
30
  ---
30
31
 
31
- Some provider routes deliver a model's tool call as **reasoning text carrying the call's own markup**
32
- instead of a structured call. The call never happens, the turn closes having produced nothing, and
33
- the conversation waits for someone to type "continue". This plugin types it for you.
32
+ ## Does this look familiar?
34
33
 
35
- ## What it does
34
+ You asked for something. The model reasoned for a while. Then the turn simply ended — no answer, no
35
+ tool call, nothing. The reasoning output ended with lines like these:
36
+
37
+ ```
38
+ </||DSML|| parameter>
39
+ </||DSML|| invoke>
40
+ </||DSML|| calls>
41
+ ```
42
+
43
+ DSML (DeepSeek Markup Language) is how DeepSeek formats a tool call. Some provider routes hand that
44
+ markup back as **ordinary reasoning text** instead of an actual call, so the call never happens, the
45
+ turn closes with no output, and everything waits for someone to type "continue".
36
46
 
37
- - Continues a turn that closed with leaked protocol markup and **nothing else**.
38
- - Sends one neutral sentence: take the next step, or close out with a summary.
39
- - Waits for you — if you are already typing, it stays out of the way.
40
- - Stops after a few attempts instead of spending requests forever.
41
- - Covers child agents too, so a delegated run is not reported finished while it is stuck.
47
+ This plugin types it.
42
48
 
43
- ## Getting started
49
+ ## What it does
44
50
 
45
- **Prerequisites**: DSH on its current RC line; Node `>= 20`. The only source for the compatible range
46
- is `engines.dsh` in [package.json](package.json).
51
+ - Continues a stalled turn: DSML markup at the end of the reasoning, and nothing else produced.
52
+ - Sends one short, neutral line — carry on, or wrap up if the work is done.
53
+ - Backs off while you are typing.
54
+ - Gives up after a few attempts instead of burning requests.
55
+ - Covers subagents too, so a delegated task is not falsely reported as completed while it is stuck.
56
+
57
+ ## Install
47
58
 
48
59
  ```bash
49
60
  dsh plugin --profile web add dsh-dsml-rescue
50
61
  ```
51
62
 
52
- Restart `dsh --profile web`. Nothing to configure: the defaults are the intended behaviour.
63
+ Then restart `dsh --profile web`. There is nothing to configure — the defaults are the intended
64
+ behaviour.
53
65
 
54
- From source: `git clone`, then `pnpm install && pnpm run build`, then
66
+ Working from source: clone, `pnpm install && pnpm run build`, then
55
67
  `dsh plugin --profile web add "$PWD"`.
56
68
 
57
69
  ## What it will not do
58
70
 
59
- - **It does not fix the cause.** The leak happens below the model, so no prompt prevents it; this
60
- plugin only shortens the pause that follows.
61
- - **It does not rescue turns that ended empty for other reasons.** No markup, no rescue — a
62
- cancellation, a failure, or a step cut off at its output ceiling all stay untouched.
63
- - **It does not touch your conversation.** Nothing is rewritten or re-sent; it adds one message and
64
- lets the host run.
65
-
66
- ## Configuration
67
-
68
- Three keys, all optional: `enabled`, `maxConsecutive`, `applyToSubagents`. Defaults, and what moving
69
- each one changes → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#configuration).
70
-
71
- ## Under the hood
72
-
73
- How a stall is recognized, why the continuation is queued where it is, and what must not change →
74
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
75
- What each check establishes, and a repeatable end-to-end run → [docs/VERIFICATION.md](docs/VERIFICATION.md).
71
+ - **It does not fix the cause.** The markup leaks below the model, out of reach of any prompt. What
72
+ it buys you is the wait, shortened.
73
+ - **It does not rescue other empty turns.** No DSML markup, no rescue: a cancellation, a failed
74
+ request, or a reply cut off at the output limit is left alone on purpose.
75
+ - **It does not touch your conversation.** Nothing is rewritten or replayed; it adds one message and
76
+ lets the host carry on.
77
+
78
+ ## Going deeper
79
+
80
+ - Which three settings exist and what each one changes →
81
+ [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#configuration)
82
+ - How a stalled turn is recognised, and the rules that must not be broken →
83
+ [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
84
+ - What the tests prove, and how to reproduce the whole thing locally →
85
+ [docs/VERIFICATION.md](docs/VERIFICATION.md)
76
86
 
77
87
  ## License
78
88
 
package/README_zh.md CHANGED
@@ -5,15 +5,16 @@
5
5
  <h1 align="center">dsh-dsml-rescue</h1>
6
6
 
7
7
  <div align="center">
8
- <p><strong>把"思考里写了工具调用标记、于是什么都没发生"的那一回合续上</strong></p>
9
- <p><em>Continue the turn a model stalled by writing tool-call markup into its reasoning</em></p>
8
+ <p><strong>空转的轮次,替你续上</strong></p>
9
+ <p><em>A stalled turn gets another one</em></p>
10
10
 
11
11
  <p>
12
- <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
12
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh"></a>
13
13
  </p>
14
14
 
15
15
  <p>
16
16
  <a href="https://github.com/dsh-plugin-lab/dsh-dsml-rescue/actions/workflows/ci.yml"><img src="https://github.com/dsh-plugin-lab/dsh-dsml-rescue/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
17
+ <a href="https://www.npmjs.com/package/dsh-dsml-rescue"><img src="https://img.shields.io/npm/v/dsh-dsml-rescue.svg?style=flat" alt="npm"></a>
17
18
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat" alt="MIT License"></a>
18
19
  </p>
19
20
 
@@ -28,44 +29,49 @@
28
29
 
29
30
  ---
30
31
 
31
- 有些 provider 路由会把模型的工具调用当成**普通思考文本**吐回来,里面夹着调用本身的协议标记。于是调用没有发生,回合以"什么都没产出"结束,会话就停在那儿等人敲一句"继续"。这个插件替你敲。
32
+ ## 这场景眼熟吗
32
33
 
33
- ## 它做什么
34
+ 你提了要求,模型推理了一会儿,然后这一轮就这么结束了——没有回复、没有工具调用,什么都没有。推理过程的最后几行是这样:
34
35
 
35
- - 只续一种回合:以泄漏的协议标记结束、且**没有产出任何别的**内容的回合。
36
- - 只发一句中性的话:要么执行下一步,要么收尾给结论。
37
- - 让着你:你已经在打字时它不插话。
38
- - 续几次就停,不会无限花请求。
39
- - 子 agent 也管,被派出去的活儿不会"卡着却被告知已结束"。
36
+ ```
37
+ </||DSML|| parameter>
38
+ </||DSML|| invoke>
39
+ </||DSML|| calls>
40
+ ```
40
41
 
41
- ## 开始用
42
+ DSML(DeepSeek Markup Language)是 DeepSeek 表示工具调用的协议标记。某些 provider 路由会把这段标记当成**普通推理文本**返回,而不是真的发出调用;于是调用没有发生,这一轮空着结束,所有人都在等一句「继续」。
42
43
 
43
- **前置**:DSH 当前 RC 线;Node `>= 20`。兼容区间的唯一来源是 [package.json](package.json) 里的 `engines.dsh`。
44
+ 这个插件替你把这句「继续」发出去。
45
+
46
+ ## 它做什么
47
+
48
+ - 只救空转的轮次:推理过程末尾带着 DSML 标记,而且没有产出任何别的内容。
49
+ - 发一句很短、很中性的话:接着做,或者已经做完就收尾。
50
+ - 你正在打字时它让开。
51
+ - 连着几次没救回来就收手,不会一直烧请求。
52
+ - 子代理也管:被派出去的任务不会在卡住时被误报为已完成。
53
+
54
+ ## 安装
44
55
 
45
56
  ```bash
46
57
  dsh plugin --profile web add dsh-dsml-rescue
47
58
  ```
48
59
 
49
- 重启 `dsh --profile web` 即可。不需要配置,默认值就是预期行为。
50
-
51
- 从源码装:`git clone` 后 `pnpm install && pnpm run build`,再 `dsh plugin --profile web add "$PWD"`。
52
-
53
- ## 它不做什么
60
+ 然后重启 `dsh --profile web`。不需要配置,默认值就是预期行为。
54
61
 
55
- - **不修根因。** 泄漏发生在模型之下,任何提示词都规避不了;本插件只把随之而来的停顿缩短。
56
- - **不救其他空转。** 没有标记就不救——用户主动取消、请求失败、以及输出被长度截断后停在原地的回合,都不碰。
57
- - **不动你的对话。** 不改写、不重发;只多加一条消息,然后交给宿主继续跑。
62
+ 从源码装:先 clone,`pnpm install && pnpm run build`,再 `dsh plugin --profile web add "$PWD"`。
58
63
 
59
- ## 配置
64
+ ## 它不会做什么
60
65
 
61
- 三个键,都可选:`enabled`、`maxConsecutive`、`applyToSubagents`。默认值以及改动各自的影响 →
62
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#configuration)。
66
+ - **不修根因。** 标记泄漏发生在模型之下,提示词够不着。它换来的只有那段等待被缩短。
67
+ - **不救别的空转轮次。** 没有 DSML 标记就不出手:你主动取消、请求失败、回复被输出上限截断,这些都刻意不碰。
68
+ - **不动你的对话。** 不改写、不重放历史,只多加一条消息,剩下的交给宿主。
63
69
 
64
- ## 内部实现
70
+ ## 想了解更多
65
71
 
66
- 怎么认定一次空转、续跑消息为什么排在那个位置、哪些约束不能破 →
67
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
68
- 每项检查能证明什么、以及可复现的端到端验法 → [docs/VERIFICATION.md](docs/VERIFICATION.md)。
72
+ - 有哪三个配置项、各自会改变什么 → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#configuration)
73
+ - 怎么认定一个空转轮次、哪些规则不能破 → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
74
+ - 测试能证明什么、怎么在本机完整复现 → [docs/VERIFICATION.md](docs/VERIFICATION.md)
69
75
 
70
76
  ## 许可
71
77
 
package/lib/index.d.ts CHANGED
@@ -4,6 +4,10 @@ import { Context } from "@deepseek-ai/cordis";
4
4
  declare const name = "dsh-dsml-rescue";
5
5
  /** The agent registry, needed to map a session back to its live agent. */
6
6
  declare const inject: string[];
7
+ /** Rescues allowed in a row before an agent is left alone. */
8
+ declare const DEFAULT_MAX_CONSECUTIVE = 3;
9
+ /** Whether child agents are rescued by default. */
10
+ declare const DEFAULT_APPLY_TO_SUBAGENTS = true;
7
11
  /** Validated configuration; the defaults are the documented behaviour. */
8
12
  interface Config {
9
13
  /** Whether the plugin acts at all. Turn it off without uninstalling. */
@@ -23,7 +27,7 @@ declare const Config: z<Config>;
23
27
  /**
24
28
  * Mount the plugin.
25
29
  * @param ctx - host context.
26
- * @param config - validated configuration.
30
+ * @param config - validated configuration; the schema fills in every default.
27
31
  */
28
32
  declare function apply(ctx: Context, config: Config): void;
29
- export { Config, apply, inject, name };
33
+ export { Config, DEFAULT_APPLY_TO_SUBAGENTS, DEFAULT_MAX_CONSECUTIVE, apply, inject, name };
package/lib/index.js CHANGED
@@ -60,7 +60,7 @@ function hasLeakedProtocolTail(reasoning) {
60
60
  /**
61
61
  * The one judgment this plugin makes: does a finished turn deserve a rescue?
62
62
  *
63
- * A turn deserves one when it ended normally yet produced nothing the loop or
63
+ * It deserves one when the turn ended normally yet produced nothing the loop or
64
64
  * the user can act on, and its reasoning ends in leaked protocol markup. Both
65
65
  * halves are required — a turn that produced nothing for another reason stays
66
66
  * untouched, because rescuing it would paper over an unrelated failure.
@@ -70,21 +70,20 @@ function hasLeakedProtocolTail(reasoning) {
70
70
  *
71
71
  * @module dsh-dsml-rescue/detect
72
72
  */
73
- /** Whether a text block carries anything other than whitespace. */
74
- function carriesText(block) {
75
- return block.type === "text" && block.text !== void 0 && /\S/.test(block.text);
76
- }
77
73
  /**
78
- * Whether the turn produced anything a caller could act on.
74
+ * Whether one block produced something a caller could act on.
79
75
  *
80
- * An unknown block type counts as actionable on purpose: a provider that adds
81
- * a content kind this plugin has never seen is far more likely to have produced
76
+ * Reasoning never counts — that is where the leak lives, not where the work
77
+ * lands. An unknown block type does count, on purpose: a provider that adds a
78
+ * content kind this plugin has never seen is far likelier to have produced
82
79
  * output than to have produced nothing, and a false rescue costs a request.
83
- * @param blocks - the turn's closing content blocks.
84
- * @returns whether any block is visible text, a tool call, or an unknown kind.
80
+ * @param block - one content block of the closing message.
81
+ * @returns whether the block is visible text, a tool call, or an unknown kind.
85
82
  */
86
- function hasActionableContent(blocks) {
87
- return blocks.some((block) => block.type === "reasoning" ? false : block.type === "text" ? carriesText(block) : true);
83
+ function isActionable(block) {
84
+ if (block.type === "reasoning") return false;
85
+ if (block.type !== "text") return true;
86
+ return block.text !== void 0 && /\S/.test(block.text);
88
87
  }
89
88
  /**
90
89
  * Concatenate every reasoning block of a message.
@@ -92,7 +91,7 @@ function hasActionableContent(blocks) {
92
91
  * @returns the joined reasoning text, or the empty string when there is none.
93
92
  */
94
93
  function joinReasoning(blocks) {
95
- return blocks.flatMap((block) => block.type === "reasoning" && block.text !== void 0 ? [block.text] : []).join("\n");
94
+ return blocks.filter((block) => block.type === "reasoning" && block.text !== void 0).map((block) => block.text ?? "").join("\n");
96
95
  }
97
96
  /**
98
97
  * Decide whether one finished turn is a leaking stall.
@@ -102,7 +101,7 @@ function joinReasoning(blocks) {
102
101
  */
103
102
  function isRescueCandidate(observation) {
104
103
  if (!observation.completed) return false;
105
- if (hasActionableContent(observation.blocks)) return false;
104
+ if (observation.blocks.some(isActionable)) return false;
106
105
  return hasLeakedProtocolTail(joinReasoning(observation.blocks));
107
106
  }
108
107
  // src/policy.ts
@@ -147,8 +146,33 @@ function evaluate(input) {
147
146
  const INITIAL_STATE = {
148
147
  consecutive: 0,
149
148
  turn: void 0,
150
- blocks: []
149
+ blocks: [],
150
+ outputTokens: void 0
151
151
  };
152
+ /** Nothing to do, with the state unchanged. */
153
+ function nothing(state) {
154
+ return {
155
+ state,
156
+ decision: "ignore",
157
+ round: 0
158
+ };
159
+ }
160
+ /**
161
+ * Whether the step being closed spent its whole output budget.
162
+ *
163
+ * Reads as exhausted only when both numbers are known: an unknown ceiling or a
164
+ * missing count reports "not exhausted" on purpose, because a wrong `true`
165
+ * would silence the rescue this plugin exists for, while a wrong `false` only
166
+ * costs the streak one attempt.
167
+ * @param state - state carrying the closing message's output tokens.
168
+ * @param ceiling - the ceiling the request was sent under, when known.
169
+ * @returns whether the model was cut off rather than finished.
170
+ */
171
+ function spentBudget(state, ceiling) {
172
+ if (state.outputTokens === void 0) return false;
173
+ if (ceiling === void 0 || ceiling <= 0) return false;
174
+ return state.outputTokens >= ceiling;
175
+ }
152
176
  /**
153
177
  * Advance one agent's machine by one event.
154
178
  *
@@ -162,41 +186,25 @@ const INITIAL_STATE = {
162
186
  */
163
187
  function reduce(state, event, limits) {
164
188
  switch (event.kind) {
165
- case "assistant-message": return {
166
- state: {
167
- ...state,
168
- turn: event.turn,
169
- blocks: event.blocks
170
- },
171
- decision: "ignore",
172
- round: 0
173
- };
174
- case "human-message": return {
175
- state: {
176
- ...state,
177
- consecutive: 0
178
- },
179
- decision: "ignore",
180
- round: 0
181
- };
182
- case "turn-end": return event.completed ? {
183
- state,
184
- decision: "ignore",
185
- round: 0
186
- } : {
187
- state: {
188
- ...state,
189
- consecutive: 0
190
- },
191
- decision: "ignore",
192
- round: 0
193
- };
189
+ case "assistant-message": return nothing({
190
+ ...state,
191
+ turn: event.turn,
192
+ blocks: event.blocks,
193
+ outputTokens: event.outputTokens
194
+ });
195
+ case "human-message": return nothing({
196
+ ...state,
197
+ consecutive: 0
198
+ });
199
+ case "turn-end": return event.completed ? nothing(state) : nothing({
200
+ ...state,
201
+ consecutive: 0
202
+ });
194
203
  case "turn-stopping": {
195
- const blocks = state.turn === event.turn ? state.blocks : [];
196
204
  const outcome = evaluate({
197
- candidate: !event.outputExhausted && isRescueCandidate({
205
+ candidate: isRescueCandidate({
198
206
  completed: true,
199
- blocks
207
+ blocks: state.turn === event.turn && !spentBudget(state, event.outputCeiling) ? state.blocks : []
200
208
  }),
201
209
  consecutive: state.consecutive,
202
210
  maxConsecutive: limits.maxConsecutive,
@@ -253,38 +261,12 @@ function humanPending(agent) {
253
261
  return agent.inbox.nextTurn.some((message) => message.source.kind === "user") || agent.inbox.nextStep.some((message) => message.source.kind === "user");
254
262
  }
255
263
  /**
256
- * Whether the step being closed spent its whole output budget.
257
- *
258
- * The seam carries no stop reason, so this compares the closing message's
259
- * output-token count with the ceiling the request was sent under. Both numbers
260
- * are the caller's own accounting — the count is what the provider reported and
261
- * the ceiling is what this session last requested — so equality means the model
262
- * was cut off, not that it chose to stop.
263
- *
264
- * The comparison is deliberately one-sided: an unknown ceiling or a missing
265
- * count reports "not exhausted", because a wrong `true` would silence the
266
- * rescue this plugin exists for, while a wrong `false` only costs the rescue
267
- * one attempt of the streak.
268
- * @param agent - the agent whose turn is closing.
269
- * @param turn - the turn being closed.
270
- * @param usage - the closing message's token accounting, when the host logged it.
271
- * @returns whether the output budget is spent.
272
- */
273
- function outputExhausted(agent, usage) {
274
- if (usage === void 0) return false;
275
- const ceiling = agent.session.requestHeader()?.config.maxTokens;
276
- if (ceiling === void 0 || ceiling <= 0) return false;
277
- return usage.outputTokens >= ceiling;
278
- }
279
- /**
280
264
  * Install the turn watcher and its continuation side effect.
281
265
  * @param ctx - plugin context; listeners are disposed with it.
282
266
  * @param config - validated limits.
283
267
  */
284
268
  function install(ctx, config) {
285
269
  const states = /* @__PURE__ */ new Map();
286
- /** Output accounting of the last assistant message, per agent. */
287
- const usageByAgent = /* @__PURE__ */ new Map();
288
270
  /** Apply one reducer event and store the result. */
289
271
  function advance(agent, event) {
290
272
  const step = reduce(states.get(agent) ?? INITIAL_STATE, event, { maxConsecutive: config.maxConsecutive });
@@ -303,9 +285,9 @@ function install(ctx, config) {
303
285
  advance(agent, {
304
286
  kind: "assistant-message",
305
287
  turn: event.data.turn,
306
- blocks: event.data.message.content
288
+ blocks: event.data.message.content,
289
+ outputTokens: event.data.usage?.outputTokens
307
290
  });
308
- usageByAgent.set(agent, event.data.usage);
309
291
  return;
310
292
  case "user/message":
311
293
  if (event.data.source.kind === "user") advance(agent, { kind: "human-message" });
@@ -326,7 +308,7 @@ function install(ctx, config) {
326
308
  kind: "turn-stopping",
327
309
  turn,
328
310
  humanPending: humanPending(agent),
329
- outputExhausted: outputExhausted(agent, usageByAgent.get(agent))
311
+ outputCeiling: agent.session.requestHeader()?.config.maxTokens
330
312
  });
331
313
  if (step.decision === "give-up") {
332
314
  ctx.logger.warn(`dsml-rescue: agent "${agent.id}" stalled ${config.maxConsecutive} times in a row; stopped continuing it`);
@@ -345,7 +327,6 @@ function install(ctx, config) {
345
327
  });
346
328
  ctx.on("agent/disposed", ({ agent }) => {
347
329
  states.delete(agent);
348
- usageByAgent.delete(agent);
349
330
  });
350
331
  }
351
332
  // src/index.ts
@@ -353,6 +334,10 @@ function install(ctx, config) {
353
334
  const name = "dsh-dsml-rescue";
354
335
  /** The agent registry, needed to map a session back to its live agent. */
355
336
  const inject = ["agents"];
337
+ /** Rescues allowed in a row before an agent is left alone. */
338
+ const DEFAULT_MAX_CONSECUTIVE = 3;
339
+ /** Whether child agents are rescued by default. */
340
+ const DEFAULT_APPLY_TO_SUBAGENTS = true;
356
341
  const Config = z.object({
357
342
  enabled: z.boolean().default(true),
358
343
  maxConsecutive: z.natural().min(1).default(3),
@@ -361,7 +346,7 @@ const Config = z.object({
361
346
  /**
362
347
  * Mount the plugin.
363
348
  * @param ctx - host context.
364
- * @param config - validated configuration.
349
+ * @param config - validated configuration; the schema fills in every default.
365
350
  */
366
351
  function apply(ctx, config) {
367
352
  if (config.enabled === false) return;
@@ -370,4 +355,4 @@ function apply(ctx, config) {
370
355
  applyToSubagents: config.applyToSubagents ?? true
371
356
  });
372
357
  }
373
- export { Config, apply, inject, name };
358
+ export { Config, DEFAULT_APPLY_TO_SUBAGENTS, DEFAULT_MAX_CONSECUTIVE, apply, inject, name };
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "DSML Rescue",
4
- "description": "Continues the turn a model stalled by writing tool-call markup into its reasoning instead of calling a tool."
4
+ "description": "Auto-continues turns stalled by DSML tool-call markup leaking into reasoning."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "DSML Rescue",
4
- "description": "模型把工具调用协议标记写进思考、导致回合空转时,自动补发一条中性指令把回合续上。"
4
+ "description": "当 DSML 工具调用标记泄漏进推理过程导致回合停滞时,自动续跑。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-dsml-rescue",
3
- "version": "0.1.0",
4
- "description": "Continue the turn a model stalled by writing tool-call markup into its reasoning",
3
+ "version": "0.2.1",
4
+ "description": "Auto-continues turns stalled by DSML tool-call markup leaking into reasoning.",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "deepseek-harness",
@@ -50,17 +50,6 @@
50
50
  "patch": "./cordis.patch.yml"
51
51
  }
52
52
  },
53
- "scripts": {
54
- "build": "tsdown",
55
- "typecheck": "tsc -p tsconfig.json",
56
- "test": "vitest run",
57
- "test:watch": "vitest",
58
- "lint": "eslint .",
59
- "format": "prettier --write .",
60
- "format:check": "prettier --check .",
61
- "check:release": "node scripts/check-release.mjs",
62
- "check": "pnpm run typecheck && pnpm run build && pnpm run test && pnpm run check:release"
63
- },
64
53
  "peerDependencies": {
65
54
  "@deepseek-ai/cordis": "^4.0.4",
66
55
  "@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
@@ -83,5 +72,16 @@
83
72
  "typescript": "^5.9.3",
84
73
  "typescript-eslint": "^8.70.1",
85
74
  "vitest": "^4.1.11"
75
+ },
76
+ "scripts": {
77
+ "build": "tsdown",
78
+ "typecheck": "tsc -p tsconfig.json",
79
+ "test": "vitest run",
80
+ "test:watch": "vitest",
81
+ "lint": "eslint .",
82
+ "format": "prettier --write .",
83
+ "format:check": "prettier --check .",
84
+ "check:release": "node scripts/check-release.mjs",
85
+ "check": "pnpm run typecheck && pnpm run build && pnpm run test && pnpm run check:release"
86
86
  }
87
- }
87
+ }