@goodandready/dsh-goal 0.2.1 → 0.2.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/lib/card-form-state.js +65 -21
- package/lib/client.js +2249 -0
- package/lib/command-handler.js +276 -250
- package/lib/goal-engine.js +1009 -0
- package/lib/index.js +982 -2
- package/package.json +63 -63
- package/docs/design/DESIGN.md +0 -184
package/lib/card-form-state.js
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Pure state and validation logic for dsh-goal settings form.
|
|
3
|
+
* Independent from DOM/React, 100% testable via node --test.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_SETTINGS = {
|
|
7
7
|
maxIterations: 25,
|
|
8
8
|
autoDrive: true,
|
|
9
9
|
enableSound: true,
|
|
10
|
+
showQuickLaunchButton: true,
|
|
11
|
+
consecutiveToolFailureLimit: 3,
|
|
12
|
+
enableBrowserNotifications: true,
|
|
13
|
+
storagePath: '',
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
+
* Parse numeric settings field:
|
|
18
|
+
* - empty string / null / undefined -> undefined (reset override)
|
|
19
|
+
* - valid integer >= min -> number
|
|
20
|
+
* - otherwise -> NaN (invalid)
|
|
21
|
+
* @param {any} raw
|
|
22
|
+
* @param {number} [min=1]
|
|
23
|
+
* @returns {number|undefined}
|
|
17
24
|
*/
|
|
18
|
-
export function parseNumberField(raw) {
|
|
25
|
+
export function parseNumberField(raw, min = 1) {
|
|
19
26
|
if (raw === '' || raw === null || raw === undefined) {
|
|
20
27
|
return undefined;
|
|
21
28
|
}
|
|
22
29
|
const n = Number(raw);
|
|
23
|
-
if (!Number.isFinite(n) || n <
|
|
30
|
+
if (!Number.isFinite(n) || n < min || !Number.isInteger(n)) {
|
|
24
31
|
return NaN;
|
|
25
32
|
}
|
|
26
33
|
return n;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
|
-
*
|
|
37
|
+
* Compute status for a specific setting field
|
|
31
38
|
* @param {string} field
|
|
32
39
|
* @param {any} draftValue
|
|
33
40
|
* @param {Object} snap - { value, base, user }
|
|
@@ -46,11 +53,10 @@ export function getFieldStatus(field, draftValue, snap) {
|
|
|
46
53
|
if (field === 'maxIterations') {
|
|
47
54
|
if (draftValue !== undefined) {
|
|
48
55
|
if (draftValue === '') {
|
|
49
|
-
// Пустая строка = сброс к базовому значению (не ошибка)
|
|
50
56
|
invalid = false;
|
|
51
|
-
isDirty = userVal !== undefined;
|
|
57
|
+
isDirty = userVal !== undefined;
|
|
52
58
|
} else {
|
|
53
|
-
const parsed = parseNumberField(draftValue);
|
|
59
|
+
const parsed = parseNumberField(draftValue, 1);
|
|
54
60
|
if (Number.isNaN(parsed)) {
|
|
55
61
|
invalid = true;
|
|
56
62
|
isDirty = true;
|
|
@@ -59,10 +65,30 @@ export function getFieldStatus(field, draftValue, snap) {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
}
|
|
68
|
+
} else if (field === 'consecutiveToolFailureLimit') {
|
|
69
|
+
if (draftValue !== undefined) {
|
|
70
|
+
if (draftValue === '') {
|
|
71
|
+
invalid = false;
|
|
72
|
+
isDirty = userVal !== undefined;
|
|
73
|
+
} else {
|
|
74
|
+
const parsed = parseNumberField(draftValue, 0);
|
|
75
|
+
if (Number.isNaN(parsed)) {
|
|
76
|
+
invalid = true;
|
|
77
|
+
isDirty = true;
|
|
78
|
+
} else {
|
|
79
|
+
isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} else if (field === 'storagePath') {
|
|
84
|
+
if (draftValue !== undefined) {
|
|
85
|
+
const cleanVal = typeof draftValue === 'string' ? draftValue.trim() : '';
|
|
86
|
+
isDirty = cleanVal !== (userVal !== undefined ? userVal : (baseVal || ''));
|
|
87
|
+
}
|
|
62
88
|
} else {
|
|
63
|
-
// boolean fields (autoDrive, enableSound)
|
|
89
|
+
// boolean fields (autoDrive, enableSound, showQuickLaunchButton, enableBrowserNotifications)
|
|
64
90
|
if (draftValue !== undefined) {
|
|
65
|
-
isDirty = draftValue !== (userVal !== undefined ? userVal : baseVal);
|
|
91
|
+
isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
|
|
66
92
|
}
|
|
67
93
|
}
|
|
68
94
|
|
|
@@ -77,7 +103,7 @@ export function getFieldStatus(field, draftValue, snap) {
|
|
|
77
103
|
}
|
|
78
104
|
|
|
79
105
|
/**
|
|
80
|
-
*
|
|
106
|
+
* Compute list of key-value writes for scope.set
|
|
81
107
|
* @param {Object|null} draft
|
|
82
108
|
* @param {Object} snap - { value, base, user }
|
|
83
109
|
* @returns {Array<[string, any]>}
|
|
@@ -86,7 +112,15 @@ export function computeSavePlan(draft, snap) {
|
|
|
86
112
|
if (!draft) return [];
|
|
87
113
|
|
|
88
114
|
const writes = [];
|
|
89
|
-
const fields = [
|
|
115
|
+
const fields = [
|
|
116
|
+
'maxIterations',
|
|
117
|
+
'autoDrive',
|
|
118
|
+
'enableSound',
|
|
119
|
+
'showQuickLaunchButton',
|
|
120
|
+
'consecutiveToolFailureLimit',
|
|
121
|
+
'enableBrowserNotifications',
|
|
122
|
+
'storagePath',
|
|
123
|
+
];
|
|
90
124
|
|
|
91
125
|
for (const f of fields) {
|
|
92
126
|
if (draft[f] === undefined) continue;
|
|
@@ -95,11 +129,21 @@ export function computeSavePlan(draft, snap) {
|
|
|
95
129
|
if (status.invalid) continue;
|
|
96
130
|
|
|
97
131
|
if (status.isDirty) {
|
|
98
|
-
if (f === 'maxIterations'
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
132
|
+
if (f === 'maxIterations') {
|
|
133
|
+
if (draft[f] === '') {
|
|
134
|
+
writes.push([f, undefined]);
|
|
135
|
+
} else {
|
|
136
|
+
writes.push([f, parseNumberField(draft[f], 1)]);
|
|
137
|
+
}
|
|
138
|
+
} else if (f === 'consecutiveToolFailureLimit') {
|
|
139
|
+
if (draft[f] === '') {
|
|
140
|
+
writes.push([f, undefined]);
|
|
141
|
+
} else {
|
|
142
|
+
writes.push([f, parseNumberField(draft[f], 0)]);
|
|
143
|
+
}
|
|
144
|
+
} else if (f === 'storagePath') {
|
|
145
|
+
const clean = typeof draft[f] === 'string' ? draft[f].trim() : '';
|
|
146
|
+
writes.push([f, clean ? clean : undefined]);
|
|
103
147
|
} else {
|
|
104
148
|
writes.push([f, Boolean(draft[f])]);
|
|
105
149
|
}
|