@goodandready/dsh-goal 0.1.1 → 0.1.2
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 +14 -0
- package/README.ru.md +14 -0
- package/docs/design/DESIGN.md +9 -5
- package/lib/card-form-state.js +110 -0
- package/lib/client.js +700 -159
- package/lib/command-handler.js +189 -0
- package/lib/goal-engine.js +86 -6
- package/lib/index.js +279 -13
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -26,6 +26,20 @@
|
|
|
26
26
|
dsh plugin add @goodandready/dsh-goal
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Requires DeepSeek Harness core **0.1.2-rc.1** or newer: the client half declares no external client modules (`dsh.client.inject: []`) because `slots` / `locale` / `settingsScope` are kernel services in rc.1, and plugin settings are registered through a schemastery schema.
|
|
30
|
+
|
|
31
|
+
## Settings
|
|
32
|
+
|
|
33
|
+
Settings live in the plugin card under **Settings → Plugins → Plugin settings** ("Goal Mode & Autonomous Loop"). The card reads the live settings snapshot (and renders nothing while the namespace is not `ready`) and saves only changed fields; a failed write keeps your drafts on screen.
|
|
34
|
+
|
|
35
|
+
| Field | Type | Default | Purpose |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `maxIterations` | number | `25` | Safety limit: max autonomous iterations per goal |
|
|
38
|
+
| `autoDrive` | boolean | `true` | Keep the autonomous loop running after each turn |
|
|
39
|
+
| `enableSound` | boolean | `true` | Play a sound when a goal completes |
|
|
40
|
+
|
|
41
|
+
Settings namespace: `dsh-goal`.
|
|
42
|
+
|
|
29
43
|
## Quick Start
|
|
30
44
|
|
|
31
45
|
1. Start a goal in chat:
|
package/README.ru.md
CHANGED
|
@@ -30,6 +30,20 @@
|
|
|
30
30
|
dsh plugin add @goodandready/dsh-goal
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Требуется ядро DeepSeek Harness **0.1.2-rc.1** или новее: клиентская половина не объявляет внешних клиентских модулей (`dsh.client.inject: []`), потому что в rc.1 `slots` / `locale` / `settingsScope` — встроенные службы ядра, а настройки плагина регистрируются schemastery-схемой.
|
|
34
|
+
|
|
35
|
+
## Настройки
|
|
36
|
+
|
|
37
|
+
Настройки живут в карточке плагина: **Настройки → Плагины → Настройки плагинов** («Цели и автономный режим (Goal Mode)»). Карточка читает живой снимок пространства настроек (пока пространство не в статусе `ready`, карточка не рисуется) и сохраняет только изменённые поля; неудачная запись не стирает введённые значения.
|
|
38
|
+
|
|
39
|
+
| Поле | Тип | По умолчанию | Назначение |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `maxIterations` | число | `25` | Safety-лимит: максимум автономных итераций на цель |
|
|
42
|
+
| `autoDrive` | булево | `true` | Продолжать автономный цикл после каждого хода |
|
|
43
|
+
| `enableSound` | булево | `true` | Звуковой сигнал по завершении цели |
|
|
44
|
+
|
|
45
|
+
Пространство настроек: `dsh-goal`.
|
|
46
|
+
|
|
33
47
|
## Использование
|
|
34
48
|
|
|
35
49
|
1. Поставьте цель сообщением в чате:
|
package/docs/design/DESIGN.md
CHANGED
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
- **Футер**: Кнопки ручного добавления шага, паузы и закрытия.
|
|
31
31
|
|
|
32
32
|
### 2.3 Settings Card (`settings.plugin.item`)
|
|
33
|
-
- Размещение во вкладке «Настройки →
|
|
34
|
-
-
|
|
33
|
+
- Размещение во вкладке «Настройки → Плагины → Настройки плагинов», свёрнута по умолчанию, раскрытие по клику по заголовку (`aria-expanded`).
|
|
34
|
+
- Ключ слота (`key`) — пространство настроек `dsh-goal`; пока снимок пространства не в статусе `ready`, карточка не рисуется; при `writable: false` поля и кнопки заблокированы.
|
|
35
|
+
- Параметры (фактический набор, регистрируется schemastery-схемой):
|
|
35
36
|
- `maxIterations`: максимальное число автономных шагов до принудительной остановки (дефолт: 25);
|
|
36
|
-
- `autoDrive`: автоматически
|
|
37
|
-
- `enableSound`: звуковой сигнал по завершении цели (дефолт: true)
|
|
38
|
-
|
|
37
|
+
- `autoDrive`: автоматически продолжать цикл после каждого хода (дефолт: true);
|
|
38
|
+
- `enableSound`: звуковой сигнал по завершении цели (дефолт: true).
|
|
39
|
+
- Поля читаются из живого снимка пространства (`ctx.settingsScope.bind({ namespace })`), кнопки «Сбросить» / «Сохранить» в футере; сохраняются только изменённые поля, все записи выполняются подряд (не до первой ошибки), результат сверяется обратным чтением; неудачная запись не стирает введённые значения.
|
|
40
|
+
- `Changed in ядро 0.1.2-rc.1`: поле `promptTemplate` никогда не было реализовано в коде и из схемы удалено из описания; контракт карточки переехал на встроенные службы ядра (`slots` / `locale` / `settingsScope`), `dsh.client.inject` пуст.
|
|
39
41
|
|
|
40
42
|
---
|
|
41
43
|
|
|
@@ -70,3 +72,5 @@
|
|
|
70
72
|
1. **Единый префикс классов**: `.dsh-goal-*` во всех CSS-правилах во избежание конфликтов стилей.
|
|
71
73
|
2. **Точный макет баннера**: скругление углов 8–10px, шапка с отступами `8px 16px`, выравнивание по центру, кнопки действий справа с `gap: 10px`.
|
|
72
74
|
3. **Безопасность импортов**: все `require('@deepseek-ai/dsh-client-ui-primitives')` обёрнуты в `try/catch` с SVG-фоллбеками.
|
|
75
|
+
4. **2026-09-04 — rc.1-совместимость клиента**: `dsh.client.inject: []` — в boot-графе клиента ядра 0.1.2-rc.1 нет package-строк `dsh-client-runtime` (модуля в ядре нет вовсе) и `dsh-client-ui-slots`; `slots`/`locale`/`settingsScope` — встроенные службы ядра. `react`, `@deepseek-ai/dsh-client-ui-slots`, `@deepseek-ai/dsh-client-ui-primitives` отвечаются статической seed-таблицей шелла, но динамических package-строк под них в boot-графе нет — объявлять их в inject нельзя. Условие пересмотра: появление этих package-строк в boot-таблице ядра.
|
|
76
|
+
5. **2026-09-04 — контракт карточки настроек**: key слота = пространство настроек (`dsh-goal`), чтение через `ctx.settingsScope.bind`, решение по статусу снимка (loading/unavailable/ready + writable), сохранение всех изменённых полей с обратной сверкой. Причина: контракт вкладки «Настройки → Плагины» ядра rc.1 (BashCard/AgentLoopCard как эталон). Пересмотр — при изменении контракта слота в ядре.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Чистая логика состояния и валидации формы настроек dsh-goal.
|
|
3
|
+
* Не зависит от DOM/React, полностью тестируема через node --test.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_SETTINGS = {
|
|
7
|
+
maxIterations: 25,
|
|
8
|
+
autoDrive: true,
|
|
9
|
+
enableSound: true,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Парсинг числового значения настроек:
|
|
14
|
+
* - пустая строка / null / undefined -> undefined (сброс к базовому значению)
|
|
15
|
+
* - валидное число >= 1 -> number
|
|
16
|
+
* - иначе -> NaN (invalid)
|
|
17
|
+
*/
|
|
18
|
+
export function parseNumberField(raw) {
|
|
19
|
+
if (raw === '' || raw === null || raw === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const n = Number(raw);
|
|
23
|
+
if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
|
|
24
|
+
return NaN;
|
|
25
|
+
}
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Вычисление состояния конкретного поля:
|
|
31
|
+
* @param {string} field
|
|
32
|
+
* @param {any} draftValue
|
|
33
|
+
* @param {Object} snap - { value, base, user }
|
|
34
|
+
*/
|
|
35
|
+
export function getFieldStatus(field, draftValue, snap) {
|
|
36
|
+
const baseVal = snap?.base?.[field] ?? DEFAULT_SETTINGS[field];
|
|
37
|
+
const userVal = snap?.user?.[field];
|
|
38
|
+
const effectiveVal = snap?.value?.[field] ?? baseVal;
|
|
39
|
+
|
|
40
|
+
const isOverridden = userVal !== undefined && userVal !== null;
|
|
41
|
+
const currentVal = draftValue !== undefined ? draftValue : effectiveVal;
|
|
42
|
+
|
|
43
|
+
let invalid = false;
|
|
44
|
+
let isDirty = false;
|
|
45
|
+
|
|
46
|
+
if (field === 'maxIterations') {
|
|
47
|
+
if (draftValue !== undefined) {
|
|
48
|
+
if (draftValue === '') {
|
|
49
|
+
// Пустая строка = сброс к базовому значению (не ошибка)
|
|
50
|
+
invalid = false;
|
|
51
|
+
isDirty = userVal !== undefined; // dirty если было переопределено
|
|
52
|
+
} else {
|
|
53
|
+
const parsed = parseNumberField(draftValue);
|
|
54
|
+
if (Number.isNaN(parsed)) {
|
|
55
|
+
invalid = true;
|
|
56
|
+
isDirty = true;
|
|
57
|
+
} else {
|
|
58
|
+
isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
// boolean fields (autoDrive, enableSound)
|
|
64
|
+
if (draftValue !== undefined) {
|
|
65
|
+
isDirty = draftValue !== (userVal !== undefined ? userVal : baseVal);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
value: currentVal,
|
|
71
|
+
baseValue: baseVal,
|
|
72
|
+
userValue: userVal,
|
|
73
|
+
isOverridden,
|
|
74
|
+
isDirty,
|
|
75
|
+
invalid,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Вычисление списка операций записи (writes) для scope.set:
|
|
81
|
+
* @param {Object|null} draft
|
|
82
|
+
* @param {Object} snap - { value, base, user }
|
|
83
|
+
* @returns {Array<[string, any]>}
|
|
84
|
+
*/
|
|
85
|
+
export function computeSavePlan(draft, snap) {
|
|
86
|
+
if (!draft) return [];
|
|
87
|
+
|
|
88
|
+
const writes = [];
|
|
89
|
+
const fields = ['maxIterations', 'autoDrive', 'enableSound'];
|
|
90
|
+
|
|
91
|
+
for (const f of fields) {
|
|
92
|
+
if (draft[f] === undefined) continue;
|
|
93
|
+
|
|
94
|
+
const status = getFieldStatus(f, draft[f], snap);
|
|
95
|
+
if (status.invalid) continue;
|
|
96
|
+
|
|
97
|
+
if (status.isDirty) {
|
|
98
|
+
if (f === 'maxIterations' && draft[f] === '') {
|
|
99
|
+
// Сброс переопределения
|
|
100
|
+
writes.push([f, undefined]);
|
|
101
|
+
} else if (f === 'maxIterations') {
|
|
102
|
+
writes.push([f, parseNumberField(draft[f])]);
|
|
103
|
+
} else {
|
|
104
|
+
writes.push([f, Boolean(draft[f])]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return writes;
|
|
110
|
+
}
|