@goodandready/dsh-agent-loop-guard 0.2.2 → 0.2.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 CHANGED
@@ -1,3 +1,21 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2026 GooDAnDReaDY
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.
package/README.md CHANGED
@@ -1,46 +1,178 @@
1
- # @goodandready/dsh-agent-loop-guard 0.2.2
2
-
3
- Host-only DeepSeek Harness bundle that prevents tool-call loops without changing DSH core.
4
-
5
- ## Behaviour
6
-
7
- - treats a repeated call as a loop only when no successful result or state
8
- change has appeared since the previous attempt;
9
- - permits legitimate iterations with the same arguments when the result or
10
- explicit progress token changes;
11
- - allows read -> edit -> read -> edit when each step produces new evidence;
12
- - keeps Gitea/curl operations distinct by their complete operation arguments,
13
- including HTTP method and endpoint, even when they share a base URL;
14
- - uses maxToolAttemptsPerTurn and maxProgressToolCallsPerTurn as budgets
15
- since the last productive action, so productive work resets the counters;
16
- - after a loop or budget denial, enters answer-only mode for the current turn
17
- and returns a normal DSH tool denial that requires a text answer; the next
18
- turn resets that mode;
19
- - logs every LOOP_GUARD_STOP, LOOP_GUARD_LIMIT,
20
- LOOP_GUARD_PROGRESS_LIMIT, LOOP_GUARD_DUPLICATE, and
21
- LOOP_GUARD_REPEAT event with a redacted call summary and progress context;
22
- - preserves the assistant-output guard: it detects text-only loops across
23
- block/step/turn boundaries and cancels with keepInbox: false; both repeated
24
- individual lines and repeated multi-line blocks are covered.
25
-
26
- The repeat threshold remains controlled by maxCallsPerRepeatGroup (default 5),
27
- but it is evaluated against the current progress epoch rather than raw call
28
- count. A successful result is considered productive when its result fingerprint
29
- or explicit progress token differs from the last successful evidence. Failed or
30
- unknown results do not reset the guard.
31
-
32
- The legacy maxCallsPerToolPerTurn setting remains accepted as a compatibility
33
- alias. maxToolAttemptsPerTurn: 0 disables only the aggregate no-progress
34
- budget; repeat, stop, progress, and assistant-output protections remain active.
35
- Denials use the documented tools.guard API and remain normal structured DSH tool
36
- results, preserving session persistence.
37
-
38
- ## Verification
1
+ # 📦 @goodandready/dsh-agent-loop-guard
39
2
 
3
+ <div align="center">
4
+
5
+ <h3>Fail-Closed Runtime Tool-Call & Assistant Output Loop Breaker for DeepSeek Harness</h3>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-agent-loop-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-agent-loop-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-agent-loop-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/All_Author_Projects-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="All Author Projects"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="docs/README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="docs/README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## ⚡ Overview & Problem Solved
29
+
30
+ When autonomous AI coding agents encounter unexpected tool failures, ambiguous instructions, or hallucination loops, they often enter infinite retry cycles — repeatedly reading the same directory, editing identical lines without progress, or echoing identical output lines across streaming turns. These loops rapidly exhaust token budgets, lock execution threads, and waste API credits without ever completing user goals.
31
+
32
+ **`@goodandready/dsh-agent-loop-guard`** is a native host runtime plugin for DeepSeek Harness that intercepts and breaks loops before they spiral out of control:
33
+
34
+ 1. **Progress-Aware Tool-Call Guard**: Employs epoch-based state tracking so that repeated calls are blocked *only* when no meaningful state changes or new evidence appear. Legitimate productive iterations (e.g. `read` ➔ `edit` ➔ `read` ➔ `edit`) remain fully unrestricted.
35
+ 2. **Assistant Output Loop Breaker**: Detects text-only repetition (identical streaming lines or multi-line markdown blocks) across turns and steps, cleanly canceling runaway generation without damaging session history.
36
+ 3. **Answer-Only Graceful Fallback**: Instead of terminating abruptly, the plugin triggers structured DSH denials that guide the LLM into answering with text explaining the bottleneck.
37
+ 4. **Zero Core Modifications**: Implemented purely through Cordis lifecycle hooks (`ctx.tools.guard`, `agent/pre-step`, `tools/execute`, `session/event`).
38
+
39
+ ---
40
+
41
+ ## 🏗️ Architecture
42
+
43
+ ```mermaid
44
+ graph TD
45
+ subgraph DSH ["DeepSeek Harness Core Runtime"]
46
+ Turn["Agent Turn Execution<br/>(LLM Reasoning & Tool Calls)"]
47
+ Stream["Assistant Output Stream<br/>(text-delta & block-end)"]
48
+ UserStop["User Message Analysis<br/>(Stop words: stop, halt, прекрати)"]
49
+ end
50
+
51
+ subgraph LoopGuard ["@goodandready/dsh-agent-loop-guard"]
52
+ ToolInterceptor["ctx.tools.guard Interceptor<br/>(denyReason evaluation)"]
53
+ ProgressEpoch["Progress Epoch Tracker<br/>(Result & Argument Fingerprints)"]
54
+ OutputState["AssistantOutputGuardState<br/>(Line & Block Deduplication)"]
55
+ SafeLogger["Sanitized Event Logger<br/>(Token/Secret Redaction)"]
56
+ end
57
+
58
+ subgraph Actions ["Enforcement Actions"]
59
+ Allow["Allow Tool Execution<br/>(Fresh state or valid progress)"]
60
+ Deny["Answer-Only Denial<br/>(Mandates text summary from agent)"]
61
+ Cancel["Session Cancel<br/>(keepInbox: false on runaway text)"]
62
+ end
63
+
64
+ Turn -->|Pre-step & Tool call| ToolInterceptor
65
+ ToolInterceptor --> ProgressEpoch
66
+ ProgressEpoch -->|Productive Result Diff| Allow
67
+ ProgressEpoch -->|Duplicate / Budget Exceeded| Deny
68
+ Stream --> OutputState
69
+ OutputState -->|Exceeds Line/Block Repeat Limit| Cancel
70
+ UserStop -->|Immediate Latch| Deny
71
+ Deny -.-> SafeLogger
72
+ Cancel -.-> SafeLogger
73
+ ```
74
+
75
+ ---
76
+
77
+ ## ✨ Full Feature Breakdown
78
+
79
+ ### 1. Progress-Aware Tool Execution Tracking
80
+
81
+ Unlike naive counters that blindly limit tool invocations, `dsh-agent-loop-guard` distinguishes between **productive iteration** and **stagnant loops**:
82
+
83
+ * **Canonical Fingerprinting**: Creates deterministic JSON signatures of tool arguments (`callFingerprint`) and results (`resultFingerprint`).
84
+ * **Progress Epochs**: When an edit or command yields a different result, the session transitions to a new progress epoch, resetting the failure budget.
85
+ * **Granular HTTP/VCS Safety**: Distinct endpoints and HTTP methods (e.g., Gitea API operations or curl scripts) are never collapsed under a shared base URL.
86
+ * **Progress Tool Whitelist**: Designated progress tools (`todo_write`, etc.) maintain independent no-progress budgets so task checklists do not trigger false positives.
87
+
88
+ ### 2. Guard Violation Codes & Enforcement Modes
89
+
90
+ When a loop is detected, the guard rejects the call with structured diagnostics and enforces **Answer-Only Mode** for the rest of the turn:
91
+
92
+ | Guard Code | Trigger Condition | Default Limit | Guard Action |
93
+ |:---|:---|:---|:---|
94
+ | `LOOP_GUARD_STOP` | User sent stop command (`stop`, `halt`, `cancel`, `остановись`, `прекрати`, `ответь`, `петля`) | Immediate | Rejects tool call; requires immediate text reply |
95
+ | `LOOP_GUARD_DUPLICATE` | Identical call arguments with identical result to previous attempt | 1 retry | Blocks exact repeat; demands alternative approach |
96
+ | `LOOP_GUARD_REPEAT` | Repeated calls within current group without state change | `maxCallsPerRepeatGroup` (5) | Prevents spinning on single tool |
97
+ | `LOOP_GUARD_LIMIT` | Total non-productive tool attempts since last successful progress | `maxToolAttemptsPerTurn` (64) | Caps turn exploratory budget |
98
+ | `LOOP_GUARD_PROGRESS_LIMIT` | Successive invocations of progress tools without content changes | `maxProgressToolCallsPerTurn` (16) | Prevents infinite todo-writing loops |
99
+ | `LOOP_GUARD_OUTPUT` | Assistant generated identical lines or multi-line blocks | `maxRepeatedAssistantLines` (5) | Cancels session cleanly via `agent.cancel()` |
100
+
101
+ ### 3. Assistant Output Stream Guard
102
+
103
+ Runaway LLM generation can manifest as repetitive narration without tool calls. The output guard monitors streaming text in real time:
104
+
105
+ * **Line Normalization**: Collapses carriage returns and whitespace to catch formatted repetition.
106
+ * **Multi-Line Block Detection**: Hashes blocks up to `maxAssistantBlockChars` (16,384 bytes) to detect cyclical paragraph generation.
107
+ * **Active Tool Immunity**: While tool calls are executing, output cancellation is temporarily suppressed to avoid false alarms during long tasks.
108
+ * **Lossless Deduplication**: Cleanses streaming chunks and message buffers without losing valid context.
109
+
110
+ ### 4. Enterprise Privacy & Token Redaction
111
+
112
+ All loop guard warning logs automatically redact sensitive credentials:
113
+ * Bearer tokens, passwords, cookies, and query parameter secrets (`token=`, `api-key=`, `secret=`) are replaced with `[redacted]` before reaching logs.
114
+ * Deeply nested argument trees are bounded to prevent memory leaks during massive JSON payloads.
115
+
116
+ ---
117
+
118
+ ## 📦 Installation
119
+
120
+ Install via DeepSeek Harness CLI:
121
+
122
+ ```bash
123
+ dsh plugin --profile web add @goodandready/dsh-agent-loop-guard
124
+ ```
125
+
126
+ Restart DSH Web UI and reload your workspace.
127
+
128
+ ---
129
+
130
+ ## ⚙️ Configuration
131
+
132
+ Configure via `config.yaml` or through the Web UI settings:
133
+
134
+ ```yaml
135
+ # config.yaml
136
+ dsh-agent-loop-guard:
137
+ maxToolAttemptsPerTurn: 64
138
+ maxProgressToolCallsPerTurn: 16
139
+ progressToolNames:
140
+ - todo_write
141
+ maxCallsPerRepeatGroup: 5
142
+ blockExactDuplicates: true
143
+ assistantOutputGuard: true
144
+ maxRepeatedAssistantLines: 5
145
+ maxRepeatedAssistantBlocks: 5
146
+ maxAssistantBlockChars: 16384
147
+ ```
148
+
149
+ ### Settings Reference Table
150
+
151
+ | Key | Type | Default | Description |
152
+ |:---|:---|:---|:---|
153
+ | `maxToolAttemptsPerTurn` | `number` | `64` | Maximum un-productive tool attempts allowed before forcing an answer. Set to `0` to disable aggregate budget. |
154
+ | `maxProgressToolCallsPerTurn` | `number` | `16` | Maximum consecutive calls to progress tools (`todo_write`) without substantive progress. |
155
+ | `progressToolNames` | `array` | `["todo_write"]` | Array of tool names considered progress markers. |
156
+ | `maxCallsPerRepeatGroup` | `number` | `5` | Maximum tool calls allowed within the same repeat group without producing a new result. |
157
+ | `blockExactDuplicates` | `boolean` | `true` | Immediately block consecutive identical calls with zero state change. |
158
+ | `assistantOutputGuard` | `boolean` | `true` | Enable real-time detection and cancellation of repetitive assistant output loops. |
159
+ | `maxRepeatedAssistantLines` | `number` | `5` | Threshold of consecutive identical output lines to trigger session cancellation. |
160
+ | `maxRepeatedAssistantBlocks` | `number` | `5` | Threshold of repeated multi-line markdown blocks before aborting generation. |
161
+ | `maxAssistantBlockChars` | `number` | `16384` | Maximum byte length captured for block fingerprinting. |
162
+
163
+ ---
164
+
165
+ ## 🧪 Testing & Verification
166
+
167
+ Run the full automated test suite covering all loop denial paths, streaming output guards, and progress epochs:
168
+
169
+ ```bash
40
170
  npm test
41
171
  npm run check
172
+ ```
42
173
 
174
+ ---
43
175
 
44
- ## Assistant output settings
176
+ ## 📄 License
45
177
 
46
- The output guard uses maxRepeatedAssistantLines for single-line repetition and maxRepeatedAssistantBlocks (default 5) for identical multi-line blocks. maxAssistantBlockChars (default 16384) bounds the captured block fingerprint. Streaming chunks and their final assistant/message are deduplicated.
178
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
@@ -0,0 +1,170 @@
1
+ # 📦 @goodandready/dsh-agent-loop-guard
2
+
3
+ <div align="center">
4
+
5
+ <h3>Аварийный предохранитель от зацикливания вызовов инструментов и текстового потока агента для DeepSeek Harness</h3>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-agent-loop-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-agent-loop-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-agent-loop-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="../README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## ⚡ Назначение и решаемая проблема
29
+
30
+ При выполнении многоэтапных задач автономные ИИ-агенты могут попадать в петли повторных попыток: бесконечно перечитывать один и тот же файл без изменений, раз за разом безуспешно вызывать инструмент с одними и теми же параметрами или повторять одинаковые строки в потоковом ответе. Такие зацикливания приводят к мгновенному исчерпанию контекста и токенов, заморозке интерфейса и бесполезной трате бюджета.
31
+
32
+ **`@goodandready/dsh-agent-loop-guard`** — нативный плагин для среды выполнения DeepSeek Harness, предотвращающий зацикливание агента на уровне хоста без вмешательства в ядро DSH:
33
+
34
+ 1. **Контроль прогресса инструментов (Progress-Aware Epochs)**: повторные вызовы инструментов блокируются *только тогда*, когда с прошлой попытки не изменилось состояние системы или результат. Легитимные продуктивные циклы (например, `чтение` ➔ `правка` ➔ `чтение` ➔ `правка`) выполняются абсолютно свободно.
35
+ 2. **Предохранитель генерации текста (Assistant Output Loop Guard)**: отслеживает повторение отдельных строк и целых многострочных абзацев между шагами и ходами, выполняя мягкую отмену генерации без потери истории диалога.
36
+ 3. **Безопасный переход в режим ответа (Answer-Only Mode)**: при фиксации зацикливания инструмент не просто падает с ошибкой, а возвращает структурированный отказ, обязывающий модель сформулировать текстовый ответ пользователю с объяснением проблемы.
37
+ 4. **Маскирование секретов в журналах**: любые ключи доступа, токены и пароли автоматически заменяются на `[redacted]` в диагностических логах.
38
+
39
+ ---
40
+
41
+ ## 🏗️ Архитектура работы
42
+
43
+ ```mermaid
44
+ graph TD
45
+ subgraph DSH ["Среда выполнения DeepSeek Harness"]
46
+ Turn["Ход выполнения агента<br/>(Рассуждение LLM и вызовы инструментов)"]
47
+ Stream["Поток генерации ассистента<br/>(text-delta и block-end)"]
48
+ UserStop["Анализ сообщений пользователя<br/>(Стоп-слова: стоп, хватит, прекрати, петля)"]
49
+ end
50
+
51
+ subgraph LoopGuard ["@goodandready/dsh-agent-loop-guard"]
52
+ ToolInterceptor["Перехватчик ctx.tools.guard<br/>(Проверка denyReason перед запуском)"]
53
+ ProgressEpoch["Трекер эпох прогресса<br/>(Хэширование аргументов и результатов)"]
54
+ OutputState["AssistantOutputGuardState<br/>(Анализ повторов строк и блоков)"]
55
+ SafeLogger["Санитизация журналов<br/>(Маскирование Bearer/API токенов)"]
56
+ end
57
+
58
+ subgraph Actions ["Действия защиты"]
59
+ Allow["Разрешить выполнение инструмента<br/>(Новый результат / зафиксирован прогресс)"]
60
+ Deny["Отказ с переходом в режим ответа<br/>(Требование текстового резюме от агента)"]
61
+ Cancel["Мягкая отмена сессии<br/>(keepInbox: false при зацикливании текста)"]
62
+ end
63
+
64
+ Turn -->|agent/pre-step и вызов инструмента| ToolInterceptor
65
+ ToolInterceptor --> ProgressEpoch
66
+ ProgressEpoch -->|Изменение состояния подтверждено| Allow
67
+ ProgressEpoch -->|Дубликат или превышен лимит| Deny
68
+ Stream --> OutputState
69
+ OutputState -->|Превышен лимит повторов текста| Cancel
70
+ UserStop -->|Мгновенная фиксация остановки| Deny
71
+ Deny -.-> SafeLogger
72
+ Cancel -.-> SafeLogger
73
+ ```
74
+
75
+ ---
76
+
77
+ ## ✨ Подробный разбор возможностей
78
+
79
+ ### 1. Эпохи прогресса и дифференциация вызовов
80
+
81
+ В отличие от примитивных счётчиков, плагин анализирует реальную полезность действий агента:
82
+
83
+ * **Детерминированные отпечатки**: строит стабильные сигнатуры аргументов (`callFingerprint`) и результатов вызовов (`resultFingerprint`).
84
+ * **Эпохи полезного действия**: как только вызов приводит к новому результату (изменился файл, вернулся новый diff или токен прогресса), счётчик холостых попыток обнуляется.
85
+ * **Изоляция сетевых операций**: различные методы и эндпоинты (например, разные API-запросы Gitea или curl) не склеиваются по общему базовому URL.
86
+ * **Белый список инструментов отслеживания задач**: инструменты ведения списков задач (`todo_write`) обладают собственным независимым бюджетом, предотвращая ложные срабатывания при обновлении чек-листов.
87
+
88
+ ### 2. Коды нарушений и реакции предохранителя
89
+
90
+ | Код защиты | Условие срабатывания | Лимит по умолчанию | Действие плагина |
91
+ |:---|:---|:---|:---|
92
+ | `LOOP_GUARD_STOP` | Пользователь отправил команду остановки (`стоп`, `хватит`, `прекрати`, `ответь`, `петля`, `stop`, `halt`, `cancel`) | Мгновенно | Блокирует вызовы инструментов; требует немедленный текстовый ответ |
93
+ | `LOOP_GUARD_DUPLICATE` | Повторный вызов с идентичными аргументами и неизменным результатом | 1 повтор | Пресекает топтание на месте; требует сменить подход |
94
+ | `LOOP_GUARD_REPEAT` | Череда вызовов инструментов одной группы без изменения состояния | `maxCallsPerRepeatGroup` (5) | Останавливает монотонные повторы |
95
+ | `LOOP_GUARD_LIMIT` | Суммарное число бесплодных попыток за текущий ход | `maxToolAttemptsPerTurn` (64) | Ограничивает общий бюджет холостых действий |
96
+ | `LOOP_GUARD_PROGRESS_LIMIT` | Повторные вызовы progress-инструментов без реального продвижения | `maxProgressToolCallsPerTurn` (16) | Блокирует бесконечную перезапись todo |
97
+ | `LOOP_GUARD_OUTPUT` | Модель генерирует одинаковые строки или блоки текста | `maxRepeatedAssistantLines` (5) | Мягко прерывает сессию через `agent.cancel()` |
98
+
99
+ ### 3. Предотвращение зацикливания текстового вывода
100
+
101
+ Иногда модель зацикливается не на инструментах, а на генерации текста (повторяет одну фразу в цикле streaming):
102
+
103
+ * **Нормализация строк**: очищает невидимые символы переноса и множественные пробелы.
104
+ * **Детекция абзацев**: хэширует блоки текста до `maxAssistantBlockChars` (16 384 байт).
105
+ * **Иммунитет во время выполнения инструментов**: пока агент ждёт завершения длительной команды, отмена текста временно блокируется.
106
+ * **Чистый сброс**: при отправке нового сообщения пользователем состояние детектора полностью очищается.
107
+
108
+ ---
109
+
110
+ ## 📦 Установка
111
+
112
+ Установка через командную строку DeepSeek Harness:
113
+
114
+ ```bash
115
+ dsh plugin --profile web add @goodandready/dsh-agent-loop-guard
116
+ ```
117
+
118
+ Перезапустите DSH и обновите вкладку браузера.
119
+
120
+ ---
121
+
122
+ ## ⚙️ Конфигурация
123
+
124
+ Настройка через файл `config.yaml` или панель управления DSH:
125
+
126
+ ```yaml
127
+ # config.yaml
128
+ dsh-agent-loop-guard:
129
+ maxToolAttemptsPerTurn: 64
130
+ maxProgressToolCallsPerTurn: 16
131
+ progressToolNames:
132
+ - todo_write
133
+ maxCallsPerRepeatGroup: 5
134
+ blockExactDuplicates: true
135
+ assistantOutputGuard: true
136
+ maxRepeatedAssistantLines: 5
137
+ maxRepeatedAssistantBlocks: 5
138
+ maxAssistantBlockChars: 16384
139
+ ```
140
+
141
+ ### Таблица параметров конфигурации
142
+
143
+ | Параметр | Тип | По умолчанию | Описание |
144
+ |:---|:---|:---|:---|
145
+ | `maxToolAttemptsPerTurn` | `number` | `64` | Максимум холостых вызовов за ход. Значение `0` отключает суммарный бюджет. |
146
+ | `maxProgressToolCallsPerTurn` | `number` | `16` | Максимум вызовов progress-инструментов (`todo_write`) подряд без изменений. |
147
+ | `progressToolNames` | `array` | `["todo_write"]` | Список имён инструментов, маркирующих прогресс задач. |
148
+ | `maxCallsPerRepeatGroup` | `number` | `5` | Максимум повторений инструментов одной группы без смены результата. |
149
+ | `blockExactDuplicates` | `boolean` | `true` | Немедленно блокировать повторные идентичные вызовы без изменения состояния. |
150
+ | `assistantOutputGuard` | `boolean` | `true` | Включить мониторинг зацикливания текстового потока модели. |
151
+ | `maxRepeatedAssistantLines` | `number` | `5` | Порог повторяющихся одинаковых строк текста для прерывания генерации. |
152
+ | `maxRepeatedAssistantBlocks` | `number` | `5` | Порог повторяющихся многострочных блоков до отмены хода. |
153
+ | `maxAssistantBlockChars` | `number` | `16384` | Максимальный размер текстового блока для хэширования (в байтах). |
154
+
155
+ ---
156
+
157
+ ## 🧪 Тестирование
158
+
159
+ Запуск набора из 26 автоматизированных тестов и статической проверки типов:
160
+
161
+ ```bash
162
+ npm test
163
+ npm run check
164
+ ```
165
+
166
+ ---
167
+
168
+ ## 📄 Лицензия
169
+
170
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
@@ -0,0 +1,168 @@
1
+ # 📦 @goodandready/dsh-agent-loop-guard
2
+
3
+ <div align="center">
4
+
5
+ <h3>面向 DeepSeek Harness 的工具调用与智能体流式输出死循环熔断引擎</h3>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-agent-loop-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-agent-loop-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-agent-loop-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者全部项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="作者全部项目"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="../README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## ⚡ 核心定位与解决痛点
29
+
30
+ 在自主 AI 智能体执行复杂多阶段研发任务时,遇到工具调用异常、指令模糊或模型幻觉时,极易陷入无限重试死循环:反复读取同一文件、以完全相同的参数反复调用工具却无实际产出,或在流式输出中无限重复相同的文本。此类死循环不仅迅速耗尽上下文 Token 预算,还会导致 UI 卡死与 API 额度浪费。
31
+
32
+ **`@goodandready/dsh-agent-loop-guard`** 是专为 DeepSeek Harness 打造的原生宿主运行时防死循环熔断插件,无需修改 DSH 核心代码即可提供全面防护:
33
+
34
+ 1. **进度感知型工具防循环(Progress-Aware Epochs)**:仅在工具调用未产生任何状态变化或新证据时判定为死循环。合法的有效迭代(如 `读` ➔ `写` ➔ `读` ➔ `写`)完全不受限制。
35
+ 2. **文本输出流防死循环(Assistant Output Guard)**:实时监测智能体流式输出,精准识别跨回合或跨步骤的单行及多行 Markdown 段落重复,安全中断生成而不破坏历史对话。
36
+ 3. **平滑降级至仅回答模式(Answer-Only Mode)**:触发死循环拦截时,向模型返回结构化 DSH 拒绝信息,强制要求模型输出文本向用户说明当前遇到的瓶颈。
37
+ 4. **日志隐私脱敏**:所有安全警告日志自动对 Token、密码、API Key 等敏感数据进行 `[redacted]` 掩码处理。
38
+
39
+ ---
40
+
41
+ ## 🏗️ 架构设计
42
+
43
+ ```mermaid
44
+ graph TD
45
+ subgraph DSH ["DeepSeek Harness 核心运行时"]
46
+ Turn["智能体回合执行<br/>(LLM 推理与工具调用)"]
47
+ Stream["助手流式输出<br/>(text-delta 与 block-end)"]
48
+ UserStop["用户指令分析<br/>(停止词识别: stop, halt, 停止)"]
49
+ end
50
+
51
+ subgraph LoopGuard ["@goodandready/dsh-agent-loop-guard"]
52
+ ToolInterceptor["ctx.tools.guard 拦截器<br/>(执行前校验 denyReason)"]
53
+ ProgressEpoch["进度纪元追踪器<br/>(参数与结果哈希签名)"]
54
+ OutputState["AssistantOutputGuardState<br/>(重复单行与段落判定)"]
55
+ SafeLogger["脱敏事件日志器<br/>(敏感凭证掩码)"]
56
+ end
57
+
58
+ subgraph Actions ["防护动作"]
59
+ Allow["放行工具执行<br/>(产生新结果或有效进展)"]
60
+ Deny["拦截并强制文本回答<br/>(要求智能体总结原因)"]
61
+ Cancel["平滑取消当前会话<br/>(keepInbox: false 中止失控输出)"]
62
+ end
63
+
64
+ Turn -->|agent/pre-step & 工具调用| ToolInterceptor
65
+ ToolInterceptor --> ProgressEpoch
66
+ ProgressEpoch -->|确认产生新状态| Allow
67
+ ProgressEpoch -->|重复无进展或超额| Deny
68
+ Stream --> OutputState
69
+ OutputState -->|超出文本重复阈值| Cancel
70
+ UserStop -->|即刻锁定停止| Deny
71
+ Deny -.-> SafeLogger
72
+ Cancel -.-> SafeLogger
73
+ ```
74
+
75
+ ---
76
+
77
+ ## ✨ 核心特性深度解析
78
+
79
+ ### 1. 进度感知型工具调用判定
80
+
81
+ 与盲目统计调用次数的简单计数器不同,本插件精准区分**有效迭代**与**停滞循环**:
82
+
83
+ * **确定性指纹签名**:为调用参数(`callFingerprint`)与返回结果(`resultFingerprint`)生成确定性 JSON 指纹。
84
+ * **进度纪元追踪(Progress Epochs)**:一旦操作产生新证据(如文件修改成功、返回新差异或进度 Token),无进展计数器立即重置。
85
+ * **网络与 VCS 细粒度隔离**:不同的 HTTP 端点或请求方法(如 Gitea API 的不同资源操作)绝不会因基础域名相同而发生误判。
86
+ * **进度工具独立白名单**:专用于维护任务清单的工具(如 `todo_write`)拥有独立的无进展预算,避免更新进度时误触拦截。
87
+
88
+ ### 2. 拦截代码速查与触发策略
89
+
90
+ | 拦截代码 | 触发场景 | 默认阈值 | 防护动作 |
91
+ |:---|:---|:---|:---|
92
+ | `LOOP_GUARD_STOP` | 用户发送了终止或要求回答的指令(`stop`, `halt`, `cancel`, `停止`, `等等`, `回答`) | 立即触发 | 拦截后续工具调用,强制智能体立即返回文本答复 |
93
+ | `LOOP_GUARD_DUPLICATE` | 连续以完全相同参数调用工具且返回结果毫无变化 | 1 次重复 | 阻止原地踏步,强制更换执行策略 |
94
+ | `LOOP_GUARD_REPEAT` | 同一工具组在未产生新状态的情况下连续重复调用 | `maxCallsPerRepeatGroup` (5) | 防止单一工具过度空转 |
95
+ | `LOOP_GUARD_LIMIT` | 当前回合自上次产出有效进展以来的总无效调用次数超标 | `maxToolAttemptsPerTurn` (64) | 限制单回合探索预算上限 |
96
+ | `LOOP_GUARD_PROGRESS_LIMIT` | 连续调用进度工具而未产生任何任务变更 | `maxProgressToolCallsPerTurn` (16) | 防止陷入无限修改清单死循环 |
97
+ | `LOOP_GUARD_OUTPUT` | 智能体在流式输出中重复输出相同单行或完整段落 | `maxRepeatedAssistantLines` (5) | 通过 `agent.cancel()` 安全中断当前输出 |
98
+
99
+ ### 3. 流式文本防死循环机制
100
+
101
+ * **行规范化**:自动剔除多余空格与不可见回车符,精准捕获带格式的文本重复。
102
+ * **段落哈希**:支持最长 `maxAssistantBlockChars` (16,384 字节) 的多行 Markdown 块哈希比对。
103
+ * **长耗时工具豁免**:在工具实际执行期间,文本中断检测自动保持静默,避免长任务被误杀。
104
+ * **用户输入无损重置**:用户发起新对话轮次时,检测状态自动全量清理重置。
105
+
106
+ ---
107
+
108
+ ## 📦 快速安装
109
+
110
+ 通过 DeepSeek Harness CLI 一键安装:
111
+
112
+ ```bash
113
+ dsh plugin --profile web add @goodandready/dsh-agent-loop-guard
114
+ ```
115
+
116
+ 重启 DSH 并刷新浏览器工作区。
117
+
118
+ ---
119
+
120
+ ## ⚙️ 配置指南
121
+
122
+ 在 `config.yaml` 或 Web UI 设置面板中配置:
123
+
124
+ ```yaml
125
+ # config.yaml
126
+ dsh-agent-loop-guard:
127
+ maxToolAttemptsPerTurn: 64
128
+ maxProgressToolCallsPerTurn: 16
129
+ progressToolNames:
130
+ - todo_write
131
+ maxCallsPerRepeatGroup: 5
132
+ blockExactDuplicates: true
133
+ assistantOutputGuard: true
134
+ maxRepeatedAssistantLines: 5
135
+ maxRepeatedAssistantBlocks: 5
136
+ maxAssistantBlockChars: 16384
137
+ ```
138
+
139
+ ### 配置参数参考表
140
+
141
+ | 参数名 | 类型 | 默认值 | 功能说明 |
142
+ |:---|:---|:---|:---|
143
+ | `maxToolAttemptsPerTurn` | `number` | `64` | 单回合最大无进展工具调用预算。设为 `0` 可禁用此聚合上限。 |
144
+ | `maxProgressToolCallsPerTurn` | `number` | `16` | 进度标记工具(`todo_write`)连续无进展调用的上限。 |
145
+ | `progressToolNames` | `array` | `["todo_write"]` | 标记任务进度的工具名称数组。 |
146
+ | `maxCallsPerRepeatGroup` | `number` | `5` | 同一组工具未产生新结果时允许调用的最大次数。 |
147
+ | `blockExactDuplicates` | `boolean` | `true` | 是否立即拦截结果毫无变化的连续相同调用。 |
148
+ | `assistantOutputGuard` | `boolean` | `true` | 是否开启助手流式文本输出防死循环监测。 |
149
+ | `maxRepeatedAssistantLines` | `number` | `5` | 触发输出中断的连续相同单行阈值。 |
150
+ | `maxRepeatedAssistantBlocks` | `number` | `5` | 触发输出中断的连续重复段落阈值。 |
151
+ | `maxAssistantBlockChars` | `number` | `16384` | 捕获用于段落指纹比对的最大字符数。 |
152
+
153
+ ---
154
+
155
+ ## 🧪 测试与校验
156
+
157
+ 运行全部 26 个单元测试及静态代码检查:
158
+
159
+ ```bash
160
+ npm test
161
+ npm run check
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 📄 开源许可证
167
+
168
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-agent-loop-guard",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Fail-closed runtime tool-call loop guard for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -13,8 +13,6 @@
13
13
  "lib",
14
14
  "cordis.patch.yml",
15
15
  "README.md",
16
- "README.ru.md",
17
- "README.zh.md",
18
16
  "docs",
19
17
  "LICENSE"
20
18
  ],
package/README.ru.md DELETED
@@ -1,46 +0,0 @@
1
- # @goodandready/dsh-agent-loop-guard 0.2.2
2
-
3
- Host-only DeepSeek Harness bundle that prevents tool-call loops without changing DSH core.
4
-
5
- ## Behaviour
6
-
7
- - treats a repeated call as a loop only when no successful result or state
8
- change has appeared since the previous attempt;
9
- - permits legitimate iterations with the same arguments when the result or
10
- explicit progress token changes;
11
- - allows read -> edit -> read -> edit when each step produces new evidence;
12
- - keeps Gitea/curl operations distinct by their complete operation arguments,
13
- including HTTP method and endpoint, even when they share a base URL;
14
- - uses maxToolAttemptsPerTurn and maxProgressToolCallsPerTurn as budgets
15
- since the last productive action, so productive work resets the counters;
16
- - after a loop or budget denial, enters answer-only mode for the current turn
17
- and returns a normal DSH tool denial that requires a text answer; the next
18
- turn resets that mode;
19
- - logs every LOOP_GUARD_STOP, LOOP_GUARD_LIMIT,
20
- LOOP_GUARD_PROGRESS_LIMIT, LOOP_GUARD_DUPLICATE, and
21
- LOOP_GUARD_REPEAT event with a redacted call summary and progress context;
22
- - preserves the assistant-output guard: it detects text-only loops across
23
- block/step/turn boundaries and cancels with keepInbox: false; both repeated
24
- individual lines and repeated multi-line blocks are covered.
25
-
26
- The repeat threshold remains controlled by maxCallsPerRepeatGroup (default 5),
27
- but it is evaluated against the current progress epoch rather than raw call
28
- count. A successful result is considered productive when its result fingerprint
29
- or explicit progress token differs from the last successful evidence. Failed or
30
- unknown results do not reset the guard.
31
-
32
- The legacy maxCallsPerToolPerTurn setting remains accepted as a compatibility
33
- alias. maxToolAttemptsPerTurn: 0 disables only the aggregate no-progress
34
- budget; repeat, stop, progress, and assistant-output protections remain active.
35
- Denials use the documented tools.guard API and remain normal structured DSH tool
36
- results, preserving session persistence.
37
-
38
- ## Verification
39
-
40
- npm test
41
- npm run check
42
-
43
-
44
- ## Assistant output settings
45
-
46
- The output guard uses maxRepeatedAssistantLines for single-line repetition and maxRepeatedAssistantBlocks (default 5) for identical multi-line blocks. maxAssistantBlockChars (default 16384) bounds the captured block fingerprint. Streaming chunks and their final assistant/message are deduplicated.
package/README.zh.md DELETED
@@ -1,46 +0,0 @@
1
- # @goodandready/dsh-agent-loop-guard 0.2.2
2
-
3
- Host-only DeepSeek Harness bundle that prevents tool-call loops without changing DSH core.
4
-
5
- ## Behaviour
6
-
7
- - treats a repeated call as a loop only when no successful result or state
8
- change has appeared since the previous attempt;
9
- - permits legitimate iterations with the same arguments when the result or
10
- explicit progress token changes;
11
- - allows read -> edit -> read -> edit when each step produces new evidence;
12
- - keeps Gitea/curl operations distinct by their complete operation arguments,
13
- including HTTP method and endpoint, even when they share a base URL;
14
- - uses maxToolAttemptsPerTurn and maxProgressToolCallsPerTurn as budgets
15
- since the last productive action, so productive work resets the counters;
16
- - after a loop or budget denial, enters answer-only mode for the current turn
17
- and returns a normal DSH tool denial that requires a text answer; the next
18
- turn resets that mode;
19
- - logs every LOOP_GUARD_STOP, LOOP_GUARD_LIMIT,
20
- LOOP_GUARD_PROGRESS_LIMIT, LOOP_GUARD_DUPLICATE, and
21
- LOOP_GUARD_REPEAT event with a redacted call summary and progress context;
22
- - preserves the assistant-output guard: it detects text-only loops across
23
- block/step/turn boundaries and cancels with keepInbox: false; both repeated
24
- individual lines and repeated multi-line blocks are covered.
25
-
26
- The repeat threshold remains controlled by maxCallsPerRepeatGroup (default 5),
27
- but it is evaluated against the current progress epoch rather than raw call
28
- count. A successful result is considered productive when its result fingerprint
29
- or explicit progress token differs from the last successful evidence. Failed or
30
- unknown results do not reset the guard.
31
-
32
- The legacy maxCallsPerToolPerTurn setting remains accepted as a compatibility
33
- alias. maxToolAttemptsPerTurn: 0 disables only the aggregate no-progress
34
- budget; repeat, stop, progress, and assistant-output protections remain active.
35
- Denials use the documented tools.guard API and remain normal structured DSH tool
36
- results, preserving session persistence.
37
-
38
- ## Verification
39
-
40
- npm test
41
- npm run check
42
-
43
-
44
- ## Assistant output settings
45
-
46
- The output guard uses maxRepeatedAssistantLines for single-line repetition and maxRepeatedAssistantBlocks (default 5) for identical multi-line blocks. maxAssistantBlockChars (default 16384) bounds the captured block fingerprint. Streaming chunks and their final assistant/message are deduplicated.